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)
+);