diff --git a/src/IDF/Form/IssueUpdate.php b/src/IDF/Form/IssueUpdate.php index 6d7b12b..79245ed 100644 --- a/src/IDF/Form/IssueUpdate.php +++ b/src/IDF/Form/IssueUpdate.php @@ -103,8 +103,8 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate 'size' => 15, ), )); - $this->fields['due_dtime'] = new Pluf_Form_Field_Date( $due_dtime = substr($this->issue->due_dtime, 0, 10); + $this->fields['due_dtime'] = new Pluf_Form_Field_Date( array('required' => false, 'label' => __('Due date'), 'initial' => $due_dtime, diff --git a/src/IDF/Form/IssueUpdate.php.orig b/src/IDF/Form/IssueUpdate.php.orig new file mode 100644 index 0000000..27b6409 --- /dev/null +++ b/src/IDF/Form/IssueUpdate.php.orig @@ -0,0 +1,506 @@ +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; + } +}