From ac286371e68703953ab0969457d0fd7ac52f5ff1 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Tue, 29 Dec 2015 10:19:03 +0000 Subject: [PATCH] added partial changes to support attachments in issue management --- codepot/Makefile.am | 2 + codepot/README | 2 + codepot/codepot.spec.in | 1 + codepot/etc/codepot.ini.in | 5 + codepot/etc/codepot.mysql | 23 +- codepot/src/codepot/controllers/issue.php | 107 +++++++ codepot/src/codepot/models/issuemodel.php | 151 +++++++++- codepot/src/codepot/views/issue_home.php | 352 ++++++++++++++++++---- codepot/src/config.php.in | 1 + codepot/src/css/common.css | 22 +- codepot/src/css/file.css | 31 ++ codepot/src/css/issue.css | 31 ++ 12 files changed, 655 insertions(+), 73 deletions(-) diff --git a/codepot/Makefile.am b/codepot/Makefile.am index db9ccd65..3d94c4e9 100644 --- a/codepot/Makefile.am +++ b/codepot/Makefile.am @@ -11,6 +11,7 @@ install-data-hook: $(INSTALL) -d "$(DESTDIR)@DEPOTDIR@" $(INSTALL) -d "$(DESTDIR)@DEPOTDIR@/svnrepo" $(INSTALL) -d "$(DESTDIR)@DEPOTDIR@/files" + $(INSTALL) -d "$(DESTDIR)@DEPOTDIR@/issuefiles" $(INSTALL) -d "$(DESTDIR)@DEPOTDIR@/attachments" $(INSTALL) -d "$(DESTDIR)@DEPOTDIR@/usericons" $(INSTALL) -d "$(DESTDIR)@LOGDIR@" @@ -19,6 +20,7 @@ install-data-hook: uninstall-hook: $(RMDIR) "$(DESTDIR)@DEPOTDIR@/usericons" $(RMDIR) "$(DESTDIR)@DEPOTDIR@/attachments" + $(RMDIR) "$(DESTDIR)@DEPOTDIR@/issuefiles" $(RMDIR) "$(DESTDIR)@DEPOTDIR@/files" $(RMDIR) "$(DESTDIR)@DEPOTDIR@/svnrepo" $(RMDIR) "$(DESTDIR)@DEPOTDIR@" diff --git a/codepot/README b/codepot/README index cafd1bda..1f1cf428 100644 --- a/codepot/README +++ b/codepot/README @@ -22,6 +22,8 @@ UPGRADING FROM 0.2.0 mysql> ALTER TABLE file DROP COLUMN md5sum; mysql> ALTER TABLE file DROP COLUMN encname; mysql> ALTER TABLE project ADD COLUMN (codecharset VARCHAR(32)); + mysql> DROP TABLE issue_attachment; + mysql> create the issue_file_list table according to the definition found in codepot.mysql INSTALLATION ON CENTOS diff --git a/codepot/codepot.spec.in b/codepot/codepot.spec.in index 2234eec8..0a8c519d 100644 --- a/codepot/codepot.spec.in +++ b/codepot/codepot.spec.in @@ -85,6 +85,7 @@ rm -rf $RPM_BUILD_ROOT %dir %attr(-,apache,apache) /var/lib/codepot/svnrepo %dir %attr(-,apache,apache) /var/lib/codepot/files +%dir %attr(-,apache,apache) /var/lib/codepot/issuefiles %dir %attr(-,apache,apache) /var/lib/codepot/attachments %dir %attr(-,apache,apache) /var/lib/codepot/usericons %dir %attr(-,apache,apache) /var/log/codepot diff --git a/codepot/etc/codepot.ini.in b/codepot/etc/codepot.ini.in index 7c7e4aea..e8c7cb44 100644 --- a/codepot/etc/codepot.ini.in +++ b/codepot/etc/codepot.ini.in @@ -186,6 +186,11 @@ svnrepo_dir = "@DEPOTDIR@/svnrepo" ;------------------------------------------------------------------------------ file_dir = "@DEPOTDIR@/files" +;------------------------------------------------------------------------------ +; directory to store issue attachments +;------------------------------------------------------------------------------ +issue_file_dir = "@DEPOTDIR@/issuefiles" + ;------------------------------------------------------------------------------ ; directory to store wiki attachments ;------------------------------------------------------------------------------ diff --git a/codepot/etc/codepot.mysql b/codepot/etc/codepot.mysql index 97333082..befeda3e 100644 --- a/codepot/etc/codepot.mysql +++ b/codepot/etc/codepot.mysql @@ -99,21 +99,24 @@ CREATE TABLE issue ( ON DELETE RESTRICT ON UPDATE CASCADE ) charset=utf8 engine=InnoDB; -CREATE TABLE issue_attachment ( - projectid VARCHAR(32) NOT NULL, - issueid BIGINT NOT NULL, - name VARCHAR(255) NOT NULL, - encname VARCHAR(255) NOT NULL, +CREATE TABLE issue_file_list ( + projectid VARCHAR(32) NOT NULL, + issueid BIGINT NOT NULL, + filename VARCHAR(255) NOT NULL, + encname VARCHAR(255) NOT NULL, + md5sum CHAR(32) NOT NULL, + description VARCHAR(255) NOT NULL, - createdon DATETIME NOT NULL, - createdby VARCHAR(32) NOT NULL, + createdon DATETIME NOT NULL, + createdby VARCHAR(32) NOT NULL, - UNIQUE KEY issue_attachment_id (projectid, issueid, name), + UNIQUE KEY issue_file_list_id (projectid, issueid, filename), + UNIQUE KEY (encname), - CONSTRAINT issue_attachment_projectid FOREIGN KEY (projectid) REFERENCES project(id) + CONSTRAINT issue_file_list_projectid FOREIGN KEY (projectid) REFERENCES project(id) ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT issue_attachment_issueid FOREIGN KEY (projectid,issueid) REFERENCES issue(projectid,id) + CONSTRAINT issue_file_list_issueid FOREIGN KEY (projectid,issueid) REFERENCES issue(projectid,id) ON DELETE RESTRICT ON UPDATE CASCADE ) charset=utf8 engine=InnoDB; diff --git a/codepot/src/codepot/controllers/issue.php b/codepot/src/codepot/controllers/issue.php index 79182cb6..a382bb56 100644 --- a/codepot/src/codepot/controllers/issue.php +++ b/codepot/src/codepot/controllers/issue.php @@ -517,4 +517,111 @@ class Issue extends Controller } } + function xhr_create ($projectid = '') + { + $this->load->model ('ProjectModel', 'projects'); + $this->load->model ('IssueModel', 'issues'); + $this->load->library ('upload'); + + $login = $this->login->getUser (); + $revision_saved = -1; + + if ($login['id'] == '') + { + $status = 'error - anonymous user'; + } + else + { + $project = $this->projects->get ($projectid); + if ($project === FALSE) + { + $status = "error - failed to get the project {$projectid}"; + } + else if ($project === NULL) + { + $status = "error - no such project {$projectid}"; + } + else if (!$login['sysadmin?'] && + $this->projects->projectHasMember($projectid, $login['id']) === FALSE) + { + $status = "error - not a member {$login['id']}"; + } + else + { + $issue = new stdClass(); + $issue->projectid = $projectid; + $issue->summary = $this->input->post('issue_new_summary'); + $issue->description = $this->input->post('issue_new_description'); + $issue->type = $this->input->post('issue_new_type'); + $issue->status = $this->issuehelper->STATUS_NEW; + $issue->priority = $this->issuehelper->PRIORITY_OTHER; + if ($this->projects->projectHasMember($project->id, $login['id'])) + { + // let the current user be the issue owner if he/she is a + // project memeber. + $issue->owner = $login['id']; + } + else + { + // if not, assign the issue to the first member. + $issue->owner = (count($project->members) > 0)? $project->members[0]: ''; + } + + $post_new_file_count = $this->input->post('issue_new_file_count'); + + if ($issue->type === FALSE || ($issue->type = trim($issue->type)) == '') + { + $status = 'error - no type'; + } + else if ($issue->summary === FALSE || ($issue->summary = trim($issue->summary)) == '') + { + $status = 'error - no name'; + } + else if ($issue->description === FALSE || ($issue->description = $issue->description) == '') + { + $status = 'error - no description'; + } + else + { + if ($post_new_file_count === FALSE || $post_new_file_count <= 0) $post_new_file_count = 0; + + $status = ''; + $attached_files = array (); + for ($i = 0; $i < $post_new_file_count; $i++) + { + $fid = "issue_new_file_{$i}"; + if (array_key_exists($fid, $_FILES) && $_FILES[$fid]['name'] != '') + { + $d = $this->input->post("issue_new_file_desc_{$i}"); + if ($d === FALSE || ($d = trim($d)) == '') $d = ''; // description optional + + if (strpos($_FILES[$fid]['name'], ':') !== FALSE || + strpos($_FILES[$fid]['name'], '/') !== FALSE) + { + // prevents these letters for wiki creole + $status = "error - colon or slash not allowed - {$_FILES[$fid]['name']}"; + break; + } + + array_push ($attached_files, array ('fid' => $fid, 'name' => $_FILES[$fid]['name'], 'desc' => $d)); + } + } + + if ($status == '') + { + if ($this->issues->create_with_files ($login['id'], $issue, $attached_files, $this->upload) === FALSE) + { + $status = 'error - ' . $this->issues->getErrorMessage(); + } + else + { + $status = 'ok'; + } + } + } + } + } + + print $status; + } } diff --git a/codepot/src/codepot/models/issuemodel.php b/codepot/src/codepot/models/issuemodel.php index a6955226..c6a44951 100644 --- a/codepot/src/codepot/models/issuemodel.php +++ b/codepot/src/codepot/models/issuemodel.php @@ -2,6 +2,18 @@ class IssueModel extends Model { + protected $errmsg = ''; + + function capture_error ($errno, $errmsg) + { + $this->errmsg = $errmsg; + } + + function getErrorMessage () + { + return $this->errmsg; + } + function IssueModel () { parent::Model (); @@ -184,7 +196,7 @@ class IssueModel extends Model $this->db->set ('projectid', $issue->projectid); $this->db->set ('userid', $userid); $this->db->set ('message', $newid); - $this->db->insert ('log'); + $this->db->insert ('log'); $this->db->trans_complete (); if ($this->db->trans_status() === FALSE) return FALSE; @@ -405,6 +417,143 @@ class IssueModel extends Model return $this->db->trans_status(); } + private function delete_all_files ($files) + { + foreach ($files as $f) @unlink ($f); + } + + private function _create_issue ($userid, $issue, $attached_files, $uploader) + { + $this->db->trans_begin (); // manual transaction. not using trans_start(). + + $this->db->where ('projectid', $issue->projectid); + $this->db->select ('MAX(id) as maxid'); + $query = $this->db->get ('issue'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $result = $query->result(); + $maxid = (empty($result) || $result[0] == NULL)? 0: $result[0]->maxid; + + $newid = $maxid + 1; + + $this->db->set ('projectid', $issue->projectid); + $this->db->set ('id', $newid); + $this->db->set ('summary', $issue->summary); + $this->db->set ('description', $issue->description); + $this->db->set ('type', $issue->type); + $this->db->set ('status', $issue->status); + $this->db->set ('owner', $issue->owner); + $this->db->set ('priority', $issue->priority); + $this->db->set ('createdon', date('Y-m-d H:i:s')); + $this->db->set ('updatedon', date('Y-m-d H:i:s')); + $this->db->set ('createdby', $userid); + $this->db->set ('updatedby', $userid); + $this->db->insert ('issue'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $this->db->set ('projectid', $issue->projectid); + $this->db->set ('id', $newid); + $this->db->set ('sno', 1); + $this->db->set ('type', $issue->type); + $this->db->set ('status', $issue->status); + $this->db->set ('owner', $issue->owner); + $this->db->set ('comment', ''); + $this->db->set ('priority', $issue->priority); + $this->db->set ('updatedon', date('Y-m-d H:i:s')); + $this->db->set ('updatedby', $userid); + $this->db->insert ('issue_change'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $config['allowed_types'] = '*'; + $config['upload_path'] = CODEPOT_ISSUE_FILE_DIR; + $config['max_size'] = CODEPOT_MAX_UPLOAD_SIZE; + $config['encrypt_name'] = TRUE; + $config['overwrite'] = FALSE; + $config['remove_spaces'] = FALSE; + $uploader->initialize ($config); + + $ok_files = array(); + $file_count = count($attached_files); + for ($i = 0; $i < $file_count; $i++) + { + $f = $attached_files[$i]; + if (!$uploader->do_upload($f['fid'])) + { + $this->errmsg = "Failed to upload {$f['name']}"; + $this->db->trans_rollback (); + $this->delete_all_files ($ok_files); + return FALSE; + } + + $ud = $uploader->data(); + array_push ($ok_files, $ud['full_path']); + + $md5sum = @md5_file ($ud['full_path']); + if ($md5sum === FALSE) + { + $this->db->trans_rollback (); + $this->delete_all_files ($ok_files); + return FALSE; + } + + $this->db->set ('projectid', $issue->projectid); + $this->db->set ('issueid', $newid); + $this->db->set ('filename', $f['name']); + $this->db->set ('encname', $ud['file_name']); + $this->db->set ('description', $f['desc']); + $this->db->set ('md5sum', $md5sum); + $this->db->insert ('issue_file_list'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + $this->delete_all_files ($ok_files); + return FALSE; + } + } + + $this->db->set ('createdon', date('Y-m-d H:i:s')); + $this->db->set ('type', 'issue'); + $this->db->set ('action', 'create'); + $this->db->set ('projectid', $issue->projectid); + $this->db->set ('userid', $userid); + $this->db->set ('message', $newid); + $this->db->insert ('log'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + $this->delete_all_files ($ok_files); + return FALSE; + } + + $this->db->trans_commit (); + return $newid; + } + + function create_with_files ($userid, $issue, $attached_files, $uploader) + { + set_error_handler (array ($this, 'capture_error')); + $errmsg = ''; + $x = $this->_create_issue ($userid, $issue, $attached_files, $uploader); + restore_error_handler (); + return $x; + } } ?> diff --git a/codepot/src/codepot/views/issue_home.php b/codepot/src/codepot/views/issue_home.php index a11732ed..a02ba245 100644 --- a/codepot/src/codepot/views/issue_home.php +++ b/codepot/src/codepot/views/issue_home.php @@ -10,12 +10,37 @@ + + + + + + + + +