diff --git a/lib/zin/wg/history/v1.php b/lib/zin/wg/history/v1.php index 96fc116292..3f812c92b2 100644 --- a/lib/zin/wg/history/v1.php +++ b/lib/zin/wg/history/v1.php @@ -20,6 +20,7 @@ class history extends wg 'actions?: array', // 操作列表数据。 'users?: array', // 用户 Map 数据。 'commentUrl?: string', // 备注对话框 URL。 + 'editCommentUrl?: string', // 修改备注对话框 URL。 'title?: string|array', // 标题。 'commentBtn?: string|array' // 是否允许添加备注。 ); @@ -74,7 +75,15 @@ class history extends wg protected function build(): wg { - list($panel, $objectID, $objectType, $title, $actions, $commentUrl, $commentBtn) = $this->prop(array('panel', 'objectID', 'objectType', 'title', 'actions', 'commentUrl', 'commentBtn')); + global $config; + + list($panel, $objectID, $objectType, $title, $actions, $commentUrl, $editCommentUrl, $commentBtn) = $this->prop(array('panel', 'objectID', 'objectType', 'title', 'actions', 'commentUrl', 'editCommentUrl', 'commentBtn')); + + if(isset($config->zin->mode) && $config->zin->mode == 'compatible') + { + if(is_null($commentUrl)) $commentUrl = createLink('action', 'commentZin', "objectType=$objectType&objectID=$objectID"); + if(is_null($editCommentUrl)) $editCommentUrl = createLink('action', 'editCommentZin', 'actionID={actionID}'); + } return zui::historyPanel ( @@ -84,6 +93,7 @@ class history extends wg set::actions($actions), set::title($title), set::commentUrl($commentUrl), + set::editCommentUrl($editCommentUrl), set::commentBtn($commentBtn), set($this->getRestProps()) ); diff --git a/module/action/control.php b/module/action/control.php index 758f708cc6..852c93af1e 100755 --- a/module/action/control.php +++ b/module/action/control.php @@ -357,6 +357,48 @@ class action extends control } } + /** + * 评论。 + * Comment. + * + * @param string $objectType + * @param int $objectID + * @access public + * @return void + */ + public function commentZin(string $objectType, int $objectID) + { + if(!empty($_POST)) + { + if(strtolower($objectType) == 'task') + { + $task = $this->loadModel('task')->getById($objectID); + $executions = explode(',', $this->app->user->view->sprints); + if(!in_array($task->execution, $executions)) return $this->send(array('result' => 'fail', 'message' => $this->lang->error->accessDenied)); + } + elseif(strtolower($objectType) == 'story') + { + $story = $this->loadModel('story')->getById($objectID); + $executions = explode(',', $this->app->user->view->sprints); + $products = explode(',', $this->app->user->view->products); + if(!array_intersect(array_keys($story->executions), $executions) and !in_array($story->product, $products) and empty($story->lib)) return $this->send(array('result' => 'fail', 'message' => $this->lang->error->accessDenied)); + } + + $actionID = $this->action->create($objectType, $objectID, 'Commented', $this->post->comment); + if(defined('RUN_MODE') && RUN_MODE == 'api') + { + return $this->send(array('status' => 'success', 'data' => $actionID)); + } + + return $this->send(array('status' => 'success', 'closeModal' => true, 'callback' => array('name' => 'zui.HistoryPanel.update', 'params' => array('objectType' => $objectType, 'objectID' => $objectID)))); + } + + $this->view->title = $this->lang->action->create; + $this->view->objectType = $objectType; + $this->view->objectID = $objectID; + $this->display(); + } + /** * Edit comment of a action. * @@ -384,6 +426,51 @@ class action extends control return $this->send(array('result' => 'success', 'locate' => 'reload', 'load' => true, 'closeModal' => true)); } + /** + * 编辑评论。 + * Edit comment. + * + * @param int $actionID + * @access public + * @return void + */ + public function editCommentZin(int $actionID) + { + $action = $this->action->getById($actionID); + + if(!empty($_POST)) + { + /* 获取表单内的数据。 */ + /* Get form data. */ + $commentData = form::data($this->config->action->form->editComment)->get(); + + $error = false; + + /* 判断是否符合更新的条件。 */ + /* Determine whether the update conditions are met. */ + if(strlen(trim(strip_tags($commentData->lastComment, ''))) != 0) + { + $error = $this->action->updateComment($actionID, $commentData->lastComment, $commentData->uid); + } + + if(!$error) + { + /* 不符合更新条件,返回错误。 */ + /* The update conditions are not met and an error is returned. */ + dao::$errors['submit'][] = $this->lang->action->historyEdit; + return $this->send(array('result' => 'fail', 'message' => dao::getError())); + } + + $action = $this->action->getById($actionID); + return $this->send(array('status' => 'success', 'closeModal' => true, 'callback' => array('name' => 'zui.HistoryPanel.update', 'params' => array('objectType' => $action->objectType, 'objectID' => $action->objectID)))); + } + + $this->view->title = $this->lang->action->editComment; + $this->view->actionID = $actionID; + $this->view->comment = $this->action->formatActionComment($action->comment); + $this->display(); + } + /** * Restore stages. * @@ -474,4 +561,20 @@ class action extends control { $this->action->cleanActions(); } + + /** + * 通过 Ajax 获取操作记录列表。 + * Get action list by ajax. + * + * @param string $objectType + * @param int $objectID + * @access public + * @return void + */ + public function ajaxGetList(string $objectType, int $objectID) + { + $actions = $this->action->getList($objectType, $objectID); + $actions = $this->action->buildActionList($actions); + return $this->send($actions); + } } diff --git a/module/action/model.php b/module/action/model.php index b4fd666ccc..8d97f0e910 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -927,21 +927,29 @@ class actionModel extends model } /** + * 打印一个对象的所有操作记录。 * Print actions of an object. * - * @param object $action - * @param string $desc + * @param object $action + * @param string $desc * @access public * @return void */ - public function renderAction($action, $desc = '') + public function renderAction(object $action, string $desc = '') { - if(!isset($action->objectType) || !isset($action->action)) return; + if(!isset($action->objectType) || !isset($action->action)) return false; $objectType = $action->objectType; $actionType = strtolower($action->action); /** + * + * 设置操作的描述。 + * + * 1. 如果模块中定义了操作的描述,使用模块中定义的。 + * 2. 如果模块中没有定义,使用公共的操作描述。 + * 3. 如果公共的操作描述中没有定义,使用默认的操作描述。 + * * Set the desc string of this action. * * 1. If the module of this action has defined desc of this actionType, use it. @@ -950,39 +958,39 @@ class actionModel extends model */ if(empty($desc)) { - if($action->objectType == 'story' && $action->action == 'reviewed' && strpos($action->extra, ',') !== false) + if(($action->objectType == 'story' or $action->objectType == 'demand') && $action->action == 'reviewed' && strpos($action->extra, ',') !== false) { - $desc = $this->lang->$objectType->action->rejectreviewed; + $desc = $this->lang->{$objectType}->action->rejectreviewed; } elseif($action->objectType == 'productplan' && in_array($action->action, array('startedbychild','finishedbychild','closedbychild','activatedbychild', 'createchild'))) { - $desc = $this->lang->$objectType->action->changebychild; + $desc = $this->lang->{$objectType}->action->changebychild; } elseif($action->objectType == 'module' && in_array($action->action, array('created', 'moved', 'deleted'))) { - $desc = $this->lang->$objectType->action->{$action->action}; + $desc = $this->lang->{$objectType}->action->{$action->action}; } elseif(strpos('createmr,editmr,removemr', $action->action) !== false && strpos($action->extra, '::') !== false) { $mrAction = str_replace('mr', '', $action->action) . 'Action'; list($mrDate, $mrActor, $mrLink) = explode('::', $action->extra); - if(isonlybody()) $mrLink .= ($this->config->requestType == 'GET' ? '&onlybody=yes' : '?onlybody=yes'); + if(isInModal()) $mrLink .= ($this->config->requestType == 'GET' ? '&onlybody=yes' : '?onlybody=yes'); $this->app->loadLang('mr'); - $desc = sprintf($this->lang->mr->$mrAction, $mrDate, $mrActor, $mrLink); + $desc = sprintf($this->lang->mr->{$mrAction}, $mrDate, $mrActor, $mrLink); } - elseif($this->config->edition == 'max' && strpos($this->config->action->assetType, ",{$action->objectType},") !== false && $action->action == 'approved') + elseif(in_array($this->config->edition, array('max', 'ipd')) && strpos($this->config->action->assetType, ",{$action->objectType},") !== false && $action->action == 'approved') { $desc = empty($this->lang->action->approve->{$action->extra}) ? '' : $this->lang->action->approve->{$action->extra}; } - elseif(isset($this->lang->$objectType) && isset($this->lang->$objectType->action->$actionType)) + elseif(isset($this->lang->{$objectType}) && isset($this->lang->{$objectType}->action->{$actionType})) { - $desc = $this->lang->$objectType->action->$actionType; + $desc = $this->lang->{$objectType}->action->{$actionType}; } - elseif($action->objectType == 'instance' && isset($this->lang->action->desc->$actionType)) + elseif($action->objectType == 'instance' && isset($this->lang->action->desc->{$actionType})) { - $desc = $this->lang->action->desc->$actionType; + $desc = $this->lang->action->desc->{$actionType}; $extra = json_decode($action->extra); if($actionType == 'adjustmemory') { @@ -991,7 +999,6 @@ class actionModel extends model } if(!empty($extra)) { - if(empty($extra->data)) $extra->data = new stdclass(); $action->oldName = zget($extra->data, 'oldName', ''); $action->newName = zget($extra->data, 'newName', ''); $action->oldVersion = zget($extra->data, 'oldVersion', ''); @@ -1004,15 +1011,15 @@ class actionModel extends model $action->extra = ''; if(!empty($extra->result->code) && $extra->result->code != 200 && !empty($extra->result->message)) $action->comment = $extra->result->message; - if(is_string($extra->result) && $extra->result != 'fail' && !empty($extra->message)) + if(is_string($extra->result) && $extra->result != 'fail') { $action->comment = "\n" . $extra->message; } } } - elseif(isset($this->lang->action->desc->$actionType)) + elseif(isset($this->lang->action->desc->{$actionType})) { - $desc = $this->lang->action->desc->$actionType; + $desc = $this->lang->action->desc->{$actionType}; } else { @@ -1023,11 +1030,13 @@ class actionModel extends model $action->date = substr($action->date, 0, 19); if($this->app->getViewType() == 'mhtml') $action->date = date('m-d H:i', strtotime($action->date)); + /* 遍历actions, 替换变量。 */ /* Cycle actions, replace vars. */ foreach($action as $key => $value) { if($key == 'history') continue; + /* 如果desc是数组,替换变量。 */ /* Desc can be an array or string. */ if(is_array($desc)) { @@ -1043,19 +1052,20 @@ class actionModel extends model } else { - if($actionType == 'restoredsnapshot' && in_array($action->objectType, array('vm', 'zanode')) && $value == 'defaultSnap') $value = $this->lang->$objectType->snapshot->defaultSnapName; + if($actionType == 'restoredsnapshot' && in_array($action->objectType, array('vm', 'zanode')) && $value == 'defaultSnap') $value = $this->lang->{$objectType}->snapshot->defaultSnapName; $desc = str_replace('$' . $key, $value, $desc); } } + /* 如果desc是数组,处理extra。 */ /* If the desc is an array, process extra. Please bug/lang. */ if(!is_array($desc)) return $desc; $extra = strtolower($action->extra); /* Fix bug #741. */ - if(isset($desc['extra'])) $desc['extra'] = $this->lang->$objectType->{$desc['extra']}; + if(isset($desc['extra'])) $desc['extra'] = $this->lang->{$objectType}->{$desc['extra']}; $actionDesc = ''; if(isset($desc['extra'][$extra])) @@ -1067,12 +1077,12 @@ class actionModel extends model $actionDesc = str_replace('$extra', $action->extra, $desc['main']); } - if($action->objectType == 'story' && $action->action == 'reviewed') + if(($action->objectType == 'story' or $action->objectType == 'demand') && $action->action == 'reviewed') { if(strpos($action->extra, ',') !== false) { list($extra, $reason) = explode(',', $extra); - $desc['reason'] = $this->lang->$objectType->{$desc['reason']}; + $desc['reason'] = $this->lang->{$objectType}->{$desc['reason']}; $actionDesc = str_replace(array('$extra', '$reason'), array($desc['extra'][$extra], $desc['reason'][$reason]), $desc['main']); } @@ -1088,7 +1098,7 @@ class actionModel extends model if(!empty($extra) && strpos($extra, '|') !== false) { list($operate, $storyID) = explode('|', $extra); - $desc['operate'] = $this->lang->$objectType->{$desc['operate']}; + $desc['operate'] = $this->lang->{$objectType}->{$desc['operate']}; $link = common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$storyID"), "#$storyID ") : "#$storyID"; $actionDesc = str_replace(array('$extra', '$operate'), array($link, $desc['operate'][$operate]), $desc['main']); } @@ -1111,6 +1121,68 @@ class actionModel extends model return $actionDesc; } + /** + * 格式化操作备注。 + * Format action comment. + * + * @param string $comment + * @access public + * @return string + */ + public function formatActionComment($comment): string + { + if(str_contains($comment, '
'))
+        {
+            $before   = explode('
', $comment);
+            $after    = explode('
', $before[1]); + $htmlCode = $after[0]; + return $before[0] . htmlspecialchars($htmlCode) . $after[1]; + } + + return strip_tags($comment) === $comment + ? nl2br($comment) + : $comment; + } + + /** + * 构建操作记录列表,便于前端组件进行渲染。 + * Build action list for render by frontend component. + * + * @param array $actions + * @param array $users + * @param bool $commentEditable + * @access public + * @return array + */ + public function buildActionList(array $actions, array $users = null, $commentEditable = true): array + { + if(empty($users)) $users = $this->loadModel('user')->getPairs('noletter'); + + $list = array(); + foreach($actions as $action) + { + $item = new stdClass(); + if(strlen(trim(($action->comment))) !== 0) + { + $item->comment = $this->formatActionComment($action->comment); + $item->commentEditable = $commentEditable && end($actions) == $action && $action->actor == $this->app->user->account && common::hasPriv('action', 'editComment'); + } + + if($action->action === 'assigned' || $action->action === 'toaudit') $action->extra = zget($users, $action->extra); + $action->actor = zget($users, $action->actor); + if(str_contains($action->actor, ':')) $action->actor = substr($action->actor, strpos($action->actor, ':') + 1); + + if(!empty($action->history)) $item->historyChanges = $this->renderChanges($action->objectType, $action->history); + + $item->id = $action->id; + $item->action = $action->action; + $item->content = $this->renderAction($action); + + $list[] = $item; + } + return $list; + } + /** * Print changes of every action. * diff --git a/module/action/ui/commentzin.html.php b/module/action/ui/commentzin.html.php new file mode 100644 index 0000000000..83b62b5473 --- /dev/null +++ b/module/action/ui/commentzin.html.php @@ -0,0 +1,28 @@ + + * @package action + * @link https://www.zentao.net + */ +namespace zin; + +$actions = array(); +$actions[] = 'submit'; +$actions[] = array('data-dismiss' => 'modal', 'text' => $lang->close); + +set::title($title); + +form +( + set::url('action', 'commentZin', "objectType=$objectType&objectID=$objectID"), + setClass('comment-form'), + editor + ( + set::name('actioncomment') + ), + set::actions($actions) +); diff --git a/module/action/ui/editcommentzin.html.php b/module/action/ui/editcommentzin.html.php new file mode 100644 index 0000000000..3d98e75997 --- /dev/null +++ b/module/action/ui/editcommentzin.html.php @@ -0,0 +1,29 @@ + + * @package action + * @link https://www.zentao.net + */ +namespace zin; + +$actions = array(); +$actions[] = 'submit'; +$actions[] = isInModal() ? array('data-dismiss' => 'modal', 'text' => $lang->close) : 'cancel'; + +set::title($title); + +form +( + set::url('action', 'editComment', "actionID=$actionID"), + setClass('comment-form is-edit'), + editor + ( + set::name('lastComment'), + html($comment) + ), + set::actions($actions) +);