diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b067092 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +tmp +src/IDF/conf/idf.php +src/IDF/conf/idf.test.php +www/test.php diff --git a/src/IDF/Conf.php b/src/IDF/Conf.php new file mode 100644 index 0000000..94483b1 --- /dev/null +++ b/src/IDF/Conf.php @@ -0,0 +1,133 @@ +_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(); + } + } +} diff --git a/src/IDF/Conf/DataProxy.php b/src/IDF/Conf/DataProxy.php new file mode 100644 index 0000000..226b17b --- /dev/null +++ b/src/IDF/Conf/DataProxy.php @@ -0,0 +1,42 @@ +obj = $obj; + } + + public function __get($field) + { + return $this->obj->getVal($field); + } +} diff --git a/src/IDF/Form/IssueCreate.php b/src/IDF/Form/IssueCreate.php new file mode 100644 index 0000000..8f26c0b --- /dev/null +++ b/src/IDF/Form/IssueCreate.php @@ -0,0 +1,261 @@ +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; + } +} diff --git a/src/IDF/Form/IssueTrackingConf.php b/src/IDF/Form/IssueTrackingConf.php new file mode 100644 index 0000000..ae7a9d0 --- /dev/null +++ b/src/IDF/Form/IssueTrackingConf.php @@ -0,0 +1,107 @@ +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), + )); + + + + } +} + + diff --git a/src/IDF/Form/IssueUpdate.php b/src/IDF/Form/IssueUpdate.php new file mode 100644 index 0000000..ae4b274 --- /dev/null +++ b/src/IDF/Form/IssueUpdate.php @@ -0,0 +1,260 @@ +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.')); + } +} diff --git a/src/IDF/Form/MembersConf.php b/src/IDF/Form/MembersConf.php new file mode 100644 index 0000000..c919aa2 --- /dev/null +++ b/src/IDF/Form/MembersConf.php @@ -0,0 +1,86 @@ +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); + } + } + } + } +} + + diff --git a/src/IDF/Issue.php b/src/IDF/Issue.php new file mode 100644 index 0000000..9341947 --- /dev/null +++ b/src/IDF/Issue.php @@ -0,0 +1,158 @@ +_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(); + } +} \ No newline at end of file diff --git a/src/IDF/IssueComment.php b/src/IDF/IssueComment.php new file mode 100644 index 0000000..8d82b94 --- /dev/null +++ b/src/IDF/IssueComment.php @@ -0,0 +1,119 @@ +_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(); + } +} \ No newline at end of file diff --git a/src/IDF/Middleware.php b/src/IDF/Middleware.php new file mode 100644 index 0000000..741c462 --- /dev/null +++ b/src/IDF/Middleware.php @@ -0,0 +1,66 @@ +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; +} + diff --git a/src/IDF/Migrations/Install.php b/src/IDF/Migrations/Install.php new file mode 100644 index 0000000..4cc6d26 --- /dev/null +++ b/src/IDF/Migrations/Install.php @@ -0,0 +1,79 @@ +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(); + } +} \ No newline at end of file diff --git a/src/IDF/Precondition.php b/src/IDF/Precondition.php new file mode 100644 index 0000000..9fc1f76 --- /dev/null +++ b/src/IDF/Precondition.php @@ -0,0 +1,43 @@ +user->hasPerm('IDF.project-owner', $request->project)) { + return true; + } + return new Pluf_HTTP_Response_Forbidden($request); + } +} \ No newline at end of file diff --git a/src/IDF/Project.php b/src/IDF/Project.php new file mode 100644 index 0000000..75f11a6 --- /dev/null +++ b/src/IDF/Project.php @@ -0,0 +1,280 @@ +_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; + } +} \ No newline at end of file diff --git a/src/IDF/Tag.php b/src/IDF/Tag.php new file mode 100644 index 0000000..9bf2e3b --- /dev/null +++ b/src/IDF/Tag.php @@ -0,0 +1,133 @@ +_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; + } + +} diff --git a/src/IDF/Tests/TestIssue.php b/src/IDF/Tests/TestIssue.php new file mode 100644 index 0000000..a6c300f --- /dev/null +++ b/src/IDF/Tests/TestIssue.php @@ -0,0 +1,138 @@ +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); + } +} \ No newline at end of file diff --git a/src/IDF/Tests/TestProject.php b/src/IDF/Tests/TestProject.php new file mode 100644 index 0000000..03a1665 --- /dev/null +++ b/src/IDF/Tests/TestProject.php @@ -0,0 +1,77 @@ +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(); + } + } +} \ No newline at end of file diff --git a/src/IDF/Views.php b/src/IDF/Views.php new file mode 100644 index 0000000..fb90764 --- /dev/null +++ b/src/IDF/Views.php @@ -0,0 +1,64 @@ +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')); + } + +} \ No newline at end of file diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php new file mode 100644 index 0000000..53d4a0b --- /dev/null +++ b/src/IDF/Views/Issue.php @@ -0,0 +1,371 @@ +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(__('Issue %d 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 %d: %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('%s', $url, Pluf_esc((string) $tag)); + } + $out = ''; + if (count($tags)) { + $out = '
'.implode(', ', $tags).''; + } + return sprintf('%s', $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); +} \ No newline at end of file diff --git a/src/IDF/Views/Project.php b/src/IDF/Views/Project.php new file mode 100644 index 0000000..48c1132 --- /dev/null +++ b/src/IDF/Views/Project.php @@ -0,0 +1,140 @@ +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); + } +} \ No newline at end of file diff --git a/src/IDF/conf/views.php b/src/IDF/conf/views.php new file mode 100644 index 0000000..472711d --- /dev/null +++ b/src/IDF/conf/views.php @@ -0,0 +1,112 @@ + '#^/$#', + '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; diff --git a/src/IDF/relations.php b/src/IDF/relations.php new file mode 100644 index 0000000..21f85c0 --- /dev/null +++ b/src/IDF/relations.php @@ -0,0 +1,30 @@ + 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; diff --git a/src/IDF/templates/admin/base.html b/src/IDF/templates/admin/base.html new file mode 100644 index 0000000..3670329 --- /dev/null +++ b/src/IDF/templates/admin/base.html @@ -0,0 +1,9 @@ +{extends "base.html"} +{block tabadmin} class="active"{/block} +{block subtabs} +
+{trans 'Project Summary'} | +{trans 'Project Members'} | +{trans 'Issue Tracking'} +
+{/block} diff --git a/src/IDF/templates/admin/issue-tracking.html b/src/IDF/templates/admin/issue-tracking.html new file mode 100644 index 0000000..d4820b5 --- /dev/null +++ b/src/IDF/templates/admin/issue-tracking.html @@ -0,0 +1,49 @@ +{extends "admin/base.html"} +{block docclass}yui-t1{assign $inIssueTracking = true}{/block} +{block body} +
+ + + + + + + + + + + + + + + + +
{$form.f.labels_issue_open.labelTag}:
+{if $form.f.labels_issue_open.errors}{$form.f.labels_issue_open.fieldErrors}{/if} +{$form.f.labels_issue_open|unsafe} +
{$form.f.labels_issue_closed.labelTag}:
+{if $form.f.labels_issue_closed.errors}{$form.f.labels_issue_closed.fieldErrors}{/if} +{$form.f.labels_issue_closed|unsafe} +
{$form.f.labels_issue_predefined.labelTag}:
+{if $form.f.labels_issue_predefined.errors}{$form.f.labels_issue_predefined.fieldErrors}{/if} +{$form.f.labels_issue_predefined|unsafe} +
{$form.f.labels_issue_one_max.labelTag}:
+{if $form.f.labels_issue_one_max.errors}{$form.f.labels_issue_one_max.fieldErrors}{/if} +{$form.f.labels_issue_one_max|unsafe} +
+ +
+
+ + +{/block} + +{block context} +
+{blocktrans} +

Instructions:

+

List one status value per line in desired sort-order.

+

Optionally, use an equals-sign to document the meaning of each status value.

+{/blocktrans} +
+{/block} diff --git a/src/IDF/templates/admin/members.html b/src/IDF/templates/admin/members.html new file mode 100644 index 0000000..6be580b --- /dev/null +++ b/src/IDF/templates/admin/members.html @@ -0,0 +1,44 @@ +{extends "admin/base.html"} +{block docclass}yui-t3{assign $inMembers = true}{/block} +{block body} +
+ + + + + + + + + + +
{$form.f.owners.labelTag}:
+{if $form.f.owners.errors}{$form.f.owners.fieldErrors}{/if} +{$form.f.owners|unsafe} +
{$form.f.members.labelTag}:
+{if $form.f.members.errors}{$form.f.members.fieldErrors}{/if} +{$form.f.members|unsafe} +
+ +
+
+ + +{/block} + +{block context} +
+{blocktrans} +

Instructions:

+

Specify each person by its login. Each person must have already registered with the given login.

+

Separate the logins with commas and/or new lines.

+{/blocktrans} +
+
+{blocktrans} +

Notes:

+

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.

+

A project member will not have access to the administration area but will have more options available in the use of the project.

+{/blocktrans} +
+{/block} diff --git a/src/IDF/templates/admin/summary.html b/src/IDF/templates/admin/summary.html new file mode 100644 index 0000000..a81a32b --- /dev/null +++ b/src/IDF/templates/admin/summary.html @@ -0,0 +1,42 @@ +{extends "admin/base.html"} +{block docclass}yui-t1{assign $inSummary = true}{/block} +{block body} +{if $form.errors} +
+

{trans 'The form contains some errors. Please correct them to update the summary.'}

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +
+{/if} +
+ + + + + + + + + + + +
{$form.f.name.labelTag}:{if $form.f.name.errors}{$form.f.name.fieldErrors}{/if} +{$form.f.name|unsafe} +
{$form.f.description.labelTag}:{if $form.f.description.errors}{$form.f.description.fieldErrors}{/if} +{$form.f.description|unsafe} +
  + +
+
+{/block} + +{block context} +
+{assign $url = 'http://daringfireball.net/projects/markdown/syntax'} +{blocktrans} +

Instructions:

+

The description of the project can be improved using the Markdown syntax.

+{/blocktrans} +
+{/block} diff --git a/src/IDF/templates/base-simple.html b/src/IDF/templates/base-simple.html new file mode 100644 index 0000000..b7d8ff6 --- /dev/null +++ b/src/IDF/templates/base-simple.html @@ -0,0 +1,63 @@ + +{* +# ***** 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 ***** +*} + + + + + + {block extraheader}{/block} + {block pagetitle}{$page_title|strip_tags}{/block} + + +
+
+

+{if !$user.isAnonymous()}{blocktrans}Welcome, {$user.first_name} {$user.last_name}.{/blocktrans} {trans 'Sign Out'}{else}{trans 'Sign in or create your account'}{/if} +| {trans 'Help'} +

+{* *} + +

{block title}{$page_title}{/block}

+ +
+
+
+
+
+ {if $user and $user.id}{getmsgs $user}{/if} +
{block body}{/block}
+
+
+
+
{block context}{/block}
+
+
{block foot}{/block}
+
+ +{block javascript}{/block} + + diff --git a/src/IDF/templates/base.html b/src/IDF/templates/base.html new file mode 100644 index 0000000..7456d28 --- /dev/null +++ b/src/IDF/templates/base.html @@ -0,0 +1,71 @@ + +{* +# ***** 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 ***** +*} + + + + + + {block extraheader}{/block} + {block pagetitle}{$page_title|strip_tags}{/block} + + +
+
+{if $project}

{$project}

{/if} +

+{if !$user.isAnonymous()}{blocktrans}Welcome, {$user.first_name} {$user.last_name}.{/blocktrans} {trans 'Sign Out'}{else}{trans 'Sign in or create your account'}{/if} +| {trans 'Help'} +

+ + +

{block title}{$page_title}{/block}

+ +
+
+
+
+
+ {if $user and $user.id}{getmsgs $user}{/if} +
{block body}{/block}
+
+
+
+
{block context}{/block}
+
+
{block foot}{/block}
+
+ +{include 'js-hotkeys.html'} +{block javascript}{/block} + + diff --git a/src/IDF/templates/index.html b/src/IDF/templates/index.html new file mode 100644 index 0000000..1a69381 --- /dev/null +++ b/src/IDF/templates/index.html @@ -0,0 +1,15 @@ +{extends "base-simple.html"} +{block docclass}yui-t1{/block} +{block body} +{if $projects.count() == 0} +

{trans 'No projects managed with InDefero were found.'}

+{if $user.administrator}

{blocktrans}Create a new project.{/blocktrans}

{/if} +{else} + +{/if} +{/block} +{block context} +

{trans 'Managed Projects:'} {$projects.count()}

+{/block} diff --git a/src/IDF/templates/issues/base.html b/src/IDF/templates/issues/base.html new file mode 100644 index 0000000..0ca71d9 --- /dev/null +++ b/src/IDF/templates/issues/base.html @@ -0,0 +1,9 @@ +{extends "base.html"} +{block tabissues} class="active"{/block} +{block subtabs} +
+{trans 'Recent issues'} +{if !$user.isAnonymous()} | {trans 'New Issue'} | {trans 'My Issues'}{/if} +{superblock} +
+{/block} diff --git a/src/IDF/templates/issues/by-label.html b/src/IDF/templates/issues/by-label.html new file mode 100644 index 0000000..89ecba8 --- /dev/null +++ b/src/IDF/templates/issues/by-label.html @@ -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)} +

{trans 'New Issue'}

{/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}

Open issues: {$open}

+

Closed issues: {$closed}

+{/blocktrans}{if $completion} +

{trans 'Completion:'} {$completion}

+{/if} +

{trans 'Label:'} +{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')} +{$label.class}:{$label.name}

+ + +{/block} diff --git a/src/IDF/templates/issues/create.html b/src/IDF/templates/issues/create.html new file mode 100644 index 0000000..6d315b2 --- /dev/null +++ b/src/IDF/templates/issues/create.html @@ -0,0 +1,72 @@ +{extends "issues/base.html"} +{block body} +{if $form.errors} +
+

{trans 'The form contains some errors. Please correct them to submit the issue.'}

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +
+{/if} + +
+ + + + + + + + +{if $isOwner or $isMember} + + + + + + + + + + + +{/if} + + + + +
{$form.f.summary.labelTag}:{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if} +{$form.f.summary|unsafe} +
{$form.f.content.labelTag}:{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if} +{$form.f.content|unsafe} +
{$form.f.status.labelTag}:{if $form.f.status.errors}{$form.f.status.fieldErrors}{/if} +{$form.f.status|unsafe} +
{$form.f.owner.labelTag}:{if $form.f.owner.errors}{$form.f.owner.fieldErrors}{/if} +{$form.f.owner|unsafe} +
{$form.f.label1.labelTag}: +{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}
+{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} +
  | {trans 'Cancel'} +
+
+{/block} +{block context} +
+{blocktrans}

When you submit the issue do not forget to provide the following information:

+{/blocktrans} +
+ +{/block} +{block javascript}{include 'issues/js-autocomplete.html'}{/block} + diff --git a/src/IDF/templates/issues/index.html b/src/IDF/templates/issues/index.html new file mode 100644 index 0000000..4b642bb --- /dev/null +++ b/src/IDF/templates/issues/index.html @@ -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)} +

{trans 'New Issue'}

{/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}

Open issues: {$open}

+

Closed issues: {$closed}

{/blocktrans} + +

{foreach $project.getTagCloud() as $label} +{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')} +{$label.class}:{$label.name}{/foreach}

+ +{/block} diff --git a/src/IDF/templates/issues/js-autocomplete.html b/src/IDF/templates/issues/js-autocomplete.html new file mode 100644 index 0000000..6ae60e4 --- /dev/null +++ b/src/IDF/templates/issues/js-autocomplete.html @@ -0,0 +1,40 @@ + + + diff --git a/src/IDF/templates/issues/my-issues.html b/src/IDF/templates/issues/my-issues.html new file mode 100644 index 0000000..992e95c --- /dev/null +++ b/src/IDF/templates/issues/my-issues.html @@ -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)} +

