Indefero

Indefero Commit Details


Date:2008-11-30 03:26:05 (16 years 22 days ago)
Author:Loic d'Anterroches
Branch:dev, develop, feature-issue_links, feature.better-home, feature.content-md5, feature.diff-whitespace, feature.download-md5, feature.issue-links, feature.issue-of-others, feature.issue-summary, feature.search-filter, feature.webrepos, feature.wiki-default-page, master, release-1.1, release-1.2, release-1.3
Commit:f690968b1149970a989bfa5e6114032b65d7f8f4
Parents: fbe364462dfb467305a1bfd67016a9c2d5b4e8d9
Message: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.
Changes:

File differences

src/IDF/Diff.php
169169
170170
171171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
172330
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 = '&nbsp;';
$line2 = '&nbsp;';
$line[2] = (strlen($line[2])) ? self::padLine(Pluf_esc($line[2])) : '&nbsp;';
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>&nbsp;</td><td>...</td><td>&nbsp;</td></tr>'."\n";
$cc++;
$i++;
}
return Pluf_Template::markSafe($out);
}
}
src/IDF/Form/ReviewCommentFile.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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;
}
}
src/IDF/Form/ReviewCreate.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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;
}
}
src/IDF/Form/ReviewFileComment.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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;
}
}
src/IDF/Form/TabsConf.php
3636
3737
3838
39
3940
4041
4142
$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'),);
src/IDF/Middleware.php
5454
5555
5656
57
5758
5859
5960
$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();
src/IDF/Migrations/8CodeReview.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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();
}
}
src/IDF/Migrations/Install.php
4242
4343
4444
45
46
47
4548
4649
4750
......
7982
8083
8184
85
86
87
8288
8389
8490
'IDF_Timeline',
'IDF_WikiPage',
'IDF_WikiRevision',
'IDF_Review',
'IDF_Review_Patch',
'IDF_Review_FileComment',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
$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',
src/IDF/Precondition.php
152152
153153
154154
155
155
156
157
158
159
160
161
162
163
164
156165
157166
158167
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.
*
* API calls are not translated.
src/IDF/Review.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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 '';
}
}
src/IDF/Review/FileComment.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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 '';
}
}
src/IDF/Review/Patch.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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)
{
}
}
src/IDF/Scm.php
5555
5656
5757
58
58
5959
6060
6161
$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));
src/IDF/Tests/TestDiff.php
5454
5555
5656
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
5774
$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));
}
}
src/IDF/Tests/test-diff-view.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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>&nbsp;</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}
src/IDF/Tests/test-diff.diff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
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>&nbsp;</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>&nbsp;</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
src/IDF/Views/Project.php
347347
348348
349349
350
350
351351
352352
353353
$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) {
src/IDF/Views/Review.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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;
}
src/IDF/conf/idf.php-dist
2424
2525
2626
27
27
2828
2929
30
31
32
3033
3134
3235
$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,
src/IDF/conf/urls.php
283283
284284
285285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
286312
287313
288314
'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 --------------------------------------
src/IDF/relations.php
3333
3434
3535
36
37
38
39
3640
3741
$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;
src/IDF/templates/idf/admin/tabs.html
3636
3737
3838
39
40
41
42
43
44
3945
4046
4147
</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>
src/IDF/templates/idf/base-full.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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>
src/IDF/templates/idf/base-simple.html
5151
5252
5353
54
54
5555
5656
5757
</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'}
src/IDF/templates/idf/base.html
4747
4848
4949
50
5051
5152
5253
{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>
src/IDF/templates/idf/index.html
1313
1414
1515
16
{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}
src/IDF/templates/idf/issues/create.html
113113
114114
115115
116
117
116
118117
119118
}
});
</script>
{/literal}{/block}
{/literal}
{include 'idf/issues/js-autocomplete.html'}{/block}
src/IDF/templates/idf/project/home.html
4040
4141
4242
43
43
</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}
src/IDF/templates/idf/review/base-full.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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}
src/IDF/templates/idf/review/base.html
1
2
3
4
5
6
7
8
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}
src/IDF/templates/idf/review/create.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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>&nbsp;</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}
src/IDF/templates/idf/review/index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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}
src/IDF/templates/idf/review/view.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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>&nbsp;</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>&nbsp;</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>&nbsp;</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}
www/media/idf/css/style.css
6969
7070
7171
72
73
74
75
76
77
78
79
7280
7381
7482
......
518526
519527
520528
529
521530
522531
523532
......
633642
634643
635644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
color: #777;
}
a.soft {
color: #777;
}
a.soft:visited {
color: #777;
}
div.context {
padding-left: 1em;
}
padding: 1px 5px;
}
/**
* view file content
*/
.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;
}

Archive Download the corresponding diff file

Page rendered in 0.21687s using 14 queries.