| <?php␊ |
| /* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */␊ |
| /*␊ |
| # ***** BEGIN LICENSE BLOCK *****␊ |
| # This file is part of InDefero, an open source project management application.␊ |
| # Copyright (C) 2008-2011 CĂ©ondo Ltd and contributors.␊ |
| #␊ |
| # InDefero is free software; you can redistribute it and/or modify␊ |
| # it under the terms of the GNU General Public License as published by␊ |
| # the Free Software Foundation; either version 2 of the License, or␊ |
| # (at your option) any later version.␊ |
| #␊ |
| # InDefero is distributed in the hope that it will be useful,␊ |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of␊ |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the␊ |
| # GNU General Public License for more details.␊ |
| #␊ |
| # You should have received a copy of the GNU General Public License␊ |
| # along with this program; if not, write to the Free Software␊ |
| # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA␊ |
| #␊ |
| # ***** END LICENSE BLOCK ***** */␊ |
| ␊ |
| /**␊ |
| * Update an issue.␊ |
| *␊ |
| * We extend IDF_Form_IssueCreate to reuse the validation logic.␊ |
| */␊ |
| class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate␊ |
| {␊ |
| public $issue = null;␊ |
| ␊ |
| public function initFields($extra=array())␊ |
| {␊ |
| $this->user = $extra['user'];␊ |
| $this->project = $extra['project'];␊ |
| $this->issue = $extra['issue'];␊ |
| if ($this->user->hasPerm('IDF.project-owner', $this->project)␊ |
| or $this->user->hasPerm('IDF.project-member', $this->project)) {␊ |
| $this->show_full = true;␊ |
| }␊ |
| $this->relation_types = $this->project->getRelationsFromConfig();␊ |
| 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,␊ |
| ),␊ |
| ));␊ |
| $upload_path = Pluf::f('upload_issue_path', false);␊ |
| if (false === $upload_path) {␊ |
| throw new Pluf_Exception_SettingError(__('The "upload_issue_path" configuration variable was not set.'));␊ |
| }␊ |
| $md5 = md5(rand().microtime().Pluf_Utils::getRandomString());␊ |
| // We add .dummy to try to mitigate security issues in the␊ |
| // case of someone allowing the upload path to be accessible␊ |
| // to everybody.␊ |
| for ($i=1;$i<4;$i++) {␊ |
| $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';␊ |
| $this->fields['attachment'.$i] = new Pluf_Form_Field_File(␊ |
| array('required' => false,␊ |
| 'label' => __('Attach a file'),␊ |
| 'move_function_params' =>␊ |
| array('upload_path' => $upload_path,␊ |
| 'upload_path_create' => true,␊ |
| 'file_name' => $filename,␊ |
| )␊ |
| )␊ |
| );␊ |
| }␊ |
| ␊ |
| if ($this->show_full) {␊ |
| $this->fields['status'] = new Pluf_Form_Field_Varchar(␊ |
| array('required' => true,␊ |
| 'label' => __('Status'),␊ |
| 'initial' => $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,␊ |
| ),␊ |
| ));␊ |
| <<<<<<< HEAD␊ |
| $this->fields['due_dtime'] = new Pluf_Form_Field_Date(␊ |
| =======␊ |
| $due_dtime = substr($this->issue->due_dtime, 0, 10);␊ |
| $this->fields['due_dtime'] = new IDF_Form_Field_Date(␊ |
| >>>>>>> 6996e1185fec6ff4735beab7ccd2267bf1dccac2␊ |
| array('required' => false,␊ |
| 'label' => __('Due date'),␊ |
| 'initial' => $due_dtime,␊ |
| 'widget_attrs' => array('size' => 15,),␊ |
| ));␊ |
| ␊ |
| $idx = 0;␊ |
| // note: clean_relation_type0 and clean_relation_issue0 already␊ |
| // exist in the base class␊ |
| $this->fields['relation_type'.$idx] = new Pluf_Form_Field_Varchar(␊ |
| array('required' => false,␊ |
| 'label' => __('This issue'),␊ |
| 'initial' => current($this->relation_types),␊ |
| 'widget_attrs' => array('size' => 15),␊ |
| ));␊ |
| ␊ |
| $this->fields['relation_issue'.$idx] = new Pluf_Form_Field_Varchar(␊ |
| array('required' => false,␊ |
| 'label' => null,␊ |
| 'initial' => '',␊ |
| 'widget_attrs' => array('size' => 10),␊ |
| ));␊ |
| ␊ |
| ++$idx;␊ |
| $relatedIssues = $this->issue->getGroupedRelatedIssues(array(), true);␊ |
| foreach ($relatedIssues as $verb => $ids) {␊ |
| $this->fields['relation_type'.$idx] = new Pluf_Form_Field_Varchar(␊ |
| array('required' => false,␊ |
| 'label' => __('This issue'),␊ |
| 'initial' => $verb,␊ |
| 'widget_attrs' => array('size' => 15),␊ |
| ));␊ |
| $m = 'clean_relation_type'.$idx;␊ |
| $this->$m = create_function('$form', '␊ |
| return $form->clean_relation_type($form->cleaned_data["relation_type'.$idx.'"]);␊ |
| ');␊ |
| ␊ |
| $this->fields['relation_issue'.$idx] = new Pluf_Form_Field_Varchar(␊ |
| array('required' => false,␊ |
| 'label' => null,␊ |
| 'initial' => implode(', ', $ids),␊ |
| 'widget_attrs' => array('size' => 10),␊ |
| ));␊ |
| $m = 'clean_relation_issue'.$idx;␊ |
| $this->$m = create_function('$form', '␊ |
| return $form->clean_relation_issue($form->cleaned_data["relation_issue'.$idx.'"]);␊ |
| ');␊ |
| ␊ |
| ++$idx;␊ |
| }␊ |
| ␊ |
| $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,␊ |
| ),␊ |
| ));␊ |
| }␊ |
| }␊ |
| }␊ |
| ␊ |
| /**␊ |
| * Clean the attachments post failure.␊ |
| */␊ |
| function failed()␊ |
| {␊ |
| $upload_path = Pluf::f('upload_issue_path', false);␊ |
| if ($upload_path == false) return;␊ |
| for ($i=1;$i<4;$i++) {␊ |
| if (!empty($this->cleaned_data['attachment'.$i]) and␊ |
| file_exists($upload_path.'/'.$this->cleaned_data['attachment'.$i])) {␊ |
| @unlink($upload_path.'/'.$this->cleaned_data['attachment'.$i]);␊ |
| }␊ |
| }␊ |
| }␊ |
| ␊ |
| function clean_content()␊ |
| {␊ |
| $content = trim($this->cleaned_data['content']);␊ |
| if (!$this->show_full and strlen($content) == 0) {␊ |
| throw new Pluf_Form_Invalid(__('You need to provide a description of the issue.'));␊ |
| }␊ |
| return $content;␊ |
| }␊ |
| ␊ |
| /**␊ |
| * We check that something is really changed.␊ |
| */␊ |
| public function clean()␊ |
| {␊ |
| $this->cleaned_data = parent::clean();␊ |
| ␊ |
| // normalize the user's input by removing dublettes and by combining␊ |
| // ids from identical verbs in different input fields into one array␊ |
| $normRelatedIssues = array();␊ |
| for ($idx = 0; isset($this->cleaned_data['relation_type'.$idx]); ++$idx) {␊ |
| $verb = $this->cleaned_data['relation_type'.$idx];␊ |
| if (empty($verb))␊ |
| continue;␊ |
| ␊ |
| $ids = preg_split('/\s*,\s*/', $this->cleaned_data['relation_issue'.$idx],␊ |
| -1, PREG_SPLIT_NO_EMPTY);␊ |
| if (count($ids) == 0)␊ |
| continue;␊ |
| ␊ |
| if (!array_key_exists($verb, $normRelatedIssues))␊ |
| $normRelatedIssues[$verb] = array();␊ |
| foreach ($ids as $id) {␊ |
| if (!in_array($id, $normRelatedIssues[$verb]))␊ |
| $normRelatedIssues[$verb][] = $id;␊ |
| }␊ |
| }␊ |
| ␊ |
| // now look at any added / removed ids␊ |
| $added = $removed = array();␊ |
| $relatedIssues = $this->issue->getGroupedRelatedIssues(array(), true);␊ |
| $added = array_diff_key($normRelatedIssues, $relatedIssues);␊ |
| $removed = array_diff_key($relatedIssues, $normRelatedIssues);␊ |
| ␊ |
| $keysToLookAt = array_keys(␊ |
| array_intersect_key($relatedIssues, $normRelatedIssues)␊ |
| );␊ |
| foreach ($keysToLookAt as $key) {␊ |
| $a = array_diff($normRelatedIssues[$key], $relatedIssues[$key]);␊ |
| if (count($a) > 0)␊ |
| $added[$key] = $a;␊ |
| $r = array_diff($relatedIssues[$key], $normRelatedIssues[$key]);␊ |
| if (count($r) > 0)␊ |
| $removed[$key] = $r;␊ |
| }␊ |
| ␊ |
| // cache the added / removed data, so we do not have to␊ |
| // calculate that again␊ |
| $this->cleaned_data['_added_issue_relations'] = $added;␊ |
| $this->cleaned_data['_removed_issue_relations'] = $removed;␊ |
| ␊ |
| // 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;␊ |
| }␊ |
| if (trim($this->issue->due_dtime) != trim($this->cleaned_data['due_dtime'])) {␊ |
| 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;␊ |
| }␊ |
| }␊ |
| ␊ |
| if (count($this->cleaned_data['_added_issue_relations']) != 0 ||␊ |
| count($this->cleaned_data['_removed_issue_relations']) != 0) {␊ |
| 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()) {␊ |
| throw new Exception(__('Cannot save the model from an invalid form.'));␊ |
| }␊ |
| if ($this->show_full) {␊ |
| // Add a tag for each label␊ |
| $tags = array();␊ |
| $tagids = array();␊ |
| for ($i=1;$i<7;$i++) {␊ |
| if (strlen($this->cleaned_data['label'.$i]) > 0) {␊ |
| if (strpos($this->cleaned_data['label'.$i], ':') !== false) {␊ |
| list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);␊ |
| list($class, $name) = array(trim($class), trim($name));␊ |
| } else {␊ |
| $class = 'Other';␊ |
| $name = trim($this->cleaned_data['label'.$i]);␊ |
| }␊ |
| $tag = IDF_Tag::add($name, $this->project, $class);␊ |
| $tags[] = $tag;␊ |
| $tagids[] = $tag->id;␊ |
| }␊ |
| }␊ |
| // Compare between the old and the new data␊ |
| $changes = array();␊ |
| $oldtags = $this->issue->get_tags_list();␊ |
| foreach ($tags as $tag) {␊ |
| if (!Pluf_Model_InArray($tag, $oldtags)) {␊ |
| if (!isset($changes['lb'])) $changes['lb'] = array();␊ |
| if (!isset($changes['lb']['add'])) $changes['lb']['add'] = array();␊ |
| if ($tag->class != 'Other') {␊ |
| $changes['lb']['add'][] = (string) $tag; //new tag␊ |
| } else {␊ |
| $changes['lb']['add'][] = (string) $tag->name;␊ |
| }␊ |
| }␊ |
| }␊ |
| foreach ($oldtags as $tag) {␊ |
| if (!Pluf_Model_InArray($tag, $tags)) {␊ |
| if (!isset($changes['lb'])) $changes['lb'] = array();␊ |
| if (!isset($changes['lb']['rem'])) $changes['lb']['rem'] = array();␊ |
| if ($tag->class != 'Other') {␊ |
| $changes['lb']['rem'][] = (string) $tag; //new tag␊ |
| } else {␊ |
| $changes['lb']['rem'][] = (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;␊ |
| }␊ |
| if (trim($this->issue->due_dtime) != trim($this->cleaned_data['due_dtime'])) {␊ |
| $changes['du'] = Pluf_Template_dateFormat($this->cleaned_data['due_dtime']);␊ |
| }␊ |
| // Issue relations - additions␊ |
| foreach ($this->cleaned_data['_added_issue_relations'] as $verb => $ids) {␊ |
| $other_verb = $this->relation_types[$verb];␊ |
| foreach ($ids as $id) {␊ |
| $related_issue = new IDF_Issue($id);␊ |
| $rel = new IDF_IssueRelation();␊ |
| $rel->issue = $this->issue;␊ |
| $rel->verb = $verb;␊ |
| $rel->other_issue = $related_issue;␊ |
| $rel->submitter = $this->user;␊ |
| $rel->create();␊ |
| ␊ |
| $other_rel = new IDF_IssueRelation();␊ |
| $other_rel->issue = $related_issue;␊ |
| $other_rel->verb = $other_verb;␊ |
| $other_rel->other_issue = $this->issue;␊ |
| $other_rel->submitter = $this->user;␊ |
| $other_rel->create();␊ |
| }␊ |
| if (!isset($changes['rel'])) $changes['rel'] = array();␊ |
| if (!isset($changes['rel']['add'])) $changes['rel']['add'] = array();␊ |
| $changes['rel']['add'][] = $verb.' '.implode(', ', $ids);␊ |
| }␊ |
| // Issue relations - removals␊ |
| foreach ($this->cleaned_data['_removed_issue_relations'] as $verb => $ids) {␊ |
| foreach ($ids as $id) {␊ |
| $db = &Pluf::db();␊ |
| $table = Pluf::factory('IDF_IssueRelation')->getSqlTable();␊ |
| $sql = new Pluf_SQL('verb=%s AND (␊ |
| (issue=%s AND other_issue=%s) OR␊ |
| (other_issue=%s AND issue=%s))',␊ |
| array($verb,␊ |
| $this->issue->id, $id,␊ |
| $this->issue->id, $id));␊ |
| $db->execute('DELETE FROM '.$table.' WHERE '.$sql->gen());␊ |
| }␊ |
| ␊ |
| if (!isset($changes['rel'])) $changes['rel'] = array();␊ |
| if (!isset($changes['rel']['rem'])) $changes['rel']['rem'] = array();␊ |
| $changes['rel']['rem'][] = $verb.' '.implode(', ', $ids);␊ |
| }␊ |
| // 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;␊ |
| $this->issue->due_dtime = $this->cleaned_data['due_dtime'];␊ |
| }␊ |
| // Create the comment␊ |
| $comment = new IDF_IssueComment();␊ |
| $comment->issue = $this->issue;␊ |
| $comment->content = $this->cleaned_data['content'];␊ |
| $comment->submitter = $this->user;␊ |
| if (!$this->show_full) $changes = array();␊ |
| $comment->changes = $changes;␊ |
| $comment->create();␊ |
| $this->issue->update();␊ |
| if ($this->issue->owner != $this->user->id and␊ |
| $this->issue->submitter != $this->user->id) {␊ |
| $this->issue->setAssoc($this->user); // interested user.␊ |
| }␊ |
| $attached_files = array();␊ |
| for ($i=1;$i<4;$i++) {␊ |
| if ($this->cleaned_data['attachment'.$i]) {␊ |
| $file = new IDF_IssueFile();␊ |
| $file->attachment = $this->cleaned_data['attachment'.$i];␊ |
| $file->submitter = $this->user;␊ |
| $file->comment = $comment;␊ |
| $file->create();␊ |
| $attached_files[] = $file;␊ |
| }␊ |
| }␊ |
| /**␊ |
| * [signal]␊ |
| *␊ |
| * IDF_Issue::update␊ |
| *␊ |
| * [sender]␊ |
| *␊ |
| * IDF_Form_IssueUpdate␊ |
| *␊ |
| * [description]␊ |
| *␊ |
| * This signal allows an application to perform a set of tasks␊ |
| * just after the update of an issue.␊ |
| *␊ |
| * [parameters]␊ |
| *␊ |
| * array('issue' => $issue,␊ |
| * 'comment' => $comment,␊ |
| * 'files' => $attached_files);␊ |
| *␊ |
| */␊ |
| $params = array('issue' => $this->issue,␊ |
| 'comment' => $comment,␊ |
| 'files' => $attached_files);␊ |
| Pluf_Signal::send('IDF_Issue::update', 'IDF_Form_IssueUpdate',␊ |
| $params);␊ |
| ␊ |
| return $this->issue;␊ |
| }␊ |
| }␊ |