{trans 'New Issue'}

{/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')} +

{trans 'Submitted issues:'} {$nb_submit}

+{if $nb_owner > 0} +

{trans 'Working issues:'} {$nb_owner}

{/if} +{/block} diff --git a/src/IDF/templates/issues/view.html b/src/IDF/templates/issues/view.html new file mode 100644 index 0000000..2c58ab2 --- /dev/null +++ b/src/IDF/templates/issues/view.html @@ -0,0 +1,110 @@ +{extends "issues/base.html"} +{block body} +{assign $i = 0} +{assign $nc = $comments.count()} +{foreach $comments as $c} +
+{if $i == 0}{assign $who = $issue.get_submitter()} +

{blocktrans}Reported by {$who}, {$c.creation_dtime|date}{/blocktrans}

+{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} +

{blocktrans}Comment {$id} by {$who}, {$c.creation_dtime|date}{/blocktrans}

+{/if} + +

{if strlen($c.content) > 0}{$c.content|nl2br}{else}{trans '(No comments were given for this change.)'}{/if}

+ +{if $i> 0 and $c.changedIssue()} +
+{foreach $c.changes as $w => $v} +{if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if} {if $w == 'lb'}{assign $l = implode(', ', $v)}{$l}{else}{$v}{/if}
+{/foreach} +
+{/if} +
{assign $i = $i + 1}{if $i == $nc and false == $form} +
+{aurl 'url', 'IDF_Views::login'}{blocktrans}Sign in to reply to this comment.{/blocktrans} +
+{/if} +{/foreach} + +{if $form} +
+ +{if $form.errors} +
+

{trans 'The form contains some errors. Please correct them to change the issue.'}

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +
+{/if} + +
+ + + + + +{if $isOwner or $isMember} + + + + + + + + + + + + + + + +{/if} + + + + +
{$form.f.content.labelTag}:{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if} +{$form.f.content|unsafe} +
{$form.f.summary.labelTag}:{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if} +{$form.f.summary|unsafe} +
{$form.f.status.labelTag}:{if $form.f.status.errors}{$form.f.status.fieldErrors}{/if} +{$form.f.status|unsafe} +
{$form.f.owner.labelTag}:{if $form.f.owner.errors}{$form.f.owner.fieldErrors}{/if} +{$form.f.owner|unsafe} +
{$form.f.label1.labelTag}: +{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}
+{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} +
  | {trans 'Cancel'} +
+
+{/if} +{/block} +{block context} +
+{assign $submitter = $issue.get_submitter()} +

{trans 'Created:'} {$issue.creation_dtime|dateago} {blocktrans}by {$submitter}{/blocktrans}

+

+{trans 'Updated:'} {$issue.modif_dtime|dateago}

+

+{trans 'Status:'} {$issue.get_status.name}

+

+{trans 'Owner:'} {if $issue.get_owner == null}{trans 'No owner'}{else}{$issue.get_owner}{/if} +

{assign $tags = $issue.get_tags_list()}{if $tags.count()} +

+{trans 'Labels:'}
+{foreach $issue.get_tags_list() as $tag}{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $tag.id, 'open')} +{$tag.class}:{$tag.name}
+{/foreach} +

{/if} +
+{/block} +{block javascript}{if $form}{include 'issues/js-autocomplete.html'}{/if}{/block} diff --git a/src/IDF/templates/js-hotkeys.html b/src/IDF/templates/js-hotkeys.html new file mode 100644 index 0000000..6ba780d --- /dev/null +++ b/src/IDF/templates/js-hotkeys.html @@ -0,0 +1,13 @@ + +{if $project} + + +{/if} diff --git a/src/IDF/templates/login_form.html b/src/IDF/templates/login_form.html new file mode 100644 index 0000000..0c69f0f --- /dev/null +++ b/src/IDF/templates/login_form.html @@ -0,0 +1,27 @@ +{extends "base-simple.html"} +{block docclass}yui-t1{/block} +{block body} +
+ +{if $error} +

{$error}

+{/if} +

{trans 'What is your login?'}

+

+ +

{trans 'Do you have a password?'}

+

+ +

,

+ +

+

+{*

{trans 'Forgot your password? Click here'}.

+ + +*} +
+ +{/block} diff --git a/www/index.php b/www/index.php new file mode 100644 index 0000000..d9ee292 --- /dev/null +++ b/www/index.php @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css new file mode 100644 index 0000000..e8583f9 --- /dev/null +++ b/www/media/idf/css/style.css @@ -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; +} diff --git a/www/media/idf/css/yui.css b/www/media/idf/css/yui.css new file mode 100644 index 0000000..14eed69 --- /dev/null +++ b/www/media/idf/css/yui.css @@ -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;} diff --git a/www/media/idf/img/dialog-error.png b/www/media/idf/img/dialog-error.png new file mode 100644 index 0000000..7d6aaf6 Binary files /dev/null and b/www/media/idf/img/dialog-error.png differ diff --git a/www/media/idf/img/indicator.gif b/www/media/idf/img/indicator.gif new file mode 100644 index 0000000..085ccae Binary files /dev/null and b/www/media/idf/img/indicator.gif differ diff --git a/www/media/idf/js/jquery-1.2.6.min.js b/www/media/idf/js/jquery-1.2.6.min.js new file mode 100644 index 0000000..82b98e1 --- /dev/null +++ b/www/media/idf/js/jquery-1.2.6.min.js @@ -0,0 +1,32 @@ +/* + * jQuery 1.2.6 - New Wave Javascript + * + * Copyright (c) 2008 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ + * $Rev: 5685 $ + */ +(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else +return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else +return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else +selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else +this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else +return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else +jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else +jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!tags.indexOf("",""]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
","
"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else +ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else +while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else +for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else +xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else +jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else +for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else +s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else +e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file diff --git a/www/media/idf/js/jquery.autocomplete.min.js b/www/media/idf/js/jquery.autocomplete.min.js new file mode 100644 index 0000000..c9ddfb2 --- /dev/null +++ b/www/media/idf/js/jquery.autocomplete.min.js @@ -0,0 +1,15 @@ +/* + * Autocomplete - jQuery plugin 1.0.2 + * + * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $ + * + */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&¤tValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];}var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value))result[i]=$.trim(value);});return result;}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else +$input.val("");}});}if(wasVisible)$.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"$1");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else +if(data[q]){return data[q];}else +if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("
").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("
    ").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}}field.focus();};})(jQuery); \ No newline at end of file diff --git a/www/media/idf/js/jquery.bgiframe.min.js b/www/media/idf/js/jquery.bgiframe.min.js new file mode 100644 index 0000000..7faef4b --- /dev/null +++ b/www/media/idf/js/jquery.bgiframe.min.js @@ -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='