Initial commit.

This commit is contained in:
Loic d'Anterroches 2008-07-25 10:26:05 +02:00
commit efbd82fccb
49 changed files with 3909 additions and 0 deletions

4
.gitignore vendored Normal file
View 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
View 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();
}
}
}

View File

@ -0,0 +1,42 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 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);
}
}

View 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;
}
}

View 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),
));
}
}

View 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.'));
}
}

View 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
View File

@ -0,0 +1,158 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 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
View 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
View 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;
}

View 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
View 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
View 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
View 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
View 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);
}
}

View 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
View File

@ -0,0 +1,64 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 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
View 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
View 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
View 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
View 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;

View 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}

View 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}

View 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}

View 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>&nbsp;</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}

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

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

View 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}

View 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}

View 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}

View 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>&nbsp;</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}

View 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}

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

View 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}

View 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>&nbsp;</td>
<td><input type="submit" value="{trans 'Submit Changes'}" name="submit" /> | <a href="{url 'IDF_Views_Issue::view', array($project.shortname, $issue.id)}">{trans 'Cancel'}</a>
</td>
</tr>
</table>
</form>
{/if}
{/block}
{block context}
<div class="issue-info">
{assign $submitter = $issue.get_submitter()}
<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}

View 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}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 B

View File

@ -0,0 +1,3 @@
img {
behavior: url("/media/idf/css/iepngfix.htc");
}

View 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
View 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;
}

View 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;}

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

10
www/media/idf/js/jquery.bgiframe.min.js vendored Normal file
View 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);

View 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);