Initial commit.
This commit is contained in:
commit
efbd82fccb
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
tmp
|
||||||
|
src/IDF/conf/idf.php
|
||||||
|
src/IDF/conf/idf.test.php
|
||||||
|
www/test.php
|
133
src/IDF/Conf.php
Normal file
133
src/IDF/Conf.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of a project.
|
||||||
|
*
|
||||||
|
* It is just storing a list of key/value
|
||||||
|
* pairs. We can that way store quite a lot of data.
|
||||||
|
*/
|
||||||
|
class IDF_Conf extends Pluf_Model
|
||||||
|
{
|
||||||
|
public $_model = __CLASS__;
|
||||||
|
public $datacache = null;
|
||||||
|
public $f = null;
|
||||||
|
protected $_project = null;
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
$this->_a['table'] = 'idf_conf';
|
||||||
|
$this->_a['model'] = __CLASS__;
|
||||||
|
$this->_a['cols'] = array(
|
||||||
|
// It is mandatory to have an "id" column.
|
||||||
|
'id' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Sequence',
|
||||||
|
//It is automatically added.
|
||||||
|
'blank' => true,
|
||||||
|
),
|
||||||
|
'project' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||||
|
'model' => 'IDF_Project',
|
||||||
|
'blank' => false,
|
||||||
|
'verbose' => __('project'),
|
||||||
|
),
|
||||||
|
'vkey' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'size' => 50,
|
||||||
|
'verbose' => __('key'),
|
||||||
|
),
|
||||||
|
'vdesc' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'size' => 250,
|
||||||
|
'verbose' => __('value'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$this->_a['idx'] = array('project_vkey_idx' =>
|
||||||
|
array(
|
||||||
|
'col' => 'project, vkey',
|
||||||
|
'type' => 'unique',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$this->f = new IDF_Conf_DataProxy($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProject($project)
|
||||||
|
{
|
||||||
|
$this->datacache = null;
|
||||||
|
$this->_project = $project;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCache()
|
||||||
|
{
|
||||||
|
$this->datacache = new ArrayObject();
|
||||||
|
$sql = new Pluf_SQL('project=%s', $this->_project);
|
||||||
|
foreach ($this->getList(array('filter' => $sql->gen())) as $val) {
|
||||||
|
$this->datacache[$val->vkey] = $val->vdesc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME: This is not efficient when setting a large number of
|
||||||
|
* values in a loop.
|
||||||
|
*/
|
||||||
|
function setVal($key, $value)
|
||||||
|
{
|
||||||
|
if (!is_null($this->getVal($key, null))
|
||||||
|
and $value == $this->getVal($key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->delVal($key, false);
|
||||||
|
$conf = new IDF_Conf();
|
||||||
|
$conf->project = $this->_project;
|
||||||
|
$conf->vkey = $key;
|
||||||
|
$conf->vdesc = $value;
|
||||||
|
$conf->create();
|
||||||
|
$this->initCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVal($key, $default='')
|
||||||
|
{
|
||||||
|
if ($this->datacache === null) {
|
||||||
|
$this->initCache();
|
||||||
|
}
|
||||||
|
return (isset($this->datacache[$key])) ? $this->datacache[$key] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delVal($key, $initcache=true)
|
||||||
|
{
|
||||||
|
$gconf = new IDF_Conf();
|
||||||
|
$sql = new Pluf_SQL('vkey=%s AND project=%s', array($key, $this->_project));
|
||||||
|
foreach ($gconf->getList(array('filter' => $sql->gen())) as $c) {
|
||||||
|
$c->delete();
|
||||||
|
}
|
||||||
|
if ($initcache) {
|
||||||
|
$this->initCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
src/IDF/Conf/DataProxy.php
Normal file
42
src/IDF/Conf/DataProxy.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
/*
|
||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This file is part of InDefero, an open source project management application.
|
||||||
|
# Copyright (C) 2008 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field proxy to access the configuration data through
|
||||||
|
* {$conf.f.fieldname} in a template.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class IDF_Conf_DataProxy
|
||||||
|
{
|
||||||
|
protected $obj = null;
|
||||||
|
|
||||||
|
public function __construct(&$obj)
|
||||||
|
{
|
||||||
|
$this->obj = $obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($field)
|
||||||
|
{
|
||||||
|
return $this->obj->getVal($field);
|
||||||
|
}
|
||||||
|
}
|
261
src/IDF/Form/IssueCreate.php
Normal file
261
src/IDF/Form/IssueCreate.php
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
<?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 issue.
|
||||||
|
*
|
||||||
|
* This create the issue entry and the first comment corresponding to
|
||||||
|
* the description and the attached files.
|
||||||
|
*
|
||||||
|
* It is possible to tag the issue following some rules. For example
|
||||||
|
* you cannot put several "status" or "priority" tags.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IDF_Form_IssueCreate 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['content'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => true,
|
||||||
|
'label' => __('Description'),
|
||||||
|
'initial' => '',
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'cols' => 58,
|
||||||
|
'rows' => 13,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
$this->fields['owner'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Owner'),
|
||||||
|
'initial' => '',
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 20,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
for ($i=1;$i<7;$i++) {
|
||||||
|
$initial = '';
|
||||||
|
switch ($i) {
|
||||||
|
case 1:
|
||||||
|
$initial = 'Type:Defect';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
$initial = 'Priority:Medium';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Labels'),
|
||||||
|
'initial' => $initial,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 50,
|
||||||
|
'size' => 20,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the interconnection in the form.
|
||||||
|
*/
|
||||||
|
public function clean()
|
||||||
|
{
|
||||||
|
// We need to check that no label with the 'Status' class is
|
||||||
|
// given.
|
||||||
|
if (!$this->show_full) {
|
||||||
|
return $this->cleaned_data;
|
||||||
|
}
|
||||||
|
$conf = new IDF_Conf();
|
||||||
|
$conf->setProject($this->project);
|
||||||
|
$onemax = array();
|
||||||
|
foreach (split(',', $conf->getVal('labels_issue_one_max', IDF_Form_IssueTrackingConf::init_one_max)) as $class) {
|
||||||
|
if (trim($class) != '') {
|
||||||
|
$onemax[] = mb_strtolower(trim($class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$count = array();
|
||||||
|
for ($i=1;$i<7;$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 ($class == 'status') {
|
||||||
|
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
|
||||||
|
$this->errors['label'.$i][] = __('You cannot add a label with the "Status" prefix to an issue.');
|
||||||
|
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
|
||||||
|
}
|
||||||
|
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 an issue.'), $class);
|
||||||
|
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()) {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
$issue->setAssoc($this->user); // the user is
|
||||||
|
// automatically
|
||||||
|
// interested.
|
||||||
|
// add the first comment
|
||||||
|
$comment = new IDF_IssueComment();
|
||||||
|
$comment->issue = $issue;
|
||||||
|
$comment->content = $this->cleaned_data['content'];
|
||||||
|
$comment->submitter = $this->user;
|
||||||
|
$comment->create();
|
||||||
|
return $issue;
|
||||||
|
}
|
||||||
|
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on the given string, try to find the matching user.
|
||||||
|
*
|
||||||
|
* Search order is: email, login, last_name.
|
||||||
|
*
|
||||||
|
* If no user found, simply returns null.
|
||||||
|
*
|
||||||
|
* @param string User
|
||||||
|
* @return Pluf_User or null
|
||||||
|
*/
|
||||||
|
public static function findUser($string)
|
||||||
|
{
|
||||||
|
$string = trim($string);
|
||||||
|
if (strlen($string) == 0) return null;
|
||||||
|
$guser = new Pluf_User();
|
||||||
|
foreach (array('email', 'login', 'last_name') as $what) {
|
||||||
|
$sql = new Pluf_SQL($what.'=%s', $string);
|
||||||
|
$users = $guser->getList(array('filter' => $sql->gen()));
|
||||||
|
if ($users->count() > 0) {
|
||||||
|
return $users[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
107
src/IDF/Form/IssueTrackingConf.php
Normal file
107
src/IDF/Form/IssueTrackingConf.php
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?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 ***** */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of the labels etc.
|
||||||
|
*/
|
||||||
|
class IDF_Form_IssueTrackingConf extends Pluf_Form
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Defined as constants to easily access the value in the
|
||||||
|
* IssueUpdate/Create form in the case nothing is in the db yet.
|
||||||
|
*/
|
||||||
|
const init_open = 'New = Issue has not had initial review yet
|
||||||
|
Accepted = Problem reproduced / Need acknowledged
|
||||||
|
Started = Work on this issue has begun';
|
||||||
|
const init_closed = 'Fixed = Developer made requested changes, QA should verify
|
||||||
|
Verified = QA has verified that the fix worked
|
||||||
|
Invalid = This was not a valid issue report
|
||||||
|
Duplicate = This report duplicates an existing issue
|
||||||
|
WontFix = We decided to not take action on this issue';
|
||||||
|
const init_predefined = 'Type:Defect = Report of a software defect
|
||||||
|
Type:Enhancement = Request for enhancement
|
||||||
|
Type:Task = Work item that doesn\'t change the code or docs
|
||||||
|
Type:Patch = Source code patch for review
|
||||||
|
Type:Other = Some other kind of issue
|
||||||
|
Priority:Critical = Must resolve in the specified milestone
|
||||||
|
Priority:High = Strongly want to resolve in the specified milestone
|
||||||
|
Priority:Medium = Normal priority
|
||||||
|
Priority:Low = Might slip to later milestone
|
||||||
|
OpSys:All = Affects all operating systems
|
||||||
|
OpSys:Windows = Affects Windows users
|
||||||
|
OpSys:Linux = Affects Linux users
|
||||||
|
OpSys:OSX = Affects Mac OS X users
|
||||||
|
Milestone:Release1.0 = All essential functionality working
|
||||||
|
Component:UI = Issue relates to program UI
|
||||||
|
Component:Logic = Issue relates to application logic
|
||||||
|
Component:Persistence = Issue relates to data storage components
|
||||||
|
Component:Scripts = Utility and installation scripts
|
||||||
|
Component:Docs = Issue relates to end-user documentation
|
||||||
|
Security = Security risk to users
|
||||||
|
Performance = Performance issue
|
||||||
|
Usability = Affects program usability
|
||||||
|
Maintainability = Hinders future changes';
|
||||||
|
const init_one_max = 'Type, Priority, Milestone';
|
||||||
|
|
||||||
|
public function initFields($extra=array())
|
||||||
|
{
|
||||||
|
$this->fields['labels_issue_open'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => true,
|
||||||
|
'label' => __('Open issue status values'),
|
||||||
|
'initial' => self::init_open,
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
'widget_attrs' => array('rows' => 5,
|
||||||
|
'cols' => 75),
|
||||||
|
));
|
||||||
|
$this->fields['labels_issue_closed'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => true,
|
||||||
|
'label' => __('Closed issue status values'),
|
||||||
|
'initial' => self::init_closed,
|
||||||
|
'widget_attrs' => array('rows' => 7,
|
||||||
|
'cols' => 75),
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['labels_issue_predefined'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => true,
|
||||||
|
'label' => __('Predefined issue labels'),
|
||||||
|
'initial' => self::init_predefined,
|
||||||
|
'widget_attrs' => array('rows' => 7,
|
||||||
|
'cols' => 75),
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->fields['labels_issue_one_max'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Each issue may have at most one label with each of these classes'),
|
||||||
|
'initial' => self::init_one_max,
|
||||||
|
'widget_attrs' => array('size' => 60),
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
260
src/IDF/Form/IssueUpdate.php
Normal file
260
src/IDF/Form/IssueUpdate.php
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
<?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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an issue.
|
||||||
|
*
|
||||||
|
* We extend IDF_Form_IssueCreate to reuse the validation logic.
|
||||||
|
*/
|
||||||
|
class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate
|
||||||
|
{
|
||||||
|
public $issue = null;
|
||||||
|
|
||||||
|
public function initFields($extra=array())
|
||||||
|
{
|
||||||
|
$this->user = $extra['user'];
|
||||||
|
$this->project = $extra['project'];
|
||||||
|
$this->issue = $extra['issue'];
|
||||||
|
if ($this->user->hasPerm('IDF.project-owner', $this->project)
|
||||||
|
or $this->user->hasPerm('IDF.project-member', $this->project)) {
|
||||||
|
$this->show_full = true;
|
||||||
|
}
|
||||||
|
if ($this->show_full) {
|
||||||
|
$this->fields['summary'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => true,
|
||||||
|
'label' => __('Summary'),
|
||||||
|
'initial' => $this->issue->summary,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 200,
|
||||||
|
'size' => 67,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
$this->fields['content'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Comment'),
|
||||||
|
'initial' => '',
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'cols' => 58,
|
||||||
|
'rows' => 9,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
if ($this->show_full) {
|
||||||
|
$this->fields['status'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => true,
|
||||||
|
'label' => __('Status'),
|
||||||
|
'initial' => $this->issue->get_status()->name,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 20,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
$initial = ($this->issue->get_owner() == null) ? '' : $this->issue->get_owner()->login;
|
||||||
|
$this->fields['owner'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Owner'),
|
||||||
|
'initial' => $initial,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 20,
|
||||||
|
'size' => 15,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
$tags = $this->issue->get_tags_list();
|
||||||
|
for ($i=1;$i<7;$i++) {
|
||||||
|
$initial = '';
|
||||||
|
if (isset($tags[$i-1])) {
|
||||||
|
if ($tags[$i-1]->class != 'Other') {
|
||||||
|
$initial = (string) $tags[$i-1];
|
||||||
|
} else {
|
||||||
|
$initial = $tags[$i-1]->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Labels'),
|
||||||
|
'initial' => $initial,
|
||||||
|
'widget_attrs' => array(
|
||||||
|
'maxlength' => 50,
|
||||||
|
'size' => 20,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We check that something is really changed.
|
||||||
|
*/
|
||||||
|
public function clean()
|
||||||
|
{
|
||||||
|
$this->cleaned_data = parent::clean();
|
||||||
|
// As soon as we know that at least one change was done, we
|
||||||
|
// return the cleaned data and do not go further.
|
||||||
|
if (strlen(trim($this->cleaned_data['content']))) {
|
||||||
|
return $this->cleaned_data;
|
||||||
|
}
|
||||||
|
if ($this->show_full) {
|
||||||
|
$status = $this->issue->get_status();
|
||||||
|
if (trim($this->cleaned_data['status']) != $status->name) {
|
||||||
|
return $this->cleaned_data;
|
||||||
|
}
|
||||||
|
if (trim($this->issue->summary) != trim($this->cleaned_data['summary'])) {
|
||||||
|
return $this->cleaned_data;
|
||||||
|
}
|
||||||
|
$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)) {
|
||||||
|
return $this->cleaned_data;
|
||||||
|
}
|
||||||
|
$tags = 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]);
|
||||||
|
}
|
||||||
|
$tags[] = array($class, $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$oldtags = $this->issue->get_tags_list();
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$found = false;
|
||||||
|
foreach ($oldtags as $otag) {
|
||||||
|
if ($otag->class == $tag[0] and $otag->name == $tag[1]) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$found) {
|
||||||
|
// new tag not found in the old tags
|
||||||
|
return $this->cleaned_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($oldtags as $otag) {
|
||||||
|
$found = false;
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
if ($otag->class == $tag[0] and $otag->name == $tag[1]) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$found) {
|
||||||
|
// old tag not found in the new tags
|
||||||
|
return $this->cleaned_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no changes!
|
||||||
|
throw new Pluf_Form_Invalid(__('No changes were entered.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
return $this->issue;
|
||||||
|
}
|
||||||
|
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||||
|
}
|
||||||
|
}
|
86
src/IDF/Form/MembersConf.php
Normal file
86
src/IDF/Form/MembersConf.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?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 ***** */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration of the members.
|
||||||
|
*
|
||||||
|
* To simplify the management. Instead of being obliged to go through
|
||||||
|
* a list of people and then select the rights member/owner, I am
|
||||||
|
* using the same approach as googlecode, that is, asking for the
|
||||||
|
* login. This makes the interface simpler and simplicity is king.
|
||||||
|
*
|
||||||
|
* In background, the row permission framework is used to give the
|
||||||
|
* member/owner permission to the given project to the users.
|
||||||
|
*/
|
||||||
|
class IDF_Form_MembersConf extends Pluf_Form
|
||||||
|
{
|
||||||
|
public $project = null;
|
||||||
|
|
||||||
|
public function initFields($extra=array())
|
||||||
|
{
|
||||||
|
$this->project = $extra['project'];
|
||||||
|
|
||||||
|
$this->fields['owners'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Project owners'),
|
||||||
|
'initial' => '',
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
'widget_attrs' => array('rows' => 5,
|
||||||
|
'cols' => 40),
|
||||||
|
));
|
||||||
|
$this->fields['members'] = new Pluf_Form_Field_Varchar(
|
||||||
|
array('required' => false,
|
||||||
|
'label' => __('Project members'),
|
||||||
|
'widget_attrs' => array('rows' => 7,
|
||||||
|
'cols' => 40),
|
||||||
|
'widget' => 'Pluf_Form_Widget_TextareaInput',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save($commit=true)
|
||||||
|
{
|
||||||
|
if (!$this->isValid()) {
|
||||||
|
throw new Exception(__('Cannot save the model from an invalid form.'));
|
||||||
|
}
|
||||||
|
// remove all the permissions
|
||||||
|
$cm = $this->project->getMembershipData();
|
||||||
|
$def = array('owners' => Pluf_Permission::getFromString('IDF.project-owner'),
|
||||||
|
'members' => Pluf_Permission::getFromString('IDF.project-member'));
|
||||||
|
$guser = new Pluf_User();
|
||||||
|
foreach ($def as $key=>$perm) {
|
||||||
|
foreach ($cm[$key] as $user) {
|
||||||
|
Pluf_RowPermission::remove($user, $this->project, $perm);
|
||||||
|
}
|
||||||
|
foreach (preg_split("/\015\012|\015|\012|\,/", $this->cleaned_data[$key], -1, PREG_SPLIT_NO_EMPTY) as $login) {
|
||||||
|
$sql = new Pluf_SQL('login=%s', array(trim($login)));
|
||||||
|
$users = $guser->getList(array('filter'=>$sql->gen()));
|
||||||
|
if ($users->count() == 1) {
|
||||||
|
Pluf_RowPermission::add($users[0], $this->project, $perm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
158
src/IDF/Issue.php
Normal file
158
src/IDF/Issue.php
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
<?php
|
||||||
|
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
/*
|
||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This file is part of InDefero, an open source project management application.
|
||||||
|
# Copyright (C) 2008 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base definition of an issue.
|
||||||
|
*
|
||||||
|
* An issue can have labels, comments, can be starred by people.
|
||||||
|
*/
|
||||||
|
class IDF_Issue extends Pluf_Model
|
||||||
|
{
|
||||||
|
public $_model = __CLASS__;
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
$this->_a['table'] = 'idf_issues';
|
||||||
|
$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' => 'issues',
|
||||||
|
),
|
||||||
|
'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_issue',
|
||||||
|
),
|
||||||
|
'owner' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||||
|
'model' => 'Pluf_User',
|
||||||
|
'blank' => true, // no owner when submitted.
|
||||||
|
'is_null' => true,
|
||||||
|
'verbose' => __('owner'),
|
||||||
|
'relate_name' => 'owned_issue',
|
||||||
|
),
|
||||||
|
'interested' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Manytomany',
|
||||||
|
'model' => 'Pluf_User',
|
||||||
|
'blank' => true,
|
||||||
|
'verbose' => __('interested users'),
|
||||||
|
'help_text' => __('Interested users will get an email notification when the issue 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_issue_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 preSave()
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
// This will be used to fire the indexing or send a
|
||||||
|
// notification email to the interested people, etc.
|
||||||
|
$q = new Pluf_Queue();
|
||||||
|
$q->model_class = __CLASS__;
|
||||||
|
$q->model_id = $this->id;
|
||||||
|
$q->action = 'updated';
|
||||||
|
$q->lock = 0;
|
||||||
|
$q->create();
|
||||||
|
}
|
||||||
|
}
|
119
src/IDF/IssueComment.php
Normal file
119
src/IDF/IssueComment.php
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<?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 an issue.
|
||||||
|
*
|
||||||
|
* The first description of an issue is also stored as a comment.
|
||||||
|
*
|
||||||
|
* A comment is also tracking the changes in the main issue.
|
||||||
|
*/
|
||||||
|
class IDF_IssueComment extends Pluf_Model
|
||||||
|
{
|
||||||
|
public $_model = __CLASS__;
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
$this->_a['table'] = 'idf_issuecomments';
|
||||||
|
$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,
|
||||||
|
),
|
||||||
|
'issue' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||||
|
'model' => 'IDF_Issue',
|
||||||
|
'blank' => false,
|
||||||
|
'verbose' => __('issue'),
|
||||||
|
'relate_name' => 'comments',
|
||||||
|
),
|
||||||
|
'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_issue',
|
||||||
|
),
|
||||||
|
'changes' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Serialized',
|
||||||
|
'blank' => true,
|
||||||
|
'verbose' => __('changes'),
|
||||||
|
'help_text' => __('Serialized array of the changes in the issue.'),
|
||||||
|
),
|
||||||
|
'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 changedIssue()
|
||||||
|
{
|
||||||
|
return count($this->changes) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _toIndex()
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
function preSave()
|
||||||
|
{
|
||||||
|
if ($this->id == '') {
|
||||||
|
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function postSave()
|
||||||
|
{
|
||||||
|
// This will be used to fire the indexing or send a
|
||||||
|
// notification email to the interested people, etc.
|
||||||
|
$q = new Pluf_Queue();
|
||||||
|
$q->model_class = __CLASS__;
|
||||||
|
$q->model_id = $this->id;
|
||||||
|
$q->action = 'updated';
|
||||||
|
$q->lock = 0;
|
||||||
|
$q->create();
|
||||||
|
}
|
||||||
|
}
|
66
src/IDF/Middleware.php
Normal file
66
src/IDF/Middleware.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project middleware.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class IDF_Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Process the request.
|
||||||
|
*
|
||||||
|
* When processing the request, check if matching a project. If
|
||||||
|
* so, directly set $request->project to the project.
|
||||||
|
*
|
||||||
|
* The url to match a project is in the format
|
||||||
|
* /p/(\w+)/whatever. This means that it will not try to match on
|
||||||
|
* /login/ or /logout/.
|
||||||
|
*
|
||||||
|
* @param Pluf_HTTP_Request The request
|
||||||
|
* @return bool false or redirect.
|
||||||
|
*/
|
||||||
|
function process_request(&$request)
|
||||||
|
{
|
||||||
|
$match = array();
|
||||||
|
if (preg_match('#^/p/(\w+)/#', $request->query, $match)) {
|
||||||
|
$request->project = IDF_Project::getOr404($match[1]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function IDF_Middleware_ContextPreProcessor($request)
|
||||||
|
{
|
||||||
|
$c = array();
|
||||||
|
if (isset($request->project)) {
|
||||||
|
$c['project'] = $request->project;
|
||||||
|
$c['isOwner'] = $request->user->hasPerm('IDF.project-owner',
|
||||||
|
$request->project);
|
||||||
|
$c['isMember'] = $request->user->hasPerm('IDF.project-member',
|
||||||
|
$request->project);
|
||||||
|
}
|
||||||
|
return $c;
|
||||||
|
}
|
||||||
|
|
79
src/IDF/Migrations/Install.php
Normal file
79
src/IDF/Migrations/Install.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup of a clean InDefero.
|
||||||
|
*
|
||||||
|
* It creates all the tables for the application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function IDF_Migrations_Install_setup($params=null)
|
||||||
|
{
|
||||||
|
$models = array(
|
||||||
|
'IDF_Project',
|
||||||
|
'IDF_Tag',
|
||||||
|
'IDF_Issue',
|
||||||
|
'IDF_IssueComment',
|
||||||
|
'IDF_Conf',
|
||||||
|
);
|
||||||
|
$db = Pluf::db();
|
||||||
|
$schema = new Pluf_DB_Schema($db);
|
||||||
|
foreach ($models as $model) {
|
||||||
|
$schema->model = new $model();
|
||||||
|
$schema->createTables();
|
||||||
|
}
|
||||||
|
// Install the permissions
|
||||||
|
$perm = new Pluf_Permission();
|
||||||
|
$perm->name = 'Project membership';
|
||||||
|
$perm->code_name = 'project-member';
|
||||||
|
$perm->description = 'Permission given to project members.';
|
||||||
|
$perm->application = 'IDF';
|
||||||
|
$perm->create();
|
||||||
|
$perm = new Pluf_Permission();
|
||||||
|
$perm->name = 'Project ownership';
|
||||||
|
$perm->code_name = 'project-owner';
|
||||||
|
$perm->description = 'Permission given to project owners.';
|
||||||
|
$perm->application = 'IDF';
|
||||||
|
$perm->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
function IDF_Migrations_Install_teardown($params=null)
|
||||||
|
{
|
||||||
|
$perm = Pluf_Permission::getFromString('IDF.project-member');
|
||||||
|
if ($perm) $perm->delete();
|
||||||
|
$perm = Pluf_Permission::getFromString('IDF.project-owner');
|
||||||
|
if ($perm) $perm->delete();
|
||||||
|
$models = array(
|
||||||
|
'IDF_Conf',
|
||||||
|
'IDF_IssueComment',
|
||||||
|
'IDF_Issue',
|
||||||
|
'IDF_Tag',
|
||||||
|
'IDF_Project',
|
||||||
|
);
|
||||||
|
$db = Pluf::db();
|
||||||
|
$schema = new Pluf_DB_Schema($db);
|
||||||
|
foreach ($models as $model) {
|
||||||
|
$schema->model = new $model();
|
||||||
|
$schema->dropTables();
|
||||||
|
}
|
||||||
|
}
|
43
src/IDF/Precondition.php
Normal file
43
src/IDF/Precondition.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?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 ***** */
|
||||||
|
|
||||||
|
class IDF_Precondition
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Check if the user is project owner.
|
||||||
|
*
|
||||||
|
* @param Pluf_HTTP_Request
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
static public function projectOwner($request)
|
||||||
|
{
|
||||||
|
$res = Pluf_Precondition::loginRequired($request);
|
||||||
|
if (true !== $res) {
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
if ($request->user->hasPerm('IDF.project-owner', $request->project)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return new Pluf_HTTP_Response_Forbidden($request);
|
||||||
|
}
|
||||||
|
}
|
280
src/IDF/Project.php
Normal file
280
src/IDF/Project.php
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
<?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 Loic d'Anterroches and contributors.
|
||||||
|
#
|
||||||
|
# Plume CMS 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.
|
||||||
|
#
|
||||||
|
# Plume CMS 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base definition of a project.
|
||||||
|
*
|
||||||
|
* The issue management system can be used to manage several projects
|
||||||
|
* at the same time.
|
||||||
|
*/
|
||||||
|
class IDF_Project extends Pluf_Model
|
||||||
|
{
|
||||||
|
public $_model = __CLASS__;
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
$this->_a['table'] = 'idf_projects';
|
||||||
|
$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,
|
||||||
|
),
|
||||||
|
'name' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'size' => 250,
|
||||||
|
'verbose' => __('name'),
|
||||||
|
),
|
||||||
|
'shortname' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'size' => 50,
|
||||||
|
'verbose' => __('short name'),
|
||||||
|
'help_text' => __('Used in the url to access the project, must be short with only letters and numbers.'),
|
||||||
|
'unique' => true,
|
||||||
|
),
|
||||||
|
'description' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Text',
|
||||||
|
'blank' => false,
|
||||||
|
'size' => 250,
|
||||||
|
'verbose' => __('description'),
|
||||||
|
'help_text' => __('The description can be extended using the markdown syntax.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$this->_a['idx'] = array( );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String representation of the abstract.
|
||||||
|
*/
|
||||||
|
function __toString()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String ready for indexation.
|
||||||
|
*/
|
||||||
|
function _toIndex()
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function preSave()
|
||||||
|
{
|
||||||
|
if ($this->id == '') {
|
||||||
|
$this->creation_dtime = gmdate('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
$this->modif_dtime = gmdate('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getOr404($shortname)
|
||||||
|
{
|
||||||
|
$sql = new Pluf_SQL('shortname=%s', array(trim($shortname)));
|
||||||
|
$projects = Pluf::factory(__CLASS__)->getList(array('filter' => $sql->gen()));
|
||||||
|
if ($projects->count() != 1) {
|
||||||
|
throw new Pluf_HTTP_Error404(sprintf(__('Project "%s" not found.'),
|
||||||
|
$shortname));
|
||||||
|
}
|
||||||
|
return $projects[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of open/closed issues.
|
||||||
|
*
|
||||||
|
* @param string Status ('open'), 'closed'
|
||||||
|
* @param IDF_Tag Subfilter with a label (null)
|
||||||
|
* @return int Count
|
||||||
|
*/
|
||||||
|
public function getIssueCountByStatus($status='open', $label=null)
|
||||||
|
{
|
||||||
|
switch ($status) {
|
||||||
|
case 'open':
|
||||||
|
$key = 'labels_issue_open';
|
||||||
|
$default = IDF_Form_IssueTrackingConf::init_open;
|
||||||
|
break;
|
||||||
|
case 'closed':
|
||||||
|
default:
|
||||||
|
$key = 'labels_issue_closed';
|
||||||
|
$default = IDF_Form_IssueTrackingConf::init_closed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$tags = array();
|
||||||
|
foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) {
|
||||||
|
$tags[] = (int)$tag->id;
|
||||||
|
}
|
||||||
|
if (count($tags) == 0) return array();
|
||||||
|
$sql = new Pluf_SQL(sprintf('project=%%s AND status IN (%s)', implode(', ', $tags)), array($this->id));
|
||||||
|
if (!is_null($label)) {
|
||||||
|
$sql2 = new Pluf_SQL('idf_tag_id=%s', array($label->id));
|
||||||
|
$sql->SAnd($sql2);
|
||||||
|
}
|
||||||
|
$params = array('filter' => $sql->gen());
|
||||||
|
if (!is_null($label)) { $params['view'] = 'join_tags'; }
|
||||||
|
$gissue = new IDF_Issue();
|
||||||
|
return $gissue->getCount($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the open/closed tag ids as they are often used when doing
|
||||||
|
* listings.
|
||||||
|
*
|
||||||
|
* @param string Status ('open') or 'closed'
|
||||||
|
* @return array Ids of the open/closed tags
|
||||||
|
*/
|
||||||
|
public function getTagIdsByStatus($status='open')
|
||||||
|
{
|
||||||
|
switch ($status) {
|
||||||
|
case 'open':
|
||||||
|
$key = 'labels_issue_open';
|
||||||
|
$default = IDF_Form_IssueTrackingConf::init_open;
|
||||||
|
break;
|
||||||
|
case 'closed':
|
||||||
|
default:
|
||||||
|
$key = 'labels_issue_closed';
|
||||||
|
$default = IDF_Form_IssueTrackingConf::init_closed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$tags = array();
|
||||||
|
foreach ($this->getTagsFromConfig($key, $default, 'Status') as $tag) {
|
||||||
|
$tags[] = (int) $tag->id;
|
||||||
|
}
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the definition of tags in the configuration into the
|
||||||
|
* corresponding list of tags.
|
||||||
|
*
|
||||||
|
* @param string Configuration key where the tag is.
|
||||||
|
* @param string Default config if nothing in the db.
|
||||||
|
* @param string Default class.
|
||||||
|
* @return array List of tags
|
||||||
|
*/
|
||||||
|
public function getTagsFromConfig($cfg_key, $default, $dclass='Other')
|
||||||
|
{
|
||||||
|
$conf = new IDF_Conf();
|
||||||
|
$conf->setProject($this);
|
||||||
|
$tags = array();
|
||||||
|
foreach (preg_split("/\015\012|\015|\012/", $conf->getVal($cfg_key, $default), -1, PREG_SPLIT_NO_EMPTY) as $s) {
|
||||||
|
$_s = split('=', $s, 2);
|
||||||
|
$v = trim($_s[0]);
|
||||||
|
$_v = split(':', $v, 2);
|
||||||
|
if (count($_v) > 1) {
|
||||||
|
$class = trim($_v[0]);
|
||||||
|
$name = trim($_v[1]);
|
||||||
|
} else {
|
||||||
|
$name = trim($_s[0]);
|
||||||
|
$class = $dclass;
|
||||||
|
}
|
||||||
|
$tags[] = IDF_Tag::add($name, $this, $class);
|
||||||
|
}
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return membership data.
|
||||||
|
*
|
||||||
|
* The array has 2 keys: 'members' and 'owners'.
|
||||||
|
*
|
||||||
|
* The list of users is only taken using the row level permission
|
||||||
|
* table. That is, if you set a user as administrator, he will
|
||||||
|
* have the member and owner rights but will not appear in the
|
||||||
|
* lists.
|
||||||
|
*
|
||||||
|
* @param string Format ('objects'), 'string'.
|
||||||
|
* @return mixed Array of Pluf_User or newline separated list of logins.
|
||||||
|
*/
|
||||||
|
public function getMembershipData($fmt='objects')
|
||||||
|
{
|
||||||
|
$mperm = Pluf_Permission::getFromString('IDF.project-member');
|
||||||
|
$operm = Pluf_Permission::getFromString('IDF.project-owner');
|
||||||
|
$grow = new Pluf_RowPermission();
|
||||||
|
$db =& Pluf::db();
|
||||||
|
$false = Pluf_DB_BooleanToDb(false, $db);
|
||||||
|
$sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative='.$false,
|
||||||
|
array('IDF_Project', $this->id, 'Pluf_User', $operm->id));
|
||||||
|
$owners = array();
|
||||||
|
foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
|
||||||
|
if ($fmt == 'objects') {
|
||||||
|
$owners[] = Pluf::factory('Pluf_User', $row->owner_id);
|
||||||
|
} else {
|
||||||
|
$owners[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$sql = new Pluf_SQL('model_class=%s AND model_id=%s AND owner_class=%s AND permission=%s AND negative=0',
|
||||||
|
array('IDF_Project', $this->id, 'Pluf_User', $mperm->id));
|
||||||
|
$members = array();
|
||||||
|
foreach ($grow->getList(array('filter' => $sql->gen())) as $row) {
|
||||||
|
if ($fmt == 'objects') {
|
||||||
|
$members[] = Pluf::factory('Pluf_User', $row->owner_id);
|
||||||
|
} else {
|
||||||
|
$members[] = Pluf::factory('Pluf_User', $row->owner_id)->login;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($fmt == 'objects') {
|
||||||
|
return array('members' => $members, 'owners' => $owners);
|
||||||
|
} else {
|
||||||
|
return array('members' => implode("\n", $members),
|
||||||
|
'owners' => implode("\n", $owners));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the tag clouds.
|
||||||
|
*
|
||||||
|
* Return an array of tags sorted class, then name. Each tag get
|
||||||
|
* the extra property 'nb_use' for the number of use in the
|
||||||
|
* project. Only open issues are used to generate the cloud.
|
||||||
|
*
|
||||||
|
* @return ArrayObject of IDF_Tag
|
||||||
|
*/
|
||||||
|
public function getTagCloud()
|
||||||
|
{
|
||||||
|
$tag_t = Pluf::factory('IDF_Tag')->getSqlTable();
|
||||||
|
$issue_t = Pluf::factory('IDF_Issue')->getSqlTable();
|
||||||
|
$asso_t = $this->_con->pfx.'idf_issue_idf_tag_assoc';
|
||||||
|
$ostatus = $this->getTagIdsByStatus('open');
|
||||||
|
if (count($ostatus) == 0) $ostatus[] = 0;
|
||||||
|
$sql = sprintf('SELECT '.$tag_t.'.id AS id, COUNT(*) AS nb_use FROM '.$tag_t.' '."\n".
|
||||||
|
'LEFT JOIN '.$asso_t.' ON idf_tag_id='.$tag_t.'.id '."\n".
|
||||||
|
'LEFT JOIN '.$issue_t.' ON idf_issue_id='.$issue_t.'.id '."\n".
|
||||||
|
'WHERE idf_tag_id NOT NULL AND '.$issue_t.'.status IN (%s) AND '.$issue_t.'.project='.$this->id.' GROUP BY '.$tag_t.'.id ORDER BY '.$tag_t.'.class ASC, '.$tag_t.'.name ASC',
|
||||||
|
implode(', ', $ostatus));
|
||||||
|
$tags = array();
|
||||||
|
foreach ($this->_con->select($sql) as $idc) {
|
||||||
|
$tag = new IDF_Tag($idc['id']);
|
||||||
|
$tag->nb_use = $idc['nb_use'];
|
||||||
|
$tags[] = $tag;
|
||||||
|
}
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
}
|
133
src/IDF/Tag.php
Normal file
133
src/IDF/Tag.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?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 Loic d'Anterroches and contributors.
|
||||||
|
#
|
||||||
|
# Plume CMS 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.
|
||||||
|
#
|
||||||
|
# Plume CMS 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 ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a tag definition.
|
||||||
|
*
|
||||||
|
* Tags are associated to a project.
|
||||||
|
*/
|
||||||
|
define('IDF_TAG_DEFAULT_CLASS', 'other');
|
||||||
|
|
||||||
|
class IDF_Tag extends Pluf_Model
|
||||||
|
{
|
||||||
|
public $_model = __CLASS__;
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
$this->_a['table'] = 'idf_tags';
|
||||||
|
$this->_a['model'] = __CLASS__;
|
||||||
|
$this->_a['cols'] = array(
|
||||||
|
// It is mandatory to have an "id" column.
|
||||||
|
'id' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Sequence',
|
||||||
|
//It is automatically added.
|
||||||
|
'blank' => true,
|
||||||
|
),
|
||||||
|
'project' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Foreignkey',
|
||||||
|
'model' => 'IDF_Project',
|
||||||
|
'blank' => false,
|
||||||
|
'verbose' => __('project'),
|
||||||
|
),
|
||||||
|
'class' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'default' => IDF_TAG_DEFAULT_CLASS,
|
||||||
|
'verbose' => __('tag class'),
|
||||||
|
'help_text' => __('The class of the tag.'),
|
||||||
|
),
|
||||||
|
'name' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'verbose' => __('name'),
|
||||||
|
),
|
||||||
|
'lcname' =>
|
||||||
|
array(
|
||||||
|
'type' => 'Pluf_DB_Field_Varchar',
|
||||||
|
'blank' => false,
|
||||||
|
'editable' => false,
|
||||||
|
'verbose' => __('lcname'),
|
||||||
|
'help_text' => __('Lower case version of the name for fast searching.'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->_a['idx'] = array(
|
||||||
|
'lcname_idx' =>
|
||||||
|
array(
|
||||||
|
'col' => 'lcname',
|
||||||
|
'type' => 'normal',
|
||||||
|
),
|
||||||
|
'class_idx' =>
|
||||||
|
array(
|
||||||
|
'col' => 'class',
|
||||||
|
'type' => 'normal',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function preSave()
|
||||||
|
{
|
||||||
|
$this->lcname = mb_strtolower($this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a tag if not already existing.
|
||||||
|
*
|
||||||
|
* @param string Name of the tag.
|
||||||
|
* @param IDF_Project Project of the tag.
|
||||||
|
* @param string Class of the tag (IDF_TAG_DEFAULT_CLASS)
|
||||||
|
* @return IDF_Tag The tag.
|
||||||
|
*/
|
||||||
|
public static function add($name, $project, $class=IDF_TAG_DEFAULT_CLASS)
|
||||||
|
{
|
||||||
|
$class = trim($class);
|
||||||
|
$name = trim($name);
|
||||||
|
$gtag = new IDF_Tag();
|
||||||
|
$sql = new Pluf_SQL('class=%s AND lcname=%s AND project=%s',
|
||||||
|
array($class, mb_strtolower($name), $project->id));
|
||||||
|
$tags = $gtag->getList(array('filter' => $sql->gen()));
|
||||||
|
if ($tags->count() < 1) {
|
||||||
|
// create a new tag
|
||||||
|
$tag = new IDF_Tag();
|
||||||
|
$tag->name = $name;
|
||||||
|
$tag->class = $class;
|
||||||
|
$tag->project = $project;
|
||||||
|
$tag->create();
|
||||||
|
return $tag;
|
||||||
|
}
|
||||||
|
return $tags[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function __toString()
|
||||||
|
{
|
||||||
|
if ($this->class != IDF_TAG_DEFAULT_CLASS) {
|
||||||
|
return $this->class.':'.$this->name;
|
||||||
|
}
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
138
src/IDF/Tests/TestIssue.php
Normal file
138
src/IDF/Tests/TestIssue.php
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<?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');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the creation/modification of a project.
|
||||||
|
*/
|
||||||
|
class IDF_Tests_TestIssue extends UnitTestCase
|
||||||
|
{
|
||||||
|
public $projects = array();
|
||||||
|
public $users = array();
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct('Test creation/modification of issues.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create 2 projects to work with and 2 users.
|
||||||
|
*/
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->projects = array();
|
||||||
|
$this->users = array();
|
||||||
|
for ($i=1;$i<3;$i++) {
|
||||||
|
$project = new IDF_Project();
|
||||||
|
$project->name = 'Test project '.$i;
|
||||||
|
$project->shortname = 'test'.$i;
|
||||||
|
$project->description = sprintf('This is a test project %d.', $i);
|
||||||
|
$project->create();
|
||||||
|
$this->projects[] = $project;
|
||||||
|
$user = new Pluf_User();
|
||||||
|
$user->last_name = 'user'.$i;
|
||||||
|
$user->login = 'user'.$i;
|
||||||
|
$user->email = 'user'.$i.'@example.com';
|
||||||
|
$user->create();
|
||||||
|
$this->users[] = $user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
// This will drop cascading issues, comments and tags.
|
||||||
|
foreach ($this->projects as $proj) {
|
||||||
|
$proj->delete();
|
||||||
|
}
|
||||||
|
foreach ($this->users as $u) {
|
||||||
|
$u->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreate()
|
||||||
|
{
|
||||||
|
$issue = new IDF_Issue();
|
||||||
|
$issue->project = $this->projects[0];
|
||||||
|
$issue->summary = 'This is a test issue';
|
||||||
|
$issue->submitter = $this->users[0];
|
||||||
|
$issue->create();
|
||||||
|
$this->assertEqual(1, $issue->id);
|
||||||
|
$this->assertIdentical(null, $issue->get_owner());
|
||||||
|
$this->assertNotIdentical(null, $issue->get_submitter());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateMultiple()
|
||||||
|
{
|
||||||
|
for ($i=1;$i<11;$i++) {
|
||||||
|
$issue = new IDF_Issue();
|
||||||
|
$issue->project = $this->projects[0];
|
||||||
|
$issue->summary = 'This is a test issue '.$i;
|
||||||
|
$issue->submitter = $this->users[0];
|
||||||
|
$issue->owner = $this->users[1];
|
||||||
|
$issue->create();
|
||||||
|
}
|
||||||
|
for ($i=11;$i<16;$i++) {
|
||||||
|
$issue = new IDF_Issue();
|
||||||
|
$issue->project = $this->projects[1];
|
||||||
|
$issue->summary = 'This is a test issue '.$i;
|
||||||
|
$issue->submitter = $this->users[1];
|
||||||
|
$issue->create();
|
||||||
|
}
|
||||||
|
$this->assertEqual(10,
|
||||||
|
$this->projects[0]->get_issues_list()->count());
|
||||||
|
$this->assertEqual(5,
|
||||||
|
$this->projects[1]->get_issues_list()->count());
|
||||||
|
$this->assertEqual(5,
|
||||||
|
$this->users[1]->get_submitted_issue_list()->count());
|
||||||
|
$this->assertEqual(10,
|
||||||
|
$this->users[0]->get_submitted_issue_list()->count());
|
||||||
|
$this->assertEqual(10,
|
||||||
|
$this->users[1]->get_owned_issue_list()->count());
|
||||||
|
$this->assertEqual(0,
|
||||||
|
$this->users[1]->get_owned_issue_list(array('filter' => 'project='.(int)$this->projects[1]->id))->count());
|
||||||
|
$this->assertEqual(10,
|
||||||
|
$this->users[1]->get_owned_issue_list(array('filter' => 'project='.(int)$this->projects[0]->id))->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddIssueComment()
|
||||||
|
{
|
||||||
|
$issue = new IDF_Issue();
|
||||||
|
$issue->project = $this->projects[0];
|
||||||
|
$issue->summary = 'This is a test issue';
|
||||||
|
$issue->submitter = $this->users[0];
|
||||||
|
$issue->create();
|
||||||
|
$ic = new IDF_IssueComment();
|
||||||
|
$ic->issue = $issue;
|
||||||
|
$ic->submitter = $this->users[0];
|
||||||
|
$ic->content = 'toto';
|
||||||
|
$changes = array('s' => 'New summary',
|
||||||
|
'st' => 'Active',
|
||||||
|
't' => '-OS:Linux OS:Windows');
|
||||||
|
$ic->changes = $changes;
|
||||||
|
$ic->create();
|
||||||
|
$comments = $issue->get_comments_list();
|
||||||
|
$this->assertEqual($changes, $comments[0]->changes);
|
||||||
|
}
|
||||||
|
}
|
77
src/IDF/Tests/TestProject.php
Normal file
77
src/IDF/Tests/TestProject.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?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');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the creation/modification of a project.
|
||||||
|
*/
|
||||||
|
class IDF_Tests_TestProject extends UnitTestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct('Test creation/modification of a project.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
foreach (Pluf::factory('IDF_Project')->getList() as $proj) {
|
||||||
|
$proj->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreate()
|
||||||
|
{
|
||||||
|
$gproj = Pluf::factory('IDF_Project')->getList();
|
||||||
|
$this->assertEqual(0, $gproj->count());
|
||||||
|
$project = new IDF_Project();
|
||||||
|
$project->name = 'Test project';
|
||||||
|
$project->shortname = 'test';
|
||||||
|
$project->description = 'This is a test project.';
|
||||||
|
$project->create();
|
||||||
|
$id = $project->id;
|
||||||
|
$p2 = new IDF_Project($id);
|
||||||
|
$this->assertEqual($p2->shortname, $project->shortname);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleCreate()
|
||||||
|
{
|
||||||
|
$project = new IDF_Project();
|
||||||
|
$project->name = 'Test project';
|
||||||
|
$project->shortname = 'test';
|
||||||
|
$project->description = 'This is a test project.';
|
||||||
|
$project->create();
|
||||||
|
try {
|
||||||
|
$project = new IDF_Project();
|
||||||
|
$project->name = 'Test project';
|
||||||
|
$project->shortname = 'test';
|
||||||
|
$project->description = 'This is a test project.';
|
||||||
|
$project->create();
|
||||||
|
// if here it as failed
|
||||||
|
$this->fail('It should not be possible to create 2 projects with same shortname');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->pass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
src/IDF/Views.php
Normal file
64
src/IDF/Views.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
/*
|
||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This file is part of InDefero, an open source project management application.
|
||||||
|
# Copyright (C) 2008 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');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base views of InDefero.
|
||||||
|
*/
|
||||||
|
class IDF_Views
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* List all the projects managed by InDefero.
|
||||||
|
*/
|
||||||
|
public function index($request, $match)
|
||||||
|
{
|
||||||
|
$projects = Pluf::factory('IDF_Project')->getList();
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('index.html',
|
||||||
|
array('page_title' => __('Projects'),
|
||||||
|
'projects' => $projects),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login view.
|
||||||
|
*/
|
||||||
|
public function login($request, $match)
|
||||||
|
{
|
||||||
|
$v = new Pluf_Views();
|
||||||
|
return $v->login($request, $match, Pluf::f('login_success_url'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout view.
|
||||||
|
*/
|
||||||
|
function logout($request, $match)
|
||||||
|
{
|
||||||
|
$views = new Pluf_Views();
|
||||||
|
return $views->logout($request, $match, Pluf::f('after_logout_page'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
371
src/IDF/Views/Issue.php
Normal file
371
src/IDF/Views/Issue.php
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
<?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');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issues' views.
|
||||||
|
*/
|
||||||
|
class IDF_Views_Issue
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* View list of issues for a given project.
|
||||||
|
*/
|
||||||
|
public function index($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$title = sprintf(__('%s Recent Issues'), (string) $prj);
|
||||||
|
// Get stats about the issues
|
||||||
|
$open = $prj->getIssueCountByStatus('open');
|
||||||
|
$closed = $prj->getIssueCountByStatus('closed');
|
||||||
|
// Paginator to paginate the issues
|
||||||
|
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||||
|
$pag->class = 'recent-issues';
|
||||||
|
$pag->item_extra_props = array('project_m' => $prj,
|
||||||
|
'shortname' => $prj->shortname);
|
||||||
|
$pag->summary = __('This table shows the open recent issues.');
|
||||||
|
$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', 'DESC');
|
||||||
|
$list_display = array(
|
||||||
|
'id' => __('Id'),
|
||||||
|
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||||
|
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||||
|
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||||
|
);
|
||||||
|
$pag->configure($list_display, array(), array('status', 'modif_dtime'));
|
||||||
|
$pag->items_per_page = 10;
|
||||||
|
$pag->no_results_text = __('No issues were found.');
|
||||||
|
$pag->setFromRequest($request);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('issues/index.html',
|
||||||
|
array('project' => $prj,
|
||||||
|
'page_title' => $title,
|
||||||
|
'open' => $open,
|
||||||
|
'closed' => $closed,
|
||||||
|
'issues' => $pag,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View the issues of a given user.
|
||||||
|
*
|
||||||
|
* Only open issues are shown.
|
||||||
|
*/
|
||||||
|
public $myIssues_precond = array('Pluf_Precondition::loginRequired');
|
||||||
|
public function myIssues($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$otags = $prj->getTagIdsByStatus('open');
|
||||||
|
if (count($otags) == 0) $otags[] = 0;
|
||||||
|
if ($match[2] == 'submit') {
|
||||||
|
$title = sprintf(__('My Submitted %s Issues'), (string) $prj);
|
||||||
|
$f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
|
||||||
|
} else {
|
||||||
|
$title = sprintf(__('My Working %s Issues'), (string) $prj);
|
||||||
|
$f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
|
||||||
|
}
|
||||||
|
// Get stats about the issues
|
||||||
|
$sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
|
||||||
|
$nb_submit = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||||
|
$sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
|
||||||
|
$nb_owner = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
|
||||||
|
// Paginator to paginate the issues
|
||||||
|
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||||
|
$pag->class = 'recent-issues';
|
||||||
|
$pag->item_extra_props = array('project_m' => $prj,
|
||||||
|
'shortname' => $prj->shortname);
|
||||||
|
$pag->summary = __('This table shows the open recent issues.');
|
||||||
|
$pag->forced_where = $f_sql;
|
||||||
|
$pag->action = array('IDF_Views_Issue::myIssues', array($prj->shortname, $match[2]));
|
||||||
|
$pag->sort_order = array('modif_dtime', 'DESC');
|
||||||
|
$list_display = array(
|
||||||
|
'id' => __('Id'),
|
||||||
|
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||||
|
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||||
|
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||||
|
);
|
||||||
|
$pag->configure($list_display, array(), array('status', 'modif_dtime'));
|
||||||
|
$pag->items_per_page = 10;
|
||||||
|
$pag->no_results_text = __('No issues were found.');
|
||||||
|
$pag->setFromRequest($request);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('issues/my-issues.html',
|
||||||
|
array('project' => $prj,
|
||||||
|
'page_title' => $title,
|
||||||
|
'nb_submit' => $nb_submit,
|
||||||
|
'nb_owner' => $nb_owner,
|
||||||
|
'issues' => $pag,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public $create_precond = array('Pluf_Precondition::loginRequired');
|
||||||
|
public function create($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$title = __('Submit a new issue');
|
||||||
|
$params = array(
|
||||||
|
'project' => $prj,
|
||||||
|
'user' => $request->user);
|
||||||
|
if ($request->method == 'POST') {
|
||||||
|
$form = new IDF_Form_IssueCreate($request->POST, $params);
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$issue = $form->save();
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
|
||||||
|
array($prj->shortname));
|
||||||
|
$urlissue = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||||
|
array($prj->shortname, $issue->id));
|
||||||
|
$request->user->setMessage(sprintf(__('<a href="%s">Issue %d</a> has been created.'), $urlissue, $issue->id));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$form = new IDF_Form_IssueCreate(null, $params);
|
||||||
|
}
|
||||||
|
$arrays = self::autoCompleteArrays($prj);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('issues/create.html',
|
||||||
|
array_merge(
|
||||||
|
array('project' => $prj,
|
||||||
|
'form' => $form,
|
||||||
|
'page_title' => $title,
|
||||||
|
),
|
||||||
|
$arrays),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$issue = Pluf_Shortcuts_GetObjectOr404('IDF_Issue', $match[2]);
|
||||||
|
if ($issue->project != $prj->id) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
$comments = $issue->get_comments_list(array('order' => 'id ASC'));
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||||
|
array($prj->shortname, $issue->id));
|
||||||
|
$title = Pluf_Template::markSafe(sprintf(__('Issue <a href="%s">%d</a>: %s'), $url, $issue->id, $issue->summary));
|
||||||
|
$form = false; // The form is available only if logged in.
|
||||||
|
if (!$request->user->isAnonymous()) {
|
||||||
|
$params = array(
|
||||||
|
'project' => $prj,
|
||||||
|
'user' => $request->user,
|
||||||
|
'issue' => $issue,
|
||||||
|
);
|
||||||
|
if ($request->method == 'POST') {
|
||||||
|
$form = new IDF_Form_IssueUpdate($request->POST, $params);
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$issue = $form->save();
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
|
||||||
|
array($prj->shortname));
|
||||||
|
$request->user->setMessage(sprintf(__('Issue %d has been updated.'), $issue->id));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$form = new IDF_Form_IssueUpdate(null, $params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$arrays = self::autoCompleteArrays($prj);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('issues/view.html',
|
||||||
|
array_merge(
|
||||||
|
array('project' => $prj,
|
||||||
|
'issue' => $issue,
|
||||||
|
'comments' => $comments,
|
||||||
|
'form' => $form,
|
||||||
|
'page_title' => $title,
|
||||||
|
),
|
||||||
|
$arrays),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View list of issues for a given project with a given status.
|
||||||
|
*/
|
||||||
|
public function listStatus($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$status = $match[2];
|
||||||
|
$title = sprintf(__('%s Closed Issues'), (string) $prj);
|
||||||
|
// Get stats about the issues
|
||||||
|
$open = $prj->getIssueCountByStatus('open');
|
||||||
|
$closed = $prj->getIssueCountByStatus('closed');
|
||||||
|
// Paginator to paginate the issues
|
||||||
|
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||||
|
$pag->class = 'recent-issues';
|
||||||
|
$pag->item_extra_props = array('project_m' => $prj,
|
||||||
|
'shortname' => $prj->shortname);
|
||||||
|
$pag->summary = __('This table shows the closed issues.');
|
||||||
|
$otags = $prj->getTagIdsByStatus('closed');
|
||||||
|
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', 'DESC');
|
||||||
|
$list_display = array(
|
||||||
|
'id' => __('Id'),
|
||||||
|
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||||
|
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||||
|
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||||
|
);
|
||||||
|
$pag->configure($list_display, array(), array('id', 'status', 'modif_dtime'));
|
||||||
|
$pag->items_per_page = 10;
|
||||||
|
$pag->no_results_text = __('No issues were found.');
|
||||||
|
$pag->setFromRequest($request);
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('issues/index.html',
|
||||||
|
array('project' => $prj,
|
||||||
|
'page_title' => $title,
|
||||||
|
'open' => $open,
|
||||||
|
'closed' => $closed,
|
||||||
|
'issues' => $pag,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View list of issues for a given project with a given label.
|
||||||
|
*/
|
||||||
|
public function listLabel($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $match[2]);
|
||||||
|
$status = $match[3];
|
||||||
|
if ($tag->project != $prj->id or !in_array($status, array('open', 'closed'))) {
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
if ($status == 'open') {
|
||||||
|
$title = sprintf(__('%1$s Issues with Label %2$s'), (string) $prj,
|
||||||
|
(string) $tag);
|
||||||
|
} else {
|
||||||
|
$title = sprintf(__('%1$s Closed Issues with Label %2$s'),
|
||||||
|
(string) $prj, (string) $tag);
|
||||||
|
}
|
||||||
|
// Get stats about the open/closed issues having this tag.
|
||||||
|
$open = $prj->getIssueCountByStatus('open', $tag);
|
||||||
|
$closed = $prj->getIssueCountByStatus('closed', $tag);
|
||||||
|
// Paginator to paginate the issues
|
||||||
|
$pag = new Pluf_Paginator(new IDF_Issue());
|
||||||
|
$pag->model_view = 'join_tags';
|
||||||
|
$pag->class = 'recent-issues';
|
||||||
|
$pag->item_extra_props = array('project_m' => $prj,
|
||||||
|
'shortname' => $prj->shortname);
|
||||||
|
$pag->summary = sprintf(__('This table shows the issues with label %s.'), (string) $tag);
|
||||||
|
$otags = $prj->getTagIdsByStatus($status);
|
||||||
|
if (count($otags) == 0) $otags[] = 0;
|
||||||
|
$pag->forced_where = new Pluf_SQL('project=%s AND idf_tag_id=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $tag->id));
|
||||||
|
$pag->action = array('IDF_Views_Issue::listLabel', array($prj->shortname, $tag->id, $status));
|
||||||
|
$pag->sort_order = array('modif_dtime', 'DESC');
|
||||||
|
$list_display = array(
|
||||||
|
'id' => __('Id'),
|
||||||
|
array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')),
|
||||||
|
array('status', 'IDF_Views_Issue_ShowStatus', __('Status')),
|
||||||
|
array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')),
|
||||||
|
);
|
||||||
|
$pag->configure($list_display, array(), array('status', 'modif_dtime'));
|
||||||
|
$pag->items_per_page = 10;
|
||||||
|
$pag->no_results_text = __('No issues were found.');
|
||||||
|
$pag->setFromRequest($request);
|
||||||
|
if (($open+$closed) > 0) {
|
||||||
|
$completion = sprintf('%01.0f%%', (100*$closed)/((float) $open+$closed));
|
||||||
|
} else {
|
||||||
|
$completion = false;
|
||||||
|
}
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('issues/by-label.html',
|
||||||
|
array('project' => $prj,
|
||||||
|
'completion' => $completion,
|
||||||
|
'page_title' => $title,
|
||||||
|
'open' => $open,
|
||||||
|
'label' => $tag,
|
||||||
|
'closed' => $closed,
|
||||||
|
'issues' => $pag,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the autocomplete arrays for the little AJAX stuff.
|
||||||
|
*/
|
||||||
|
public static function autoCompleteArrays($project)
|
||||||
|
{
|
||||||
|
$conf = new IDF_Conf();
|
||||||
|
$conf->setProject($project);
|
||||||
|
$auto = array('auto_status' => '', 'auto_labels' => '');
|
||||||
|
$auto_raw = array('auto_status' => '', 'auto_labels' => '');
|
||||||
|
$st = $conf->getVal('labels_issue_open', IDF_Form_IssueTrackingConf::init_open);
|
||||||
|
$st .= "\n".$conf->getVal('labels_issue_closed', IDF_Form_IssueTrackingConf::init_closed);
|
||||||
|
$auto_raw['auto_status'] = $st;
|
||||||
|
$auto_raw['auto_labels'] = $conf->getVal('labels_issue_predefined', IDF_Form_IssueTrackingConf::init_predefined);
|
||||||
|
foreach ($auto_raw as $key => $st) {
|
||||||
|
$st = preg_split("/\015\012|\015|\012/", $st, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
foreach ($st as $s) {
|
||||||
|
$v = '';
|
||||||
|
$d = '';
|
||||||
|
$_s = split('=', $s, 2);
|
||||||
|
if (count($_s) > 1) {
|
||||||
|
$v = trim($_s[0]);
|
||||||
|
$d = trim($_s[1]);
|
||||||
|
} else {
|
||||||
|
$v = trim($_s[0]);
|
||||||
|
}
|
||||||
|
$auto[$key] .= sprintf('{ name: "%s", to: "%s" }, ',
|
||||||
|
Pluf_esc($d),
|
||||||
|
Pluf_esc($v));
|
||||||
|
}
|
||||||
|
$auto[$key] = substr($auto[$key], 0, -1);
|
||||||
|
}
|
||||||
|
return $auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the summary of an issue, then on a new line, display the
|
||||||
|
* list of labels with a link to a view "by label only".
|
||||||
|
*
|
||||||
|
* The summary of the issue is linking to the issue.
|
||||||
|
*/
|
||||||
|
function IDF_Views_Issue_SummaryAndLabels($field, $issue, $extra='')
|
||||||
|
{
|
||||||
|
$edit = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
|
||||||
|
array($issue->shortname, $issue->id));
|
||||||
|
$tags = array();
|
||||||
|
foreach ($issue->get_tags_list() as $tag) {
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::listLabel',
|
||||||
|
array($issue->shortname, $tag->id, 'open'));
|
||||||
|
$tags[] = sprintf('<a class="label" href="%s">%s</a>', $url, Pluf_esc((string) $tag));
|
||||||
|
}
|
||||||
|
$out = '';
|
||||||
|
if (count($tags)) {
|
||||||
|
$out = '<br /><span class="note">'.implode(', ', $tags).'</span>';
|
||||||
|
}
|
||||||
|
return sprintf('<a href="%s">%s</a>', $edit, Pluf_esc($issue->summary)).$out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the status in the issue listings.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function IDF_Views_Issue_ShowStatus($field, $issue, $extra='')
|
||||||
|
{
|
||||||
|
return Pluf_esc($issue->get_status()->name);
|
||||||
|
}
|
140
src/IDF/Views/Project.php
Normal file
140
src/IDF/Views/Project.php
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<?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');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project's views.
|
||||||
|
*/
|
||||||
|
class IDF_Views_Project
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Administrate the summary of a project.
|
||||||
|
*/
|
||||||
|
public $admin_precond = array('IDF_Precondition::projectOwner');
|
||||||
|
public function admin($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$title = sprintf(__('%s Project Summary'), (string) $prj);
|
||||||
|
$form_fields = array('fields'=> array('name', 'description'));
|
||||||
|
if ($request->method == 'POST') {
|
||||||
|
$form = Pluf_Shortcuts_GetFormForModel($prj, $request->POST,
|
||||||
|
$form_fields);
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$prj = $form->save();
|
||||||
|
$request->user->setMessage(__('The project has been updated.'));
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::admin',
|
||||||
|
array($prj->shortname));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$form = Pluf_Shortcuts_GetFormForModel($prj, $prj->getData(),
|
||||||
|
$form_fields);
|
||||||
|
}
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('admin/summary.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'form' => $form,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Administrate the issue tracking of a project.
|
||||||
|
*/
|
||||||
|
public $adminIssueTracking_precond = array('IDF_Precondition::projectOwner');
|
||||||
|
public function adminIssues($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$title = sprintf(__('%s Issue Tracking Configuration'), (string) $prj);
|
||||||
|
$conf = new IDF_Conf();
|
||||||
|
$conf->setProject($prj);
|
||||||
|
if ($request->method == 'POST') {
|
||||||
|
$form = new IDF_Form_IssueTrackingConf($request->POST);
|
||||||
|
if ($form->isValid()) {
|
||||||
|
foreach ($form->cleaned_data as $key=>$val) {
|
||||||
|
$conf->setVal($key, $val);
|
||||||
|
}
|
||||||
|
$request->user->setMessage(__('The issue tracking configuration has been saved.'));
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminIssues',
|
||||||
|
array($prj->shortname));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$params = array();
|
||||||
|
$keys = array('labels_issue_open', 'labels_issue_closed',
|
||||||
|
'labels_issue_predefined', 'labels_issue_one_max');
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$_val = $conf->getVal($key, false);
|
||||||
|
if ($_val !== false) {
|
||||||
|
$params[$key] = $_val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count($params) == 0) {
|
||||||
|
$params = null; //Nothing in the db, so new form.
|
||||||
|
}
|
||||||
|
$form = new IDF_Form_IssueTrackingConf($params);
|
||||||
|
}
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('admin/issue-tracking.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'form' => $form,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Administrate the members of a project.
|
||||||
|
*/
|
||||||
|
public $adminMembers_precond = array('IDF_Precondition::projectOwner');
|
||||||
|
public function adminMembers($request, $match)
|
||||||
|
{
|
||||||
|
$prj = $request->project;
|
||||||
|
$title = sprintf(__('%s Project Members'), (string) $prj);
|
||||||
|
$params = array(
|
||||||
|
'project' => $prj,
|
||||||
|
'user' => $request->user,
|
||||||
|
);
|
||||||
|
if ($request->method == 'POST') {
|
||||||
|
$form = new IDF_Form_MembersConf($request->POST, $params);
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$form->save();
|
||||||
|
$request->user->setMessage(__('The project membership has been saved.'));
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminMembers',
|
||||||
|
array($prj->shortname));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$form = new IDF_Form_MembersConf($prj->getMembershipData('string'), $params);
|
||||||
|
}
|
||||||
|
return Pluf_Shortcuts_RenderToResponse('admin/members.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'form' => $form,
|
||||||
|
),
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
}
|
112
src/IDF/conf/views.php
Normal file
112
src/IDF/conf/views.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?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 Loic d'Anterroches and contributors.
|
||||||
|
#
|
||||||
|
# Plume CMS 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.
|
||||||
|
#
|
||||||
|
# Plume CMS 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 ***** */
|
||||||
|
|
||||||
|
|
||||||
|
$ctl = array();
|
||||||
|
$base = Pluf::f('idf_base');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views',
|
||||||
|
'method' => 'index');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/login/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views',
|
||||||
|
'method' => 'login');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/logout/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views',
|
||||||
|
'method' => 'logout');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/help/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views',
|
||||||
|
'method' => 'faq');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views',
|
||||||
|
'method' => 'projectHome');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/issues/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Issue',
|
||||||
|
'method' => 'index');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/issues/(\d+)/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Issue',
|
||||||
|
'method' => 'view');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/issues/status/(\w+)/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Issue',
|
||||||
|
'method' => 'listStatus');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/issues/label/(\d+)/(\w+)/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Issue',
|
||||||
|
'method' => 'listLabel');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/issues/create/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Issue',
|
||||||
|
'method' => 'create');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/issues/my/(\w+)/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Issue',
|
||||||
|
'method' => 'myIssues');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/admin/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Project',
|
||||||
|
'method' => 'admin');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/admin/issues/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Project',
|
||||||
|
'method' => 'adminIssues');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/p/(\w+)/admin/members/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'priority' => 4,
|
||||||
|
'model' => 'IDF_Views_Project',
|
||||||
|
'method' => 'adminMembers');
|
||||||
|
|
||||||
|
return $ctl;
|
30
src/IDF/relations.php
Normal file
30
src/IDF/relations.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?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 Loic d'Anterroches and contributors.
|
||||||
|
#
|
||||||
|
# Plume CMS 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.
|
||||||
|
#
|
||||||
|
# Plume CMS 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 ***** */
|
||||||
|
|
||||||
|
$m = array();
|
||||||
|
$m['IDF_Tag'] = array('relate_to' => array('IDF_Project'));
|
||||||
|
$m['IDF_Issue'] = array('relate_to' => array('IDF_Project', 'Pluf_User', 'IDF_Tag'),
|
||||||
|
'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
|
||||||
|
$m['IDF_IssueComment'] = array('relate_to' => array('IDF_Issue', 'Pluf_User'));
|
||||||
|
|
||||||
|
return $m;
|
9
src/IDF/templates/admin/base.html
Normal file
9
src/IDF/templates/admin/base.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{extends "base.html"}
|
||||||
|
{block tabadmin} class="active"{/block}
|
||||||
|
{block subtabs}
|
||||||
|
<div id="sub-tabs">
|
||||||
|
<a {if $inSummary}class="active" {/if}href="{url 'IDF_Views_Project::admin', array($project.shortname)}">{trans 'Project Summary'}</a> |
|
||||||
|
<a {if $inMembers}class="active" {/if}href="{url 'IDF_Views_Project::adminMembers', array($project.shortname)}">{trans 'Project Members'}</a> |
|
||||||
|
<a {if $inIssueTracking}class="active" {/if}href="{url 'IDF_Views_Project::adminIssues', array($project.shortname)}">{trans 'Issue Tracking'}</a>
|
||||||
|
</div>
|
||||||
|
{/block}
|
49
src/IDF/templates/admin/issue-tracking.html
Normal file
49
src/IDF/templates/admin/issue-tracking.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{extends "admin/base.html"}
|
||||||
|
{block docclass}yui-t1{assign $inIssueTracking = true}{/block}
|
||||||
|
{block body}
|
||||||
|
<form method="post" action=".">
|
||||||
|
<table class="form" summary="">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><strong>{$form.f.labels_issue_open.labelTag}:</strong><br />
|
||||||
|
{if $form.f.labels_issue_open.errors}{$form.f.labels_issue_open.fieldErrors}{/if}
|
||||||
|
{$form.f.labels_issue_open|unsafe}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><strong>{$form.f.labels_issue_closed.labelTag}:</strong><br />
|
||||||
|
{if $form.f.labels_issue_closed.errors}{$form.f.labels_issue_closed.fieldErrors}{/if}
|
||||||
|
{$form.f.labels_issue_closed|unsafe}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><strong>{$form.f.labels_issue_predefined.labelTag}:</strong><br />
|
||||||
|
{if $form.f.labels_issue_predefined.errors}{$form.f.labels_issue_predefined.fieldErrors}{/if}
|
||||||
|
{$form.f.labels_issue_predefined|unsafe}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><strong>{$form.f.labels_issue_one_max.labelTag}:</strong><br />
|
||||||
|
{if $form.f.labels_issue_one_max.errors}{$form.f.labels_issue_one_max.fieldErrors}{/if}
|
||||||
|
{$form.f.labels_issue_one_max|unsafe}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
{blocktrans}
|
||||||
|
<p><strong>Instructions:</strong></p>
|
||||||
|
<p>List one status value per line in desired sort-order.</p>
|
||||||
|
<p>Optionally, use an equals-sign to document the meaning of each status value.</p>
|
||||||
|
{/blocktrans}
|
||||||
|
</div>
|
||||||
|
{/block}
|
44
src/IDF/templates/admin/members.html
Normal file
44
src/IDF/templates/admin/members.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{extends "admin/base.html"}
|
||||||
|
{block docclass}yui-t3{assign $inMembers = true}{/block}
|
||||||
|
{block body}
|
||||||
|
<form method="post" action=".">
|
||||||
|
<table class="form" summary="">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">{$form.f.owners.labelTag}:<br />
|
||||||
|
{if $form.f.owners.errors}{$form.f.owners.fieldErrors}{/if}
|
||||||
|
{$form.f.owners|unsafe}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">{$form.f.members.labelTag}:<br />
|
||||||
|
{if $form.f.members.errors}{$form.f.members.fieldErrors}{/if}
|
||||||
|
{$form.f.members|unsafe}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
{blocktrans}
|
||||||
|
<p><strong>Instructions:</strong></p>
|
||||||
|
<p>Specify each person by its login. Each person must have already registered with the given login.</p>
|
||||||
|
<p>Separate the logins with commas and/or new lines.</p>
|
||||||
|
{/blocktrans}
|
||||||
|
</div>
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
{blocktrans}
|
||||||
|
<p><strong>Notes:</strong></p>
|
||||||
|
<p>A project owner may make any change to this project, including removing other project owners. You need to be carefull when you give owner rights.</p>
|
||||||
|
<p>A project member will not have access to the administration area but will have more options available in the use of the project.</p>
|
||||||
|
{/blocktrans}
|
||||||
|
</div>
|
||||||
|
{/block}
|
42
src/IDF/templates/admin/summary.html
Normal file
42
src/IDF/templates/admin/summary.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{extends "admin/base.html"}
|
||||||
|
{block docclass}yui-t1{assign $inSummary = true}{/block}
|
||||||
|
{block body}
|
||||||
|
{if $form.errors}
|
||||||
|
<div class="px-message-error">
|
||||||
|
<p>{trans 'The form contains some errors. Please correct them to update the summary.'}</p>
|
||||||
|
{if $form.get_top_errors}
|
||||||
|
{$form.render_top_errors|unsafe}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<form method="post" action=".">
|
||||||
|
<table class="form" summary="">
|
||||||
|
<tr>
|
||||||
|
<th><strong>{$form.f.name.labelTag}:</strong></th>
|
||||||
|
<td>{if $form.f.name.errors}{$form.f.name.fieldErrors}{/if}
|
||||||
|
{$form.f.name|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><td> </td>
|
||||||
|
<td>
|
||||||
|
<input type="submit" name="submit" value="{trans 'Save Changes'}" />
|
||||||
|
</td>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
{assign $url = 'http://daringfireball.net/projects/markdown/syntax'}
|
||||||
|
{blocktrans}
|
||||||
|
<p><strong>Instructions:</strong></p>
|
||||||
|
<p>The description of the project can be improved using the <a href="{$url}">Markdown syntax</a>.</p>
|
||||||
|
{/blocktrans}
|
||||||
|
</div>
|
||||||
|
{/block}
|
63
src/IDF/templates/base-simple.html
Normal file
63
src/IDF/templates/base-simple.html
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<!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}" class="{block docclass}yui-t3{/block}">
|
||||||
|
<div id="hd">
|
||||||
|
<p class="top">
|
||||||
|
{if !$user.isAnonymous()}{blocktrans}Welcome, <strong>{$user.first_name} {$user.last_name}</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}
|
||||||
|
| <a href="{url 'IDF_Views::faq'}">{trans 'Help'}</a>
|
||||||
|
</p>
|
||||||
|
{* <div id="header">
|
||||||
|
|
||||||
|
</div> *}
|
||||||
|
|
||||||
|
<h1 class="title">{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 class="yui-b context">{block context}{/block}</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>
|
||||||
|
{block javascript}{/block}
|
||||||
|
</body>
|
||||||
|
</html>
|
71
src/IDF/templates/base.html
Normal file
71
src/IDF/templates/base.html
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<!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}" class="{block docclass}yui-t3{/block}">
|
||||||
|
<div id="hd">
|
||||||
|
{if $project}<h1 class="project-title">{$project}</h1>{/if}
|
||||||
|
<p class="top">
|
||||||
|
{if !$user.isAnonymous()}{blocktrans}Welcome, <strong>{$user.first_name} {$user.last_name}</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}
|
||||||
|
| <a href="{url 'IDF_Views::faq'}">{trans 'Help'}</a>
|
||||||
|
</p>
|
||||||
|
<div id="header">
|
||||||
|
<div id="main-tabs">
|
||||||
|
{if $project}
|
||||||
|
<a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>
|
||||||
|
{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">{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 class="yui-b context">{block context}{/block}</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 'js-hotkeys.html'}
|
||||||
|
{block javascript}{/block}
|
||||||
|
</body>
|
||||||
|
</html>
|
15
src/IDF/templates/index.html
Normal file
15
src/IDF/templates/index.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{extends "base-simple.html"}
|
||||||
|
{block docclass}yui-t1{/block}
|
||||||
|
{block body}
|
||||||
|
{if $projects.count() == 0}
|
||||||
|
<p>{trans 'No projects managed with InDefero were found.'}</p>
|
||||||
|
{if $user.administrator}<p>{blocktrans}Create a new project.{/blocktrans}</p>{/if}
|
||||||
|
{else}
|
||||||
|
<ul>{foreach $projects as $p}
|
||||||
|
<li><a href="{url 'IDF_Views::projectHome', array($p.shortname)}">{$p}</a> - {$p.description}</li>
|
||||||
|
{/foreach}</ul>
|
||||||
|
{/if}
|
||||||
|
{/block}
|
||||||
|
{block context}
|
||||||
|
<p><strong>{trans 'Managed Projects:'}</strong> {$projects.count()}</p>
|
||||||
|
{/block}
|
9
src/IDF/templates/issues/base.html
Normal file
9
src/IDF/templates/issues/base.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{extends "base.html"}
|
||||||
|
{block tabissues} class="active"{/block}
|
||||||
|
{block subtabs}
|
||||||
|
<div id="sub-tabs">
|
||||||
|
<a href="{url 'IDF_Views_Issue::index', array($project.shortname)}">{trans 'Recent issues'}</a>
|
||||||
|
{if !$user.isAnonymous()} | <a href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>{/if}
|
||||||
|
{superblock}
|
||||||
|
</div>
|
||||||
|
{/block}
|
23
src/IDF/templates/issues/by-label.html
Normal file
23
src/IDF/templates/issues/by-label.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{extends "issues/base.html"}
|
||||||
|
{block docclass}yui-t1{/block}
|
||||||
|
{block body}
|
||||||
|
{$issues.render}
|
||||||
|
{if !$user.isAnonymous()}
|
||||||
|
{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)}
|
||||||
|
<p><a href="{$url}">{trans 'New Issue'}</a></p>{/if}
|
||||||
|
|
||||||
|
{/block}
|
||||||
|
{block context}
|
||||||
|
{aurl 'open_url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')}
|
||||||
|
{aurl 'closed_url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, '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}{if $completion}
|
||||||
|
<p><strong>{trans 'Completion:'}</strong> {$completion}</p>
|
||||||
|
{/if}
|
||||||
|
<p><strong>{trans 'Label:'}</strong>
|
||||||
|
{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')}
|
||||||
|
<a href="{$url}" class="label"><strong>{$label.class}:</strong>{$label.name}</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
{/block}
|
72
src/IDF/templates/issues/create.html
Normal file
72
src/IDF/templates/issues/create.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{extends "issues/base.html"}
|
||||||
|
{block body}
|
||||||
|
{if $form.errors}
|
||||||
|
<div class="px-message-error">
|
||||||
|
<p>{trans 'The form contains some errors. Please correct them to submit the issue.'}</p>
|
||||||
|
{if $form.get_top_errors}
|
||||||
|
{$form.render_top_errors|unsafe}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data" action="{url 'IDF_Views_Issue::create', array($project.shortname)}" >
|
||||||
|
<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.content.labelTag}:</strong></th>
|
||||||
|
<td>{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if}
|
||||||
|
{$form.f.content|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>
|
||||||
|
<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 Issue'}" name="submit" /> | <a href="{url 'IDF_Views_Issue::index', array($project.shortname)}">{trans 'Cancel'}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{/block}
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
{blocktrans}<p>When you submit the issue do not forget to provide the following information:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The steps to reproduce the problem.</li>
|
||||||
|
<li>The version of the software and your operating system.</li>
|
||||||
|
<li>Any information that can help the developpers to solve the issue.</li>
|
||||||
|
<li><strong>Do not provide any password or confidential information!</strong></li>
|
||||||
|
</ul>{/blocktrans}
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
document.getElementById('id_summary').focus()
|
||||||
|
</script>
|
||||||
|
{/block}
|
||||||
|
{block javascript}{include 'issues/js-autocomplete.html'}{/block}
|
||||||
|
|
20
src/IDF/templates/issues/index.html
Normal file
20
src/IDF/templates/issues/index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{extends "issues/base.html"}
|
||||||
|
{block docclass}yui-t2{/block}
|
||||||
|
{block body}
|
||||||
|
{$issues.render}
|
||||||
|
{if !$user.isAnonymous()}
|
||||||
|
{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)}
|
||||||
|
<p><a href="{$url}">{trans 'New Issue'}</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}
|
||||||
|
|
||||||
|
<p class="smaller">{foreach $project.getTagCloud() as $label}
|
||||||
|
{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')}
|
||||||
|
<a href="{$url}" class="label"><strong>{$label.class}:</strong>{$label.name}</a>{/foreach}</p>
|
||||||
|
|
||||||
|
{/block}
|
40
src/IDF/templates/issues/js-autocomplete.html
Normal file
40
src/IDF/templates/issues/js-autocomplete.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<script type="text/javascript" src="{media '/idf/js/jquery.bgiframe.min.js'}"></script>
|
||||||
|
<script type="text/javascript" src="{media '/idf/js/jquery.autocomplete.min.js'}"></script>
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
// <!-- {literal}
|
||||||
|
$(document).ready(function(){
|
||||||
|
var auto_labels = [{/literal}{$auto_labels|safe}{literal}];
|
||||||
|
var auto_status = [{/literal}{$auto_status|safe}{literal}];
|
||||||
|
var j=0;
|
||||||
|
for (j=1;j<7;j=j+1) {
|
||||||
|
$("#id_label"+j).autocomplete(auto_labels, {
|
||||||
|
minChars: 0,
|
||||||
|
width: 310,
|
||||||
|
matchContains: true,
|
||||||
|
max: 50,
|
||||||
|
highlightItem: false,
|
||||||
|
formatItem: function(row, i, max, term) {
|
||||||
|
return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + " <span style='font-size: 80%;'>" + row.name + "</span>";
|
||||||
|
},
|
||||||
|
formatResult: function(row) {
|
||||||
|
return row.to;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$("#id_status").autocomplete(auto_status, {
|
||||||
|
minChars: 0,
|
||||||
|
width: 310,
|
||||||
|
matchContains: true,
|
||||||
|
max: 50,
|
||||||
|
highlightItem: false,
|
||||||
|
formatItem: function(row, i, max, term) {
|
||||||
|
return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + " <span style='font-size: 80%;'>" + row.name + "</span>";
|
||||||
|
},
|
||||||
|
formatResult: function(row) {
|
||||||
|
return row.to;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
{/literal} //-->
|
||||||
|
</script>
|
16
src/IDF/templates/issues/my-issues.html
Normal file
16
src/IDF/templates/issues/my-issues.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{extends "issues/base.html"}
|
||||||
|
{block docclass}yui-t2{/block}
|
||||||
|
{block body}
|
||||||
|
{$issues.render}
|
||||||
|
{if !$user.isAnonymous()}
|
||||||
|
{aurl 'url', 'IDF_Views_Issue::create', array($project.shortname)}
|
||||||
|
<p><a href="{$url}">{trans 'New Issue'}</a></p>{/if}
|
||||||
|
|
||||||
|
{/block}
|
||||||
|
{block context}
|
||||||
|
{aurl 'owner_url', 'IDF_Views_Issue::myIssues', array($project.shortname, 'owner')}
|
||||||
|
{aurl 'submit_url', 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}
|
||||||
|
<p><strong>{trans 'Submitted issues:'}</strong> <a href="{$submit_url}">{$nb_submit}</a></p>
|
||||||
|
{if $nb_owner > 0}
|
||||||
|
<p><strong>{trans 'Working issues:'}</strong> <a href="{$owner_url}">{$nb_owner}</a></p>{/if}
|
||||||
|
{/block}
|
110
src/IDF/templates/issues/view.html
Normal file
110
src/IDF/templates/issues/view.html
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
{extends "issues/base.html"}
|
||||||
|
{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}">
|
||||||
|
{if $i == 0}{assign $who = $issue.get_submitter()}
|
||||||
|
<p>{blocktrans}Reported by {$who}, {$c.creation_dtime|date}{/blocktrans}</p>
|
||||||
|
{else}{assign $who = $c.get_submitter()}
|
||||||
|
{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}">{$id}</a> by {$who}, {$c.creation_dtime|date}{/blocktrans}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<p class="issue-comment-text">{if strlen($c.content) > 0}{$c.content|nl2br}{else}<i>{trans '(No comments were given for this change.)'}</i>{/if}</p>
|
||||||
|
|
||||||
|
{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}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
{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()}
|
||||||
|
<p><strong>{trans 'Created:'}</strong> <span class="nobrk">{$issue.creation_dtime|dateago}</span> <span class="nobrk">{blocktrans}by {$submitter}{/blocktrans}</span></p>
|
||||||
|
<p>
|
||||||
|
<strong>{trans 'Updated:'}</strong> <span class="nobrk">{$issue.modif_dtime|dateago}</span></p>
|
||||||
|
<p>
|
||||||
|
<strong>{trans 'Status:'}</strong> {$issue.get_status.name}</p>
|
||||||
|
<p>
|
||||||
|
<strong>{trans 'Owner:'}</strong> {if $issue.get_owner == null}{trans 'No owner'}{else}{$issue.get_owner}{/if}
|
||||||
|
</p>{assign $tags = $issue.get_tags_list()}{if $tags.count()}
|
||||||
|
<p>
|
||||||
|
<strong>{trans 'Labels:'}</strong><br />
|
||||||
|
{foreach $issue.get_tags_list() 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 'issues/js-autocomplete.html'}{/if}{/block}
|
13
src/IDF/templates/js-hotkeys.html
Normal file
13
src/IDF/templates/js-hotkeys.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script type="text/javascript" src="{media '/idf/js/jquery.hotkeys.js'}"></script>
|
||||||
|
{if $project}
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
// <!--
|
||||||
|
var hk_submit_issue = '{url 'IDF_Views_Issue::create', array($project.shortname)}';
|
||||||
|
var hk_recent_issues = '{url 'IDF_Views_Issue::index', array($project.shortname)}';
|
||||||
|
{literal}
|
||||||
|
jQuery.hotkeys.add('Shift+a',function (){window.location.href=hk_submit_issue;});
|
||||||
|
jQuery.hotkeys.add('Shift+r',function (){window.location.href=hk_recent_issues;});
|
||||||
|
{/literal} //-->
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{/if}
|
27
src/IDF/templates/login_form.html
Normal file
27
src/IDF/templates/login_form.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{extends "base-simple.html"}
|
||||||
|
{block docclass}yui-t1{/block}
|
||||||
|
{block body}
|
||||||
|
<form method="post" action="{url 'IDF_Views::login'}">
|
||||||
|
<input type="hidden" name="_redirect_after" value="{$_redirect_after}" />
|
||||||
|
{if $error}
|
||||||
|
<p class="px-form-error">{$error}</p>
|
||||||
|
{/if}
|
||||||
|
<h3>{trans 'What is your login?'}</h3>
|
||||||
|
<p><label for="id_login">{trans 'My login is'}</label> <input type="text" name="login" id="id_login" value="{$login}" /></p>
|
||||||
|
|
||||||
|
<h3>{trans 'Do you have a password?'}</h3>
|
||||||
|
<p><input name="action" id="action-new-user" value="new-user" type="radio" /> <label for="action-new-user">{trans 'No, I am a new here.'}</label></p>
|
||||||
|
|
||||||
|
<p><input name="action" id="action-login" value="login" type="radio" checked="checked" /> <label for="action-login">{trans 'Yes'}</label>, <label for="id_password">{trans 'my password is'}</label> <input type="password" name="password" id="id_password" /></p>
|
||||||
|
|
||||||
|
<p><input type="submit" value="{trans 'Sign in'}" />
|
||||||
|
</p>
|
||||||
|
{* <p><a href="{url 'CM_Views::passwordRecovery-1', array(), true}">{trans 'Forgot your password? Click here'}</a>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
*}
|
||||||
|
</form>
|
||||||
|
<script type="text/javascript">
|
||||||
|
document.getElementById('id_login').focus()
|
||||||
|
</script>
|
||||||
|
{/block}
|
29
www/index.php
Normal file
29
www/index.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?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 ***** */
|
||||||
|
|
||||||
|
// Set the include path to have Pluf and IDF in it.
|
||||||
|
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).'/../src');
|
||||||
|
require 'Pluf.php';
|
||||||
|
Pluf::start(dirname(__FILE__).'/../src/IDF/conf/idf.php');
|
||||||
|
Pluf_Dispatcher::loadControllers(Pluf::f('idf_views'));
|
||||||
|
Pluf_Dispatcher::dispatch(Pluf_HTTP_URL::getAction());
|
BIN
www/media/idf/css/blank.gif
Normal file
BIN
www/media/idf/css/blank.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 B |
3
www/media/idf/css/ie6.css
Normal file
3
www/media/idf/css/ie6.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
img {
|
||||||
|
behavior: url("/media/idf/css/iepngfix.htc");
|
||||||
|
}
|
67
www/media/idf/css/iepngfix.htc
Normal file
67
www/media/idf/css/iepngfix.htc
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<public:component>
|
||||||
|
<public:attach event="onpropertychange" onevent="doFix()" />
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
// IE5.5+ PNG Alpha Fix v1.0RC4
|
||||||
|
// (c) 2004-2005 Angus Turnbull http://www.twinhelix.com
|
||||||
|
|
||||||
|
// This is licensed under the CC-GNU LGPL, version 2.1 or later.
|
||||||
|
// For details, see: http://creativecommons.org/licenses/LGPL/2.1/
|
||||||
|
|
||||||
|
|
||||||
|
// This must be a path to a blank image. That's all the configuration you need.
|
||||||
|
if (typeof blankImg == 'undefined') var blankImg = '/media/idf/css/blank.gif';
|
||||||
|
|
||||||
|
var f = 'DXImageTransform.Microsoft.AlphaImageLoader';
|
||||||
|
|
||||||
|
function filt(s, m)
|
||||||
|
{
|
||||||
|
if (filters[f])
|
||||||
|
{
|
||||||
|
filters[f].enabled = s ? true : false;
|
||||||
|
if (s) with (filters[f]) { src = s; sizingMethod = m }
|
||||||
|
}
|
||||||
|
else if (s) style.filter = 'progid:'+f+'(src="'+s+'",sizingMethod="'+m+'")';
|
||||||
|
}
|
||||||
|
|
||||||
|
function doFix()
|
||||||
|
{
|
||||||
|
// Assume IE7 is OK.
|
||||||
|
if (!/MSIE (5\.5|6\.)/.test(navigator.userAgent) ||
|
||||||
|
(event && !/(background|src)/.test(event.propertyName))) return;
|
||||||
|
|
||||||
|
var bgImg = currentStyle.backgroundImage || style.backgroundImage;
|
||||||
|
|
||||||
|
if (tagName == 'IMG')
|
||||||
|
{
|
||||||
|
if ((/\.png$/i).test(src))
|
||||||
|
{
|
||||||
|
if (currentStyle.width == 'auto' && currentStyle.height == 'auto')
|
||||||
|
style.width = offsetWidth + 'px';
|
||||||
|
filt(src, 'scale');
|
||||||
|
src = blankImg;
|
||||||
|
}
|
||||||
|
else if (src.indexOf(blankImg) < 0) filt();
|
||||||
|
}
|
||||||
|
else if (bgImg && bgImg != 'none')
|
||||||
|
{
|
||||||
|
if (bgImg.match(/^url[("']+(.*\.png)[)"']+$/i))
|
||||||
|
{
|
||||||
|
var s = RegExp.$1;
|
||||||
|
if (currentStyle.width == 'auto' && currentStyle.height == 'auto')
|
||||||
|
style.width = offsetWidth + 'px';
|
||||||
|
style.backgroundImage = 'none';
|
||||||
|
filt(s, 'crop');
|
||||||
|
// IE link fix.
|
||||||
|
for (var n = 0; n < childNodes.length; n++)
|
||||||
|
if (childNodes[n].style) childNodes[n].style.position = 'relative';
|
||||||
|
}
|
||||||
|
else filt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doFix();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</public:component>
|
292
www/media/idf/css/style.css
Normal file
292
www/media/idf/css/style.css
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
/*
|
||||||
|
# ***** 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 ***** */
|
||||||
|
|
||||||
|
.yui-g {
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.context {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form
|
||||||
|
*/
|
||||||
|
table.form th, table.form td {
|
||||||
|
border: none;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
table.form th {
|
||||||
|
text-align: right;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-message-error {
|
||||||
|
padding-left: 37px;
|
||||||
|
background: url("../img/dialog-error.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 3px 0;
|
||||||
|
color: #c00;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.errorlist {
|
||||||
|
color: #c00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.user-messages {
|
||||||
|
border: 1px solid rgb(229, 225, 169);
|
||||||
|
background-color: #fffde3;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-left: -1px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recent issues
|
||||||
|
*/
|
||||||
|
table.recent-issues {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.recent-issues th {
|
||||||
|
background-color: #e4e8E0;
|
||||||
|
vertical-align: top;
|
||||||
|
border-color: #d3d7cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.recent-issues tr {
|
||||||
|
border-left: 1px solid #d3d7cf;
|
||||||
|
border-right: 1px solid #d3d7cf;
|
||||||
|
border-bottom: 1px solid #d3d7cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.recent-issues td {
|
||||||
|
border: none;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.recent-issues tfoot th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.recent-issues tfoot th a {
|
||||||
|
color: #000;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.recent-issues th a.px-current-page {
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.px-sort {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 70%;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.px-header-title {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue
|
||||||
|
*/
|
||||||
|
p.issue-comment-text {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.issue-comment {
|
||||||
|
border-left: 3px solid #8ae234;
|
||||||
|
border-bottom: 1px solid #d3d7cf;
|
||||||
|
border-right: 1px solid #d3d7cf;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.issue-comment-first {
|
||||||
|
border-top: 1px solid #d3d7cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.issue-comment-signin {
|
||||||
|
-moz-border-radius: 0 0 3px 3px;
|
||||||
|
-webkit-border-radius: 0 0 3px 3px;
|
||||||
|
background-color: #d3d7cf;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.issue-comment-signin a {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.issue-changes {
|
||||||
|
background-color: #d3d7cf;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
padding: 4px;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.issue-submit-info {
|
||||||
|
background-color: #d3d7cf;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
padding: 0.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.label {
|
||||||
|
color: #204a87;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.label {
|
||||||
|
color: #204a87;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.nobrk {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr { visibility: hidden; }
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.title {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.project-title {
|
||||||
|
font-weight: normal;
|
||||||
|
float: right;
|
||||||
|
z-index: 100;
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smaller {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helptext {
|
||||||
|
font-size: 80%;
|
||||||
|
color: #555753;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.container {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tabs
|
||||||
|
*/
|
||||||
|
#main-tabs a {
|
||||||
|
background-color: #d3d7cf;
|
||||||
|
-moz-border-radius: 3px 3px 0 0;
|
||||||
|
-webkit-border-radius: 3px 3px 0 0;
|
||||||
|
padding: 4px 4px 0 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #2e3436;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-tabs a.active {
|
||||||
|
background-color: #a5e26a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sub-tabs {
|
||||||
|
background-color: #a5e26a;
|
||||||
|
-moz-border-radius: 0 3px 3px 3px;
|
||||||
|
-webkit-border-radius: 0 3px 3px 3px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sub-tabs a {
|
||||||
|
color: #2e3436;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sub-tabs a.active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autocomplete.
|
||||||
|
*/
|
||||||
|
.ac_results {
|
||||||
|
padding: 0px;
|
||||||
|
border: 1px solid black;
|
||||||
|
background-color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 99999;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ac_results ul {
|
||||||
|
width: 100%;
|
||||||
|
list-style-position: outside;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ac_results li {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
cursor: default;
|
||||||
|
display: block;
|
||||||
|
/*
|
||||||
|
if width will be 100% horizontal scrollbar will apear
|
||||||
|
when scroll mode will be used
|
||||||
|
*/
|
||||||
|
/*width: 100%;*/
|
||||||
|
font: menu;
|
||||||
|
font-size: 12px;
|
||||||
|
/*
|
||||||
|
it is very important, if line-height not setted or setted
|
||||||
|
in relative units scroll will be broken in firefox
|
||||||
|
*/
|
||||||
|
line-height: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ac_loading {
|
||||||
|
background: white url('../img/indicator.gif') right center no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ac_odd {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ac_over {
|
||||||
|
background-color: #4e9a06;
|
||||||
|
color: white;
|
||||||
|
}
|
9
www/media/idf/css/yui.css
Normal file
9
www/media/idf/css/yui.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
|
||||||
|
Code licensed under the BSD License:
|
||||||
|
http://developer.yahoo.net/yui/license.txt
|
||||||
|
version: 2.5.1
|
||||||
|
*/
|
||||||
|
html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}body {font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}
|
||||||
|
body{text-align:center;}#ft{clear:both;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;min-width:750px;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}.yui-t6 .yui-b{float:right;width:23.0769em;*width:22.50em;}.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}s .yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#bd:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#bd,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;}
|
||||||
|
h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;} em{font-style:italic;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{border:1px solid #000;padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}
|
BIN
www/media/idf/img/dialog-error.png
Normal file
BIN
www/media/idf/img/dialog-error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 925 B |
BIN
www/media/idf/img/indicator.gif
Normal file
BIN
www/media/idf/img/indicator.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
32
www/media/idf/js/jquery-1.2.6.min.js
vendored
Normal file
32
www/media/idf/js/jquery-1.2.6.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15
www/media/idf/js/jquery.autocomplete.min.js
vendored
Normal file
15
www/media/idf/js/jquery.autocomplete.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
www/media/idf/js/jquery.bgiframe.min.js
vendored
Normal file
10
www/media/idf/js/jquery.bgiframe.min.js
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net)
|
||||||
|
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||||
|
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
||||||
|
*
|
||||||
|
* $LastChangedDate: 2007-07-22 01:45:56 +0200 (Son, 22 Jul 2007) $
|
||||||
|
* $Rev: 2447 $
|
||||||
|
*
|
||||||
|
* Version 2.1.1
|
||||||
|
*/
|
||||||
|
(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&/6.0/.test(navigator.userAgent)){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};})(jQuery);
|
126
www/media/idf/js/jquery.hotkeys.js
Normal file
126
www/media/idf/js/jquery.hotkeys.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/******************************************************************************************************************************
|
||||||
|
|
||||||
|
* @ Original idea by by Binny V A, Original version: 2.00.A
|
||||||
|
* @ http://www.openjs.com/scripts/events/keyboard_shortcuts/
|
||||||
|
* @ Original License : BSD
|
||||||
|
|
||||||
|
* @ jQuery Plugin by Tzury Bar Yochay
|
||||||
|
mail: tzury.by@gmail.com
|
||||||
|
blog: evalinux.wordpress.com
|
||||||
|
face: facebook.com/profile.php?id=513676303
|
||||||
|
|
||||||
|
(c) Copyrights 2007
|
||||||
|
|
||||||
|
* @ jQuery Plugin version Beta (0.0.2)
|
||||||
|
* @ License: jQuery-License.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
add queue support (as in gmail) e.g. 'x' then 'y', etc.
|
||||||
|
add mouse + mouse wheel events.
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
$.hotkeys.add('Ctrl+c', function(){ alert('copy anyone?');});
|
||||||
|
$.hotkeys.add('Ctrl+c', {target:'div#editor', type:'keyup', propagate: true},function(){ alert('copy anyone?');});>
|
||||||
|
$.hotkeys.remove('Ctrl+c');
|
||||||
|
$.hotkeys.remove('Ctrl+c', {target:'div#editor', type:'keypress'});
|
||||||
|
|
||||||
|
******************************************************************************************************************************/
|
||||||
|
(function (jQuery){
|
||||||
|
this.version = '(beta)(0.0.3)';
|
||||||
|
this.all = {};
|
||||||
|
this.special_keys = {
|
||||||
|
27: 'esc', 9: 'tab', 32:'space', 13: 'return', 8:'backspace', 145: 'scroll', 20: 'capslock',
|
||||||
|
144: 'numlock', 19:'pause', 45:'insert', 36:'home', 46:'del',35:'end', 33: 'pageup',
|
||||||
|
34:'pagedown', 37:'left', 38:'up', 39:'right',40:'down', 112:'f1',113:'f2', 114:'f3',
|
||||||
|
115:'f4', 116:'f5', 117:'f6', 118:'f7', 119:'f8', 120:'f9', 121:'f10', 122:'f11', 123:'f12'};
|
||||||
|
|
||||||
|
this.shift_nums = { "`":"~", "1":"!", "2":"@", "3":"#", "4":"$", "5":"%", "6":"^", "7":"&",
|
||||||
|
"8":"*", "9":"(", "0":")", "-":"_", "=":"+", ";":":", "'":"\"", ",":"<",
|
||||||
|
".":">", "/":"?", "\\":"|" };
|
||||||
|
|
||||||
|
this.add = function(combi, options, callback) {
|
||||||
|
if (jQuery.isFunction(options)){
|
||||||
|
callback = options;
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
var opt = {},
|
||||||
|
defaults = {type: 'keydown', propagate: false, disableInInput: false, target: jQuery('html')[0]},
|
||||||
|
that = this;
|
||||||
|
opt = jQuery.extend( opt , defaults, options || {} );
|
||||||
|
combi = combi.toLowerCase();
|
||||||
|
|
||||||
|
// inspect if keystroke matches
|
||||||
|
var inspector = function(event) {
|
||||||
|
event = jQuery.event.fix(event); // jQuery event normalization.
|
||||||
|
var element = event.target;
|
||||||
|
// @ TextNode -> nodeType == 3
|
||||||
|
element = (element.nodeType==3) ? element.parentNode : element;
|
||||||
|
|
||||||
|
if(opt['disableInInput']) { // Disable shortcut keys in Input, Textarea fields
|
||||||
|
var target = jQuery(element);
|
||||||
|
if( target.is("input") || target.is("textarea")){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var code = event.which,
|
||||||
|
type = event.type,
|
||||||
|
character = String.fromCharCode(code).toLowerCase(),
|
||||||
|
special = that.special_keys[code],
|
||||||
|
shift = event.shiftKey,
|
||||||
|
ctrl = event.ctrlKey,
|
||||||
|
alt= event.altKey,
|
||||||
|
propagate = true, // default behaivour
|
||||||
|
mapPoint = null;
|
||||||
|
|
||||||
|
// in opera + safari, the event.target is unpredictable.
|
||||||
|
// for example: 'keydown' might be associated with HtmlBodyElement
|
||||||
|
// or the element where you last clicked with your mouse.
|
||||||
|
if (jQuery.browser.opera || jQuery.browser.safari){
|
||||||
|
while (!that.all[element] && element.parentNode){
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var cbMap = that.all[element].events[type].callbackMap;
|
||||||
|
if(!shift && !ctrl && !alt) { // No Modifiers
|
||||||
|
mapPoint = cbMap[special] || cbMap[character]
|
||||||
|
}
|
||||||
|
// deals with combinaitons (alt|ctrl|shift+anything)
|
||||||
|
else{
|
||||||
|
var modif = '';
|
||||||
|
if(alt) modif +='alt+';
|
||||||
|
if(ctrl) modif+= 'ctrl+';
|
||||||
|
if(shift) modif += 'shift+';
|
||||||
|
// modifiers + special keys or modifiers + characters or modifiers + shift characters
|
||||||
|
mapPoint = cbMap[modif+special] || cbMap[modif+character] || cbMap[modif+that.shift_nums[character]]
|
||||||
|
}
|
||||||
|
if (mapPoint){
|
||||||
|
mapPoint.cb(event);
|
||||||
|
if(!mapPoint.propagate) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// first hook for this element
|
||||||
|
if (!this.all[opt.target]){
|
||||||
|
this.all[opt.target] = {events:{}};
|
||||||
|
}
|
||||||
|
if (!this.all[opt.target].events[opt.type]){
|
||||||
|
this.all[opt.target].events[opt.type] = {callbackMap: {}}
|
||||||
|
jQuery.event.add(opt.target, opt.type, inspector);
|
||||||
|
}
|
||||||
|
this.all[opt.target].events[opt.type].callbackMap[combi] = {cb: callback, propagate:opt.propagate};
|
||||||
|
return jQuery;
|
||||||
|
};
|
||||||
|
this.remove = function(exp, opt) {
|
||||||
|
opt = opt || {};
|
||||||
|
target = opt.target || jQuery('html')[0];
|
||||||
|
type = opt.type || 'keydown';
|
||||||
|
exp = exp.toLowerCase();
|
||||||
|
delete this.all[target].events[type].callbackMap[exp]
|
||||||
|
return jQuery;
|
||||||
|
};
|
||||||
|
jQuery.hotkeys = this;
|
||||||
|
return jQuery;
|
||||||
|
})(jQuery);
|
Loading…
Reference in New Issue
Block a user