From e409324188c334ebfd4bcd9abf2245f85ef94060 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Tue, 29 Dec 2015 16:52:09 +0000 Subject: [PATCH] improved the issue show view to manipulate attached files --- codepot/etc/codepot.oracle | 21 +- codepot/etc/codepot.pgsql | 21 ++ codepot/src/codepot/controllers/issue.php | 225 ++++++++++- codepot/src/codepot/models/filemodel.php | 1 - codepot/src/codepot/models/issuemodel.php | 277 +++++++++++++- codepot/src/codepot/views/file_show.php | 34 +- codepot/src/codepot/views/issue_home.php | 3 +- codepot/src/codepot/views/issue_show.php | 433 +++++++++++++++++++++- 8 files changed, 976 insertions(+), 39 deletions(-) diff --git a/codepot/etc/codepot.oracle b/codepot/etc/codepot.oracle index 4cde82d4..9b4a5c12 100644 --- a/codepot/etc/codepot.oracle +++ b/codepot/etc/codepot.oracle @@ -103,14 +103,17 @@ CREATE TABLE "cpot_issue" ( CREATE INDEX cpot_issue_index_1 ON "cpot_issue"("projectid", "status", "type", "summary"); CREATE INDEX cpot_issue_index_2 ON "cpot_issue"("projectid", "summary"); -CREATE TABLE "cpot_issue_attachment" ( - "projectid" VARCHAR(32) NOT NULL, - "issueid" NUMBER(20,0) NOT NULL, - "name" VARCHAR(255) NOT NULL, - "encname" VARCHAR(255) NOT NULL, - "createdon" TIMESTAMP NOT NULL, - "createdby" VARCHAR(32) NOT NULL, - UNIQUE ("projectid", "issueid", "name"), +CREATE TABLE "cpot_issue_file_list" ( + "projectid" VARCHAR(32) NOT NULL, + "issueid" NUMBER(20,0) NOT NULL, + "filename" VARCHAR(255) NOT NULL, + "encname" VARCHAR(255) NOT NULL, + "md5sum" CHAR(32) NOT NULL, + "description" CLOB NOT NULL, + "createdon" TIMESTAMP NOT NULL, + "createdby" VARCHAR(32) NOT NULL, + UNIQUE ("projectid", "issueid", "filename"), + UNIQUE ("encname"), CONSTRAINT issue_attachment_projectid FOREIGN KEY ("projectid") REFERENCES "cpot_project"("id"), CONSTRAINT issue_attachment_issueid FOREIGN KEY ("projectid","issueid") REFERENCES "cpot_issue"("projectid","id") ); @@ -242,7 +245,7 @@ END; CREATE OR REPLACE TRIGGER cpot_upon_issue_id_update AFTER UPDATE OF "id" ON "cpot_issue" FOR EACH ROW BEGIN - UPDATE "cpot_issue_attachment" SET "issueid" = :new."id" WHERE "projectid" = :old."projectid" AND "issueid" = :old."id"; + UPDATE "cpot_issue_file_list" SET "issueid" = :new."id" WHERE "projectid" = :old."projectid" AND "issueid" = :old."id"; UPDATE "cpot_issue_change" SET "id" = :new."id" WHERE "projectid" = :old."projectid" AND "id" = :old."id"; UPDATE "cpot_issue_change_attachment" SET "issueid" = :new."id" WHERE "projectid" = :old."projectid" AND "issueid" = :old."id"; END; diff --git a/codepot/etc/codepot.pgsql b/codepot/etc/codepot.pgsql index 73d8b7bb..4a629774 100644 --- a/codepot/etc/codepot.pgsql +++ b/codepot/etc/codepot.pgsql @@ -135,6 +135,27 @@ CREATE TABLE issue_attachment ( ON DELETE RESTRICT ON UPDATE CASCADE ); +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, + + UNIQUE (projectid, issueid, filename), + UNIQUE (encname), + + CONSTRAINT issue_file_list_projectid FOREIGN KEY (projectid) REFERENCES project(id) + ON DELETE RESTRICT ON UPDATE CASCADE, + + CONSTRAINT issue_file_list_issueid FOREIGN KEY (projectid,issueid) REFERENCES issue(projectid,id) + ON DELETE RESTRICT ON UPDATE CASCADE +); + CREATE TABLE issue_change ( projectid VARCHAR(32) NOT NULL, id BIGINT NOT NULL, diff --git a/codepot/src/codepot/controllers/issue.php b/codepot/src/codepot/controllers/issue.php index 5b8b887f..92764bae 100644 --- a/codepot/src/codepot/controllers/issue.php +++ b/codepot/src/codepot/controllers/issue.php @@ -257,6 +257,8 @@ class Issue extends Controller } } +/* +DEPRECATED function _edit_issue ($projectid, $hexid, $mode) { $this->load->helper ('form'); @@ -414,6 +416,7 @@ class Issue extends Controller return $this->_edit_issue ($projectid, $hexid, 'update'); } + function delete ($projectid = '', $hexid = '') { $this->load->helper ('form'); @@ -516,6 +519,7 @@ class Issue extends Controller } } +*/ function xhr_create ($projectid = '') { @@ -609,7 +613,7 @@ class Issue extends Controller if ($status == '') { - if ($this->issues->create_with_files ($login['id'], $issue, $attached_files, $this->upload) === FALSE) + if ($this->issues->createWithFiles ($login['id'], $issue, $attached_files, $this->upload) === FALSE) { $status = 'error - ' . $this->issues->getErrorMessage(); } @@ -681,7 +685,7 @@ class Issue extends Controller if ($status == '') { - if ($this->issues->update_summary_and_description ($login['id'], $issue) === FALSE) + if ($this->issues->updateSummaryAndDescription ($login['id'], $issue) === FALSE) { $status = 'error - ' . $this->issues->getErrorMessage(); } @@ -697,6 +701,223 @@ class Issue extends Controller print $status; } + function xhr_delete ($projectid = '', $issueid = '') + { + $this->load->model ('ProjectModel', 'projects'); + $this->load->model ('IssueModel', 'issues'); + + $login = $this->login->getUser (); + + if ($login['id'] == '') + { + $status = 'error - anonymous user'; + } + else + { + $issueid = $this->converter->HexToAscii ($issueid); + + $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 + { + $post_delete_confirm = $this->input->post('issue_delete_confirm'); + + if ($post_delete_confirm !== FALSE && $post_delete_confirm == 'Y') + { + if ($this->issues->deleteWithFiles ($login['id'], $projectid, $issueid) === FALSE) + { + $status = 'error - ' . $this->issues->getErrorMessage(); + } + else + { + $status = 'ok'; + } + } + else + { + $status = 'error - not confirmed'; + } + } + } + + print $status; + } + + function xhr_add_file ($projectid = '', $issueid = '') + { + $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 + { + $issueid = $this->converter->HexToAscii ($issueid); + + $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 + { + $post_add_file_count = $this->input->post('issue_add_file_count'); + if ($post_add_file_count === FALSE || $post_add_file_count <= 0) $post_add_file_count = 0; + + $status = ''; + $add_files = array (); + for ($i = 0; $i < $post_add_file_count; $i++) + { + $fid = "issue_add_file_{$i}"; + if (array_key_exists($fid, $_FILES) && $_FILES[$fid]['name'] != '') + { + $d = $this->input->post("file_add_file_desc_{$i}"); + if ($d === FALSE || ($d = trim($d)) == '') $d = ''; + + 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 ($add_files, array ('fid' => $fid, 'name' => $_FILES[$fid]['name'], 'desc' => $d)); + } + } + + if ($status == '') + { + if (count($add_files) <= 0) + { + $status = 'error - no files uploaded'; + } + else if ($this->issues->addFiles ($login['id'], $projectid, $issueid, $add_files, $this->upload) === FALSE) + { + $status = 'error - ' . $this->issues->getErrorMessage(); + } + else + { + $status = 'ok'; + } + } + } + } + + print $status; + } + + function xhr_edit_file ($projectid = '', $issueid = '') + { + $this->load->model ('ProjectModel', 'projects'); + $this->load->model ('IssueModel', 'issues'); + + $login = $this->login->getUser (); + $revision_saved = -1; + + if ($login['id'] == '') + { + $status = 'error - anonymous user'; + } + else + { + $issueid = $this->converter->HexToAscii ($issueid); + + $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 + { + $post_edit_file_count = $this->input->post('issue_edit_file_count'); + if ($post_edit_file_count === FALSE || $post_edit_file_count <= 0) $post_edit_file_count = 0; + + $status = ''; + $edit_files = array (); + for ($i = 0; $i < $post_edit_file_count; $i++) + { + $n = $this->input->post("issue_edit_file_name_{$i}"); + $k = $this->input->post("issue_edit_file_kill_{$i}"); + $d = $this->input->post("issue_edit_file_desc_{$i}"); + + if ($n != '') + { + if ($k == 'yes') + { + array_push ($edit_files, array ('name' => $n, 'kill' => $k)); + } + else if ($d !== FALSE) + { + if (($d = trim($d)) == '') + { + $status = "error - no short description for {$n}"; + break; + } + + array_push ($edit_files, array ('name' => $n, 'desc' => $d)); + } + } + } + + if ($status == '') + { + if (count($edit_files) <= 0) + { + //$status = 'error - no input avaialble'; + $status = 'ok'; + } + else if ($this->issues->editFiles ($login['id'], $projectid, $issueid, $edit_files) === FALSE) + { + $status = 'error - ' . $this->issues->getErrorMessage(); + } + else + { + $status = 'ok'; + } + } + } + } + + print $status; + } + private function _handle_file ($login, $projectid, $issueid, $filename) { $this->load->model ('ProjectModel', 'projects'); diff --git a/codepot/src/codepot/models/filemodel.php b/codepot/src/codepot/models/filemodel.php index 35e65380..59071936 100644 --- a/codepot/src/codepot/models/filemodel.php +++ b/codepot/src/codepot/models/filemodel.php @@ -241,7 +241,6 @@ class FileModel extends Model { if ($i == 0) { - $this->errmsg = $this->db->_error_message(); $this->db->trans_rollback (); return FALSE; } diff --git a/codepot/src/codepot/models/issuemodel.php b/codepot/src/codepot/models/issuemodel.php index a7460643..5e4c160c 100644 --- a/codepot/src/codepot/models/issuemodel.php +++ b/codepot/src/codepot/models/issuemodel.php @@ -451,6 +451,7 @@ class IssueModel extends Model return $this->db->trans_status(); } + private function delete_all_files ($files) { foreach ($files as $f) @unlink ($f); @@ -580,7 +581,7 @@ class IssueModel extends Model return $newid; } - function create_with_files ($userid, $issue, $attached_files, $uploader) + function createWithFiles ($userid, $issue, $attached_files, $uploader) { set_error_handler (array ($this, 'capture_error')); $errmsg = ''; @@ -589,7 +590,7 @@ class IssueModel extends Model return $x; } - function update_summary_and_description ($userid, $issue) + function updateSummaryAndDescription ($userid, $issue) { // TODO: check if userid can do this.. $this->db->trans_start (); @@ -614,6 +615,278 @@ class IssueModel extends Model return $issue->id; } + + private function _delete_issue ($userid, $projectid, $issueid) + { + $this->db->trans_begin (); // manual transaction. not using trans_start(). + + $this->db->where ('projectid', $projectid); + $this->db->where ('issueid', $issueid); + $query = $this->db->get ('issue_file_list'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $result = $query->result (); + $file_names = array (); + foreach ($result as $f) + { + array_push ($file_names, $f->encname); + } + + $this->db->where ('projectid', $projectid); + $this->db->where ('issueid', $issueid); + $this->db->delete ('issue_file_list'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $this->db->where ('projectid', $projectid); + $this->db->where ('id', $issueid); + $this->db->delete ('issue_change'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $this->db->where ('projectid', $projectid); + $this->db->where ('id', $issueid); + $this->db->delete ('issue'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $this->db->set ('createdon', date('Y-m-d H:i:s')); + $this->db->set ('type', 'issue'); + $this->db->set ('action', 'delete'); + $this->db->set ('projectid', $projectid); + $this->db->set ('userid', $userid); + $this->db->set ('message', $issueid); + $this->db->insert ('log'); + + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $file_name_count = count($file_names); + for ($i = 0; $i < $file_name_count; $i++) + { + $encname = $file_names[$i]; + $path = CODEPOT_ISSUE_FILE_DIR . '/' . $encname; + if (@unlink ($path) === FALSE) + { + if ($i == 0) + { + $this->db->trans_rollback (); + return FALSE; + } + else + { + // there is no good way to recover from the error. + // carry on. some files will get orphaned. + } + } + } + + $this->db->trans_commit (); + return TRUE; + } + + function deleteWithFiles ($userid, $projectid, $issueid) + { + set_error_handler (array ($this, 'capture_error')); + $errmsg = ''; + $x = $this->_delete_issue ($userid, $projectid, $issueid); + restore_error_handler (); + return $x; + } + + + private function _add_files ($userid, $projectid, $issueid, $add_files, $uploader) + { + $this->db->trans_begin (); // manual transaction. not using trans_start(). + + $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($add_files); + for ($i = 0; $i < $file_count; $i++) + { + $f = $add_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', $projectid); + $this->db->set ('issueid', $issueid); + $this->db->set ('filename', $f['name']); + $this->db->set ('encname', $ud['file_name']); + + $this->db->set ('md5sum', $md5sum); + $this->db->set ('description', $f['desc']); + $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', 'update'); + $this->db->set ('projectid', $projectid); + $this->db->set ('userid', $userid); + $this->db->set ('message', $issueid); + $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 TRUE; + } + + function addFiles ($userid, $projectid, $issueid, $add_files, $uploader) + { + set_error_handler (array ($this, 'capture_error')); + $errmsg = ''; + $x = $this->_add_files ($userid, $projectid, $issueid, $add_files, $uploader); + restore_error_handler (); + return $x; + } + + private function _edit_files ($userid, $projectid, $issueid, $edit_files) + { + $this->db->trans_begin (); // manual transaction. not using trans_start(). + + $kill_files = array(); + $file_count = count($edit_files); + for ($i = 0; $i < $file_count; $i++) + { + $f = $edit_files[$i]; + + if (array_key_exists('kill', $f)) + { + $this->db->where ('projectid', $projectid); + $this->db->where ('issueid', $issueid); + $this->db->where ('filename', $f['name']); + $this->db->select ('encname'); + $query = $this->db->get('issue_file_list'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $result = $query->result (); + if (empty($result)) + { + $this->errmsg = "no such file - {$f['name']}"; + $this->db->trans_rollback (); + return FALSE; + } + + array_push ($kill_files, CODEPOT_ISSUE_FILE_DIR . '/' . $result[0]->encname); + + $this->db->where ('projectid', $projectid); + $this->db->where ('issueid', $issueid); + $this->db->where ('filename', $f['name']); + $query = $this->db->delete('issue_file_list'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + } + else if (array_key_exists('desc', $f)) + { + $this->db->where ('projectid', $projectid); + $this->db->where ('issueid', $issueid); + $this->db->where ('filename', $f['name']); + $this->db->set ('description', $f['desc']); + $this->db->update ('issue_file_list'); + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + } + } + + $this->db->set ('createdon', date('Y-m-d H:i:s')); + $this->db->set ('type', 'issue'); + $this->db->set ('action', 'update'); + $this->db->set ('projectid', $projectid); + $this->db->set ('userid', $userid); + $this->db->set ('message', $issueid); + $this->db->insert ('log'); + + if ($this->db->trans_status() === FALSE) + { + $this->errmsg = $this->db->_error_message(); + $this->db->trans_rollback (); + return FALSE; + } + + $this->delete_all_files ($kill_files); + $this->db->trans_commit (); + return TRUE; + } + + function editFiles ($userid, $projectid, $issueid, $edit_files) + { + set_error_handler (array ($this, 'capture_error')); + $errmsg = ''; + $x = $this->_edit_files ($userid, $projectid, $issueid, $edit_files); + restore_error_handler (); + return $x; + } } ?> diff --git a/codepot/src/codepot/views/file_show.php b/codepot/src/codepot/views/file_show.php index 459f3e33..cd1f37af 100644 --- a/codepot/src/codepot/views/file_show.php +++ b/codepot/src/codepot/views/file_show.php @@ -124,32 +124,32 @@ function kill_edit_file (no) d.prop ('disabled', true); } } - } var delete_in_progress = false; var add_file_in_progress = false; var edit_file_in_progress = false; -var original_file_name = [file_list[$i]; - printf ("%s\t'%s'", (($i == 0)? '': ",\n"), addslashes($f->filename)); -} -print "\n"; -?> +var original_file_name = [ + file_list[$i]; + printf ("%s\t'%s'", (($i == 0)? '': ",\n"), addslashes($f->filename)); + } + print "\n"; + ?> ]; var original_file_desc = [ -file_list[$i]; - printf ("%s\t'%s'", (($i == 0)? '': ",\n"), addslashes($f->description)); -} -print "\n"; -?> + file_list[$i]; + printf ("%s\t'%s'", (($i == 0)? '': ",\n"), addslashes($f->description)); + } + print "\n"; + ?> ]; $(function () { diff --git a/codepot/src/codepot/views/issue_home.php b/codepot/src/codepot/views/issue_home.php index 5e2ceb16..06fc6031 100644 --- a/codepot/src/codepot/views/issue_home.php +++ b/codepot/src/codepot/views/issue_home.php @@ -290,7 +290,8 @@ $this->load->view ( ), 'ctxmenuitems' => array ( - array ("issue/create/{$project->id}", ' ' . $this->lang->line('New'), 'issue_home_new') + // DEPRECATED + //array ("issue/create/{$project->id}", ' ' . $this->lang->line('New'), 'issue_home_new') ) ) ); diff --git a/codepot/src/codepot/views/issue_show.php b/codepot/src/codepot/views/issue_show.php index 1c3eedca..5d275058 100644 --- a/codepot/src/codepot/views/issue_show.php +++ b/codepot/src/codepot/views/issue_show.php @@ -23,6 +23,7 @@ converter->AsciiToHex ($issue->id); +$issue_file_count = count ($issue->files); ?>