Files
2024-04-09 10:48:05 +08:00

2799 lines
132 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* The model file of action module of ZenTaoPMS.
*
* @copyright Copyright 2009-2015 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Chunsheng Wang <chunsheng@cnezsoft.com>
* @package action
* @version $Id: model.php 5028 2013-07-06 02:59:41Z wyd621@gmail.com $
* @link http://www.zentao.net
*/
?>
<?php
class actionModel extends model
{
const BE_UNDELETED = 0; // The deleted object has been undeleted.
const CAN_UNDELETED = 1; // The deleted object can be undeleted.
const BE_HIDDEN = 2; // The deleted object has been hidded.
/**
* Create a action.
*
* @param string $objectType
* @param int $objectID
* @param string $actionType
* @param string $comment
* @param string $extra the extra info of this action, according to different modules and actions, can set different extra.
* @param string $actor
* @param bool $autoDelete
* @access public
* @return int
*/
public function create($objectType, $objectID, $actionType, $comment = '', $extra = '', $actor = '', $autoDelete = true)
{
if(strtolower($actionType) == 'commented' and empty($comment)) return false;
$actor = $actor ? $actor : $this->app->user->account;
$actionType = strtolower($actionType);
$actor = ($actionType == 'openedbysystem' or $actionType == 'closedbysystem') ? '' : $actor;
if($actor == 'guest' and $actionType == 'logout') return false;
$objectType = str_replace('`', '', $objectType);
$action = new stdclass();
$action->objectType = strtolower($objectType);
$action->objectID = $objectID;
$action->actor = $actor;
$action->action = $actionType;
$action->date = helper::now();
$action->extra = $extra;
if(!defined('IN_UPGRADE')) $action->vision = $this->config->vision;
if($objectType == 'story' and strpos(',reviewpassed,reviewrejected,reviewclarified,reviewreverted,synctwins,', ",$actionType,") !== false) $action->actor = $this->lang->action->system;
/* Use purifier to process comment. Fix bug #2683. */
if(empty($comment)) $comment = '';
$action->comment = fixer::stripDataTags($comment);
/* Process action. */
if($this->post->uid)
{
$action = $this->loadModel('file')->processImgURL($action, 'comment', $this->post->uid);
if($autoDelete) $this->file->autoDelete($this->post->uid);
}
/* Get product project and execution for this object. */
$relation = $this->getRelatedFields($action->objectType, $objectID, $actionType, $extra);
$action->product = $relation['product'];
$action->project = (int)$relation['project'];
$action->execution = (int)$relation['execution'];
$this->dao->insert(TABLE_ACTION)->data($action)->autoCheck()->exec();
$actionID = $this->dao->lastInsertID();
$hasRecentTable = true;
if(defined('IN_UPGRADE') and IN_UPGRADE)
{
$fromVersion = $this->loadModel('setting')->getItem('owner=system&module=common&section=global&key=version');
if(is_numeric($fromVersion[0]) and version_compare($fromVersion, '18.6', '<')) $hasRecentTable = false;
if(strpos($fromVersion, 'pro') !== false) $hasRecentTable = false;
if(strpos($fromVersion, 'biz') !== false and version_compare($fromVersion, 'biz8.6', '<')) $hasRecentTable = false;
if(strpos($fromVersion, 'max') !== false and version_compare($fromVersion, 'max4.6', '<')) $hasRecentTable = false;
if(strpos($fromVersion, 'ipd') !== false and version_compare($fromVersion, 'ipd1.0.1', '<')) $hasRecentTable = false;
}
if($hasRecentTable) $this->dao->insert(TABLE_ACTIONRECENT)->data($action)->autoCheck()->exec();
if($this->post->uid) $this->file->updateObjectID($this->post->uid, $objectID, $objectType);
/* Call the message notification function. */
$this->loadModel('message')->send(strtolower($objectType), $objectID, $actionType, $actionID, $actor, $extra);
/* Add index for global search. */
$this->saveIndex($objectType, $objectID, $actionType);
$changeFunc = 'after' . ucfirst($objectType);
if(method_exists($this, $changeFunc)) call_user_func_array(array($this, $changeFunc), array($action, $actionID));
return $actionID;
}
/**
* Update read field of action when view a task/bug.
*
* @param string $objectType
* @param int $objectID
* @access public
* @return void
*/
public function read($objectType, $objectID)
{
$this->dao->update(TABLE_ACTION)
->set('`read`')->eq(1)
->where('objectType')->eq($objectType)
->andWhere('objectID')->eq($objectID)
->andWhere('`read`')->eq(0)
->exec();
}
/**
* Get the unread actions.
*
* @param int $actionID
* @access public
* @return string
*/
public function getUnreadActions($actionID = 0)
{
if(!is_numeric($actionID)) $actionID = 0;
$actions = array();
$objectList = array('task' => TABLE_TASK, 'bug' => TABLE_BUG);
foreach($objectList as $object => $table)
{
$idList = $this->dao->select('id')->from($table)->where('assignedTo')->eq($this->app->user->account)->fetchPairs('id');
$tmpActions = $this->dao->select('*')->from(TABLE_ACTION)
->where('objectType')->eq($object)
->andWhere('objectID')->in($idList)
->andWhere('`read`')->eq(0)
->andWhere('id')->gt($actionID)
->fetchAll('id');
if(empty($tmpActions)) continue;
$tmpActions = $this->transformActions($tmpActions);
foreach($tmpActions as $action)
{
$actions[$action->objectType][] = array(
'actionID' => $action->id,
'objectType' => $action->objectType,
'objectID' => $action->objectID,
'action' => $action->actor . ' ' . $action->actionLabel . ' ' . $action->objectType . " #$action->objectID" . $action->objectName
);
}
}
return json_encode($actions);
}
/**
* Get product, project, execution of the object.
*
* @param string $objectType
* @param int $objectID
* @param string $actionType
* @param string $extra
* @access public
* @return array
*/
public function getRelatedFields($objectType, $objectID, $actionType = '', $extra = '')
{
$emptyRecord = array('product' => ',0,', 'project' => 0, 'execution' => 0);
switch($objectType)
{
case 'program':
return $emptyRecord;
case 'product':
return array('product' => ",$objectID,", 'project' => 0, 'execution' => 0);
case 'marketresearch':
return array('product' => ',0,', 'project' => $objectID, 'execution' => 0);
case 'project':
case 'execution':
$products = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($objectID)->fetchPairs('product');
$productList = ',' . join(',', array_keys($products)) . ',';
$relation = array($objectType => $objectID, 'product' => $productList);
if($objectType == 'execution')
{
$project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($objectID)->fetch('project');
$relation['project'] = $project;
}
else
{
$relation['execution'] = 0;
}
return $relation;
}
/* Only process these object types. */
if(strpos($this->config->action->needGetRelateField, ",{$objectType},") !== false)
{
if(!isset($this->config->objectTables[$objectType])) return $emptyRecord;
$record = $emptyRecord;
switch($objectType)
{
case 'story':
if($actionType == 'linked2build' or $actionType == 'unlinkedfrombuild')
{
$build = $this->dao->select('project,execution')->from(TABLE_BUILD)->where('id')->eq((int)$extra)->fetch();
$record['project'] = $build->project;
$record['execution'] = $build->execution;
}
elseif($actionType == 'estimated')
{
$record['project'] = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq((int)$extra)->fetch('project');
$record['execution'] = (int)$extra;
}
else
{
$projects = $this->dao->select('t2.id,t2.project,t2.type')->from(TABLE_PROJECTSTORY)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project = t2.id')
->where('t1.story')->eq($objectID)
->fetchAll();
foreach($projects as $project)
{
if($project->type == 'project')
{
$record['project'] = $project->id;
continue;
}
$record['project'] = $project->project;
$record['execution'] = $project->id;
}
}
case 'productplan':
case 'branch':
$record['product'] = $objectID == 0 ? $extra : $this->dao->select('product')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch('product');
break;
case 'testcase':
case 'case':
$result = $this->dao->select('product, project, execution')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
$record['product'] = $result->product;
$record['project'] = $result->project;
$record['execution'] = $result->execution;
if(strpos(',linked2testtask,unlinkedfromtesttask,assigned,run,', ',' . $actionType . ',') !== false and (int)$extra)
{
$testtask = $this->dao->select('project,execution')->from(TABLE_TESTTASK)->where('id')->eq((int)$extra)->fetch();
$record['project'] = $testtask->project;
$record['execution'] = $testtask->execution;
}
break;
case 'build':
case 'bug':
case 'testtask':
case 'doc':
$result = $this->dao->select('product, project, execution')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
$record['product'] = $result->product;
$record['project'] = $result->project;
$record['execution'] = $result->execution;
break;
case 'repo':
$record['execution'] = $this->dao->select('execution')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch('execution');
break;
case 'release':
$result = $this->dao->select('product, build')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
$record['product'] = $result->product;
$record['project'] = $this->dao->select('project')->from(TABLE_BUILD)->where('id')->in($result->build)->fetch('project');
break;
case 'task':
$fields = 'project, execution, story';
$result = $this->dao->select($fields)->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
if(empty($result)) break;
if($result->story != 0)
{
$product = $this->dao->select('product')->from(TABLE_STORY)->where('id')->eq($result->story)->fetchPairs('product');
$record['product'] = join(',', array_keys($product));
}
else
{
$products = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($result->execution)->fetchPairs('product');
$record['product'] = join(',', array_keys($products));
}
$record['project'] = $result->project;
$record['execution'] = $result->execution;
break;
case 'kanbanlane':
$record['execution'] = $this->dao->select('execution')->from(TABLE_KANBANLANE)->where('id')->eq($objectID)->fetch('execution');
break;
case 'kanbancolumn':
$record['execution'] = $extra;
break;
case 'team':
$team = $this->dao->select('type')->from(TABLE_PROJECT)->where('id')->eq($objectID)->fetch();
$type = $team->type == 'project' ? 'project' : 'execution';
$record[$type] = $objectID;
break;
case 'whitelist':
if($extra == 'product') $record['product'] = $objectID;
if($extra == 'project') $record['project'] = $objectID;
if($extra == 'sprint' or $extra == 'stage') $record['execution'] = $objectID;
break;
case 'module':
if(strpos(',deleted,', ",$actionType,") === false) $module = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->in($extra)->fetch();
if(strpos(',deleted,', ",$actionType,") !== false) $module = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($objectID)->fetch();
if(!empty($module) and $module->type == 'story') $record['product'] = $module->root;
break;
case 'review':
$result = $this->dao->select('*')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
if($result)
{
$products = $this->dao->select('product')->from(TABLE_PROJECTPRODUCT)->where('project')->eq($result->project)->fetchPairs('product');
$record['product'] = join(',', array_keys($products));
$record['project'] = zget($result, 'project', 0);
}
break;
default:
$result = $this->dao->select('*')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch();
$record['product'] = zget($result, 'product', '0');
$record['project'] = zget($result, 'project', 0);
$record['execution'] = zget($result, 'execution', 0);
}
if($actionType == 'unlinkedfromproject' or $actionType == 'linked2project') $record['project'] = (int)$extra ;
if(in_array($actionType, array('unlinkedfromexecution', 'linked2execution', 'linked2kanban'))) $record['execution'] = (int)$extra;
if($record)
{
$record['product'] = isset($record['product']) ? ',' . $record['product'] . ',' : ',0,';
if(empty($record['project'])) $record['project'] = 0;
if(empty($record['execution'])) $record['execution'] = 0;
if(!empty($record['execution']) and empty($record['project'])) $record['project'] = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($record['execution'])->fetch('project');
return $record;
}
return $emptyRecord;
}
return $emptyRecord;
}
/**
* Get actions of an object.
*
* @param int $objectType
* @param int $objectID
* @access public
* @return array
*/
public function getList($objectType, $objectID)
{
$orderBy = isset($_COOKIE['historyOrder']) && $this->cookie->historyOrder == 'desc' ? 'date_desc, id_desc' : 'date_asc, id_asc';
$objectID = is_array($objectID) ? $objectID : (int)$objectID;
$modules = $objectType == 'module' ? $this->dao->select('id')->from(TABLE_MODULE)->where('root')->in($objectID)->fetchPairs('id') : array();
$commiters = $this->loadModel('user')->getCommiters();
$actions = $this->dao->select('*')->from(TABLE_ACTION)
->beginIF($objectType == 'project')
->where("objectType IN('project', 'testtask', 'build')")
->andWhere('project')->in($objectID)
->fi()
->beginIF($objectType == 'story')
->where('objectType')->in('story,requirement')
->andWhere('objectID')->in($objectID)
->fi()
->beginIF($objectType == 'case')
->where('objectType')->in('case,testcase')
->andWhere('objectID')->in($objectID)
->fi()
->beginIF($objectType == 'module')
->where('objectType')->eq($objectType)
->andWhere('((action')->ne('deleted')->andWhere('objectID')->in($objectID)->markRight(1)
->orWhere('(action')->eq('deleted')->andWhere('objectID')->in($modules)->markRight(1)->markRight(1)
->fi()
->beginIF(!in_array($objectType, array('project', 'case', 'story', 'module')))
->where('objectType')->eq($objectType)
->andWhere('objectID')->in($objectID)
->fi()
->orderBy($orderBy)
->fetchAll('id');
$histories = $this->getHistory(array_keys($actions));
$this->loadModel('file');
if($objectType == 'project')
{
$this->app->loadLang('build');
$this->app->loadLang('testtask');
$actions = $this->processProjectActions($actions);
}
foreach($actions as $actionID => $action)
{
$actionName = strtolower($action->action);
if($actionName == 'svncommited' and isset($commiters[$action->actor]))
{
$action->actor = $commiters[$action->actor];
}
elseif($actionName == 'gitcommited' and isset($commiters[$action->actor]))
{
$action->actor = $commiters[$action->actor];
}
elseif($actionName == 'linked2execution' or $actionName == 'linked2kanban')
{
$execution = $this->dao->select('name,type,multiple')->from(TABLE_PROJECT)->where('id')->eq($action->extra)->fetch();
if(!empty($execution))
{
if($execution->type != 'project' and empty($execution->multiple))
{
unset($actions[$actionID]);
continue;
}
$name = $execution->name;
$method = $execution->type == 'kanban' ? 'kanban' : 'view';
$action->extra = (!common::hasPriv('execution', $method) or ($method == 'kanban' and isonlybody())) ? $name : html::a(helper::createLink('execution', $method, "executionID=$action->execution"), $name, '', "data-app='execution'");
}
}
elseif($actionName == 'linked2project')
{
$project = $this->dao->select('name,model')->from(TABLE_PROJECT)->where('id')->eq($action->extra)->fetch();
$productID = trim($action->product, ',');
$name = $project->name;
$method = $project->model == 'kanban' ? 'index' : 'view';
if($name) $action->extra = (common::hasPriv('project', $method) and $this->config->vision != 'or') ? html::a(helper::createLink('project', $method, "projectID=$action->project"), $name) : $name;
}
elseif($actionName == 'linked2plan')
{
$title = $this->dao->select('title')->from(TABLE_PRODUCTPLAN)->where('id')->eq($action->extra)->fetch('title');
if($title) $action->extra = common::hasPriv('productplan', 'view') ? html::a(helper::createLink('productplan', 'view', "planID=$action->extra"), $title) : $title;
}
elseif($actionName == 'changedbycharter')
{
$name = $this->dao->select('name')->from(TABLE_CHARTER)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('charter', 'view') ? html::a(helper::createLink('charter', 'view', "charterID=$action->extra"), $name) : $name;
}
elseif($actionName == 'linked2roadmap' and $action->objectType == 'story')
{
$name = $this->dao->select('name')->from(TABLE_ROADMAP)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('roadmap', 'view') ? html::a(helper::createLink('roadmap', 'view', "roadmapID=$action->extra"), $name) : $name;
}
elseif($actionName == 'linked2build')
{
$name = $this->dao->select('name')->from(TABLE_BUILD)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('build', 'view') ? html::a(helper::createLink('build', 'view', "builID=$action->extra&type={$action->objectType}"), $name) : $name;
}
elseif($actionName == 'linked2bug')
{
$name = $this->dao->select('name')->from(TABLE_BUILD)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('build', 'view') ? html::a(helper::createLink('build', 'view', "builID=$action->extra&type={$action->objectType}"), $name) : $name;
}
elseif($actionName == 'linked2release')
{
$name = $this->dao->select('name')->from(TABLE_RELEASE)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('release', 'view') ? html::a(helper::createLink('release', 'view', "releaseID=$action->extra&type={$action->objectType}"), $name) : $name;
}
elseif($actionName == 'linked2testtask')
{
$name = $this->dao->select('name')->from(TABLE_TESTTASK)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('testtask', 'view') ? html::a(helper::createLink('testtask', 'view', "taskID=$action->extra"), $name) : $name;
}
elseif($actionName == 'linked2revision' or $actionName == 'unlinkedfromrevision')
{
$commit = $this->dao->select('repo,revision')->from(TABLE_REPOHISTORY)->where('id')->eq($action->extra)->fetch();
if($commit)
{
$revision = substr($commit->revision, 0, 10);
$action->extra = common::hasPriv('repo', 'revision') ? html::a(helper::createLink('repo', 'revision', "repoID=$commit->repo&objectID=0&revision=$commit->revision"), $revision) : $revision;
}
}
elseif($actionName == 'moved' and $action->objectType != 'module')
{
$name = $this->dao->select('name')->from(TABLE_PROJECT)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('execution', 'task') ? html::a(helper::createLink('execution', 'task', "executionID=$action->extra"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'frombug' and common::hasPriv('bug', 'view'))
{
$action->extra = html::a(helper::createLink('bug', 'view', "bugID=$action->extra"), $action->extra);
}
elseif($actionName == 'unlinkedfromexecution')
{
$name = $this->dao->select('name')->from(TABLE_PROJECT)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('execution', 'view') ? html::a(helper::createLink('execution', 'view', "executionID=$action->extra"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'unlinkedfromproject')
{
$name = $this->dao->select('name')->from(TABLE_PROJECT)->where('id')->eq($action->extra)->fetch('name');
$productID = trim($action->product, ',');
if($name) $action->extra = common::hasPriv('projectstory', 'story') ? html::a(helper::createLink('projectstory', 'story', "projectID=$action->execution&productID=$productID"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'unlinkedfrombuild')
{
$name = $this->dao->select('name')->from(TABLE_BUILD)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('build', 'view') ? html::a(helper::createLink('build', 'view', "builID=$action->extra&type={$action->objectType}"), $name) : $name;
}
elseif($actionName == 'unlinkedfromrelease')
{
$name = $this->dao->select('name')->from(TABLE_RELEASE)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('release', 'view') ? html::a(helper::createLink('release', 'view', "releaseID=$action->extra&type={$action->objectType}"), $name) : $name;
}
elseif($actionName == 'unlinkedfromplan')
{
$title = $this->dao->select('title')->from(TABLE_PRODUCTPLAN)->where('id')->eq($action->extra)->fetch('title');
if($title) $action->extra = common::hasPriv('productplan', 'view') ? html::a(helper::createLink('productplan', 'view', "planID=$action->extra"), "#$action->extra " . $title) : "#$action->extra " . $title;
}
elseif($actionName == 'unlinkedfromroadmap' and $action->objectType == 'story')
{
$name = $this->dao->select('name')->from(TABLE_ROADMAP)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('roadmap', 'view') ? html::a(helper::createLink('roadmap', 'view', "roadmapID=$action->extra"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'unlinkedfromtesttask')
{
$name = $this->dao->select('name')->from(TABLE_TESTTASK)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('testtask', 'view') ? html::a(helper::createLink('testtask', 'view', "taskID=$action->extra"), $name) : $name;
}
elseif(strpos('feedback,ticket', $action->objectType) === false and $actionName == 'tostory')
{
$productShadow = $this->dao->select('shadow')->from(TABLE_PRODUCT)->where('id')->in(trim($action->product, ','))->fetch('shadow');
$title = $this->dao->select('title')->from(TABLE_STORY)->where('id')->eq($action->extra)->fetch('title');
$defaultExtra = "#$action->extra " . $title;
if($productShadow)
{
$projectID = $this->dao->select('project')->from(TABLE_PROJECTSTORY)->where('story')->eq($action->extra)->fetch('project');
if($title) $action->extra = (common::hasPriv('projectstory', 'view') and $projectID) ? html::a(helper::createLink('projectstory', 'view', "storyID={$action->extra}&projectID=$projectID"), $defaultExtra) : $defaultExtra;
}
else
{
if($title) $action->extra = common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$action->extra"), $defaultExtra) : $defaultExtra;
}
}
elseif($actionName == 'importedcard')
{
$title = $this->dao->select('name')->from(TABLE_KANBAN)->where('id')->eq($action->extra)->fetch('name');
if($title) $action->extra = (common::hasPriv('kanban', 'view') and !isonlybody()) ? html::a(helper::createLink('kanban', 'view', "kanbanID=$action->extra"), "#$action->extra " . $title) : "#$action->extra " . $title;
}
elseif($actionName == 'createchildren')
{
$names = $this->dao->select('id,name')->from(TABLE_TASK)->where('id')->in($action->extra)->fetchPairs('id', 'name');
$action->extra = '';
if($names)
{
foreach($names as $id => $name) $action->extra .= common::hasPriv('task', 'view') ? html::a(helper::createLink('task', 'view', "taskID=$id"), "#$id " . $name) . ', ' : "#$id " . $name . ', ';
}
$action->extra = trim(trim($action->extra), ',');
}
/* Code for waterfall. */
elseif($actionName == 'createrequirements')
{
$names = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($action->extra)->fetchPairs('id', 'title');
$action->extra = '';
if($names)
{
foreach($names as $id => $name) $action->extra .= common::hasPriv('requriement', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$id"), "#$id " . $name) . ', ' : "#$id " . $name . ', ';
}
$action->extra = trim(trim($action->extra), ',');
}
elseif($action->objectType != 'feedback' and (strpos(',totask,linkchildtask,unlinkchildrentask,linkparenttask,unlinkparenttask,deletechildrentask,converttotask,', ",$actionName,") !== false))
{
$name = $this->dao->select('name')->from(TABLE_TASK)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('task', 'view') ? html::a(helper::createLink('task', 'view', "taskID=$action->extra"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'linkchildstory' or $actionName == 'unlinkchildrenstory' or $actionName == 'linkparentstory' or $actionName == 'unlinkparentstory' or $actionName == 'deletechildrenstory')
{
$name = $this->dao->select('title')->from(TABLE_STORY)->where('id')->eq($action->extra)->fetch('title');
if($name) $action->extra = common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$action->extra"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'deletechildrendemand')
{
$name = $this->dao->select('title')->from(TABLE_DEMAND)->where('id')->eq($action->extra)->fetch('title');
if($name) $action->extra = common::hasPriv('demand', 'view') ? html::a(helper::createLink('demand', 'view', "demandID=$action->extra"), "#$action->extra " . $name) : "#$action->extra " . $name;
}
elseif($actionName == 'buildopened')
{
$name = $this->dao->select('name')->from(TABLE_BUILD)->where('id')->eq($action->objectID)->fetch('name');
if($name) $action->extra = common::hasPriv('build', 'view') ? html::a(helper::createLink('build', 'view', "buildID=$action->objectID"), "#$action->objectID " . $name) : "#$action->objectID " . $name;
}
elseif($actionName == 'testtaskopened' or $actionName == 'testtaskstarted' or $actionName == 'testtaskclosed')
{
$name = $this->dao->select('name')->from(TABLE_TESTTASK)->where('id')->eq($action->objectID)->fetch('name');
if($name) $action->extra = common::hasPriv('testtask', 'view') ? html::a(helper::createLink('testtask', 'view', "testtaskID=$action->objectID"), "#$action->objectID " . $name) : "#$action->objectID " . $name;
}
elseif($actionName == 'fromlib' and $action->objectType == 'case')
{
$name = $this->dao->select('name')->from(TABLE_TESTSUITE)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('caselib', 'browse') ? html::a(helper::createLink('caselib', 'browse', "libID=$action->extra"), $name) : $name;
}
elseif(strpos(',importfromstorylib,importfromrisklib,importfromissuelib,importfromopportunitylib,', ",{$actionName},") !== false and ($this->config->edition == 'max' or $this->config->edition == 'ipd'))
{
$name = $this->dao->select('name')->from(TABLE_ASSETLIB)->where('id')->eq($action->extra)->fetch('name');
if($name) $action->extra = common::hasPriv('assetlib', $action->objectType) ? html::a(helper::createLink('assetlib', $action->objectType, "libID=$action->extra"), $name) : $name;
}
elseif(($actionName == 'closed' and $action->objectType == 'story') or ($actionName == 'resolved' and $action->objectType == 'bug'))
{
$action->appendLink = '';
if(strpos($action->extra, '|') !== false) $action->extra = substr($action->extra, 0, strpos($action->extra, '|'));
if(strpos($action->extra, ':') !== false)
{
list($extra, $id) = explode(':', $action->extra);
$action->extra = $extra;
if($id)
{
$table = $action->objectType == 'story' ? TABLE_STORY : TABLE_BUG;
$name = $this->dao->select('title')->from($table)->where('id')->eq($id)->fetch('title');
if($name) $action->appendLink = html::a(helper::createLink($action->objectType, 'view', "id=$id"), "#$id " . $name);
}
}
}
elseif($actionName == 'closed' and $action->objectType == 'demand')
{
$action->appendLink = '';
if(strpos($action->extra, '|') !== false) $action->extra = substr($action->extra, 0, strpos($action->extra, '|'));
if(strpos($action->extra, ':') !== false)
{
list($extra, $id) = explode(':', $action->extra);
$action->extra = $extra;
if($id)
{
$name = $this->dao->select('title')->from(TABLE_DEMAND)->where('id')->eq($id)->fetch('title');
if($name) $action->appendLink = html::a(helper::createLink($action->objectType, 'view', "id=$id"), "#$id " . $name);
}
}
}
elseif($actionName == 'finished' and $objectType == 'todo')
{
$action->appendLink = '';
if(strpos($action->extra, ':')!== false)
{
list($extra, $id) = explode(':', $action->extra);
$action->extra = strtolower($extra);
if($id)
{
$table = $this->config->objectTables[$action->extra];
$field = $this->config->action->objectNameFields[$action->extra];
$name = $this->dao->select($field)->from($table)->where('id')->eq($id)->fetch($field);
if($name) $action->appendLink = html::a(helper::createLink($action->extra, 'view', "id=$id"), "#$id " . $name);
}
}
}
elseif(($actionName == 'opened' or $actionName == 'managed' or $actionName == 'edited') and ($objectType == 'execution' || $objectType == 'project'))
{
$this->app->loadLang('execution');
$linkedProducts = $this->dao->select('id,name')->from(TABLE_PRODUCT)->where('id')->in($action->extra)->fetchPairs('id', 'name');
$action->extra = '';
if($linkedProducts and $this->config->vision == 'rnd')
{
foreach($linkedProducts as $productID => $productName) $linkedProducts[$productID] = html::a(helper::createLink('product', 'browse', "productID=$productID"), "#{$productID} {$productName}");
$action->extra = sprintf($this->lang->execution->action->extra, '<strong>' . join(', ', $linkedProducts) . '</strong>');
}
}
elseif($objectType == 'demand' && in_array($actionName, array('retracted', 'restored')))
{
$story = $this->loadModel('story')->getById($action->extra);
if($story) $action->extra = common::hasPriv('story', 'view') ? "#{$action->extra} {$this->lang->story->title} " . html::a(helper::createLink('story', 'view', "storyID=$story->id"), "$story->title") : "#$action->extra " . $demand->title;
}
elseif($objectType == 'story' && $actionName == 'distributed')
{
$demand = $this->loadModel('demand')->getById($action->extra);
if($demand) $action->extra = common::hasPriv('demand', 'view') && $this->config->vision == 'or' ? html::a(helper::createLink('demand', 'view', "demandID=$action->extra"), "#$action->extra " . $demand->title) : "#$action->extra " . $demand->title;
}
$action->history = isset($histories[$actionID]) ? $histories[$actionID] : array();
$actionName = strtolower($action->action);
if(strpos(',linkstory,unlinkstory,createchildrenstory,linkur,unlinkur,', ",$actionName,") !== false)
{
$extra = '';
foreach(explode(',', $action->extra) as $id) $extra .= common::hasPriv('story', 'view') ? html::a(helper::createLink('story', 'view', "storyID=$id"), "#$id ") . ', ' : "#$id, ";
$action->extra = trim(trim($extra), ',');
}
elseif($actionName == 'linkbug' or $actionName == 'unlinkbug')
{
$extra = '';
foreach(explode(',', $action->extra) as $id) $extra .= common::hasPriv('bug', 'view') ? html::a(helper::createLink('bug', 'view', "bugID=$id"), "#$id ") . ', ' : "#$id, ";
$action->extra = trim(trim($extra), ',');
}
elseif($actionName == 'repocreated')
{
$action->extra = str_replace("class='iframe'", 'data-app="devops"', $action->extra);
}
foreach($action->history as $history)
{
if(empty($history->diff)) continue;
if($history->field == 'git' || $history->field == 'subversion')
{
$history->diff = str_replace('+', '%2B', $history->diff);
$history->diff = str_replace("class='iframe'", "class='iframe' data-height='500'", $history->diff);
}
}
$action->comment = $this->file->setImgSize($action->comment, $this->config->action->commonImgSize);
$actions[$actionID] = $action;
}
return $actions;
}
/**
* Process Project Actions change actionStype.
*
* @param array $actions
* @access public
* @return array
*/
public function processProjectActions($actions)
{
/* Define the action map table. */
$map = array();
$map['testtask']['opened'] = 'testtaskopened';
$map['testtask']['started'] = 'testtaskstarted';
$map['testtask']['closed'] = 'testtaskclosed';
$map['build']['opened'] = 'buildopened';
/* Process actions. */
foreach($actions as $key => $action)
{
if($action->objectType != 'project' and !isset($map[$action->objectType][$action->action])) unset($actions[$key]);
if(isset($map[$action->objectType][$action->action])) $action->action = $map[$action->objectType][$action->action];
}
return $actions;
}
/**
* Get an action record.
*
* @param int $actionID
* @access public
* @return object
*/
public function getById($actionID)
{
$action = $this->dao->findById((int)$actionID)->from(TABLE_ACTION)->fetch();
/* Splice domain name for connection when the action is equal to 'repocreated'.*/
if($action->action == 'repocreated') $action->extra = str_replace("href='", "href='" . common::getSysURL(), $action->extra);
return $action;
}
/**
* Get deleted objects.
*
* @param string $objectType
* @param string $type all|hidden
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getTrashes($objectType, $type, $orderBy, $pager)
{
$extra = $type == 'hidden' ? self::BE_HIDDEN : self::CAN_UNDELETED;
$trashes = $this->dao->select('*')->from(TABLE_ACTION)
->where('action')->eq('deleted')
->beginIF($objectType != 'all')->andWhere('objectType')->eq($objectType)->fi()
->andWhere('extra')->eq($extra)
->andWhere('vision')->eq($this->config->vision)
->orderBy($orderBy)->page($pager)->fetchAll();
if(!$trashes) return array();
/* Group trashes by objectType, and get there name field. */
foreach($trashes as $object)
{
$object->objectType = str_replace('`', '', $object->objectType);
$typeTrashes[$object->objectType][] = $object->objectID;
}
foreach($typeTrashes as $objectType => $objectIdList)
{
if(!isset($this->config->objectTables[$objectType])) continue;
if(!isset($this->config->action->objectNameFields[$objectType])) continue;
$objectIdList = array_unique($objectIdList);
$table = $this->config->objectTables[$objectType];
$field = $this->config->action->objectNameFields[$objectType];
if($objectType == 'pipeline')
{
$objectNames['jenkins'] = $this->dao->select("id, $field AS name")->from($table)->where('id')->in($objectIdList)->andWhere('type')->eq('jenkins')->fetchPairs();
$objectNames['gitlab'] = $this->dao->select("id, $field AS name")->from($table)->where('id')->in($objectIdList)->andWhere('type')->eq('gitlab')->fetchPairs();
}
else
{
$objectNames[$objectType] = $this->dao->select("id, $field AS name")->from($table)->where('id')->in($objectIdList)->fetchPairs();
}
}
/* Add name field to the trashes. */
foreach($trashes as $trash)
{
$objectType = $trash->objectType;
if($objectType == 'pipeline')
{
if(isset($objectNames['gitlab'][$trash->objectID])) $objectType = 'gitlab';
if(isset($objectNames['jenkins'][$trash->objectID])) $objectType = 'jenkins';
$trash->objectType = $objectType;
}
$trash->objectName = isset($objectNames[$objectType][$trash->objectID]) ? $objectNames[$objectType][$trash->objectID] : '';
}
return $trashes;
}
/**
* Get deleted objects by search.
*
* @param string $objectType
* @param string $type all|hidden
* @param int $queryID
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getTrashesBySearch($objectType, $type, $queryID, $orderBy, $pager = null)
{
if($objectType == 'all') return array();
if($queryID and $queryID != 'myQueryID')
{
$query = $this->loadModel('search')->getQuery($queryID);
if($query)
{
$this->session->set('trashQuery', $query->sql);
$this->session->set('trashForm', $query->form);
}
else
{
$this->session->set('trashQuery', ' 1 = 1');
}
}
else
{
if($this->session->trashQuery == false) $this->session->set('trashQuery', ' 1 = 1');
}
$extra = $type == 'hidden' ? self::BE_HIDDEN : self::CAN_UNDELETED;
$trashQuery = $this->session->trashQuery;
$trashQuery = str_replace(array('`objectID`', '`actor`', '`date`'), array('t1.`objectID`', 't1.`actor`', 't1.`date`'), $trashQuery);
$table = $this->config->objectTables[$objectType];
$nameField = isset($this->config->action->objectNameFields[$objectType]) ? 't2.' . '`' . $this->config->action->objectNameFields[$objectType] . '`' : '';
if($nameField) $trashQuery = preg_replace("/`objectName`/", $nameField, $trashQuery);
if($objectType != 'pipeline')
{
$trashes = $this->dao->select("t1.*, $nameField as objectName")->from(TABLE_ACTION)->alias('t1')
->leftJoin($table)->alias('t2')->on('t1.objectID=t2.id')
->where('t1.action')->eq('deleted')
->andWhere($trashQuery)
->andWhere('t1.extra')->eq($extra)
->andWhere('t1.vision')->eq($this->config->vision)
->beginIF($objectType != 'all')->andWhere('t1.objectType')->eq($objectType)->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('objectID');
}
else
{
$trashes = $this->dao->select("t1.*, t1.objectType as type, t2.name as objectName, t2.type as objectType")->from(TABLE_ACTION)->alias('t1')
->leftJoin(TABLE_PIPELINE)->alias('t2')->on('t1.objectID=t2.id')
->where('t1.action')->eq('deleted')
->andWhere($trashQuery)
->andWhere('t1.extra')->eq($extra)
->andWhere('t1.vision')->eq($this->config->vision)
->andWhere('(t2.type')->eq('gitlab')
->orWhere('t2.type')->eq('jenkins')
->markRight(1)
->orderBy($orderBy)
->page($pager)
->fetchAll('objectID');
}
return $trashes;
}
/**
* Get object type list of trashes.
*
* @param string $type
* @access public
* @return array
*/
public function getTrashObjectTypes($type)
{
$extra = $type == 'hidden' ? self::BE_HIDDEN : self::CAN_UNDELETED;
return $this->dao->select('objectType')->from(TABLE_ACTION)->where('action')->eq('deleted')->andWhere('extra')->eq($extra)->andWhere('vision')->eq($this->config->vision)->fetchAll('objectType');
}
/**
* Get histories of an action.
*
* @param int $actionID
* @access public
* @return array
*/
public function getHistory($actionID)
{
return $this->dao->select()->from(TABLE_HISTORY)->where('action')->in($actionID)->fetchGroup('action');
}
/**
* Log histories for an action.
*
* @param int $actionID
* @param array $changes
* @access public
* @return void
*/
public function logHistory($actionID, $changes)
{
if(empty($actionID)) return false;
foreach($changes as $change)
{
if(is_object($change))
{
$change->action = $actionID;
}
else
{
$change['action'] = $actionID;
}
$this->dao->insert(TABLE_HISTORY)->data($change)->exec();
}
if(isset($this->session->callbackActionList[$actionID]))
{
$callbackMethod = $this->session->callbackActionList[$actionID];
unset($this->session->callbackActionList[$actionID]);
if(method_exists($this, $callbackMethod)) call_user_func_array(array($this, $callbackMethod), array($actionID));
}
}
/**
* 打印一个对象的所有操作记录。
* Print actions of an object.
*
* @param object $action
* @param string $desc
* @access public
* @return void
*/
public function renderAction(object $action, string $desc = '')
{
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.
* 2. If no defined in the module language, search the common action define.
* 3. If not found in the lang->action->desc, use the $lang->action->desc->common or $lang->action->desc->extra as the default.
*/
if(empty($desc))
{
if(($action->objectType == 'story' or $action->objectType == 'demand') && $action->action == 'reviewed' && strpos($action->extra, ',') !== false)
{
$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;
}
elseif($action->objectType == 'module' && in_array($action->action, array('created', 'moved', 'deleted')))
{
$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(isInModal()) $mrLink .= ($this->config->requestType == 'GET' ? '&onlybody=yes' : '?onlybody=yes');
$this->app->loadLang('mr');
$desc = sprintf($this->lang->mr->{$mrAction}, $mrDate, $mrActor, $mrLink);
}
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}))
{
$desc = $this->lang->{$objectType}->action->{$actionType};
}
elseif($action->objectType == 'instance' && isset($this->lang->action->desc->{$actionType}))
{
$desc = $this->lang->action->desc->{$actionType};
$extra = json_decode($action->extra);
if($actionType == 'adjustmemory')
{
$action->newMemory = $action->comment;
$action->comment = '';
}
if(!empty($extra))
{
$action->extra = '';
if(!empty($extra->data))
{
$action->oldName = zget($extra->data, 'oldName', '');
$action->newName = zget($extra->data, 'newName', '');
$action->oldVersion = zget($extra->data, 'oldVersion', '');
$action->newVersion = zget($extra->data, 'newVersion', '');
$action->oldAppName = zget($extra->data, 'oldAppName', '');
$action->newAppName = zget($extra->data, 'newAppName', '');
$enableAutoBackup = zget($extra->data, 'autoBackup', 0);
if($actionType == 'saveautobackupsettings' && $enableAutoBackup) $desc = $this->lang->action->desc->closeautobackupsettings;
}
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' && isset($extra->message))
{
$action->comment = "\n" . $extra->message;
}
}
}
elseif(isset($this->lang->action->desc->{$actionType}))
{
$desc = $this->lang->action->desc->{$actionType};
}
else
{
$desc = $action->extra ? $this->lang->action->desc->extra : $this->lang->action->desc->common;
}
}
$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))
{
if($key == 'extra') continue;
if($action->objectType == 'story' && $action->action == 'reviewed' && strpos($action->extra, '|') !== false && $key == 'actor')
{
$desc['main'] = str_replace('$actor', $this->lang->action->superReviewer . ' ' . $value, $desc['main']);
}
else
{
$desc['main'] = str_replace('$' . $key, $value, $desc['main']);
}
}
else
{
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']};
$actionDesc = '';
if(isset($desc['extra'][$extra]))
{
$actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
else
{
$actionDesc = str_replace('$extra', $action->extra, $desc['main']);
}
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']};
$actionDesc = str_replace(array('$extra', '$reason'), array($desc['extra'][$extra], $desc['reason'][$reason]), $desc['main']);
}
if(strpos($action->extra, '|') !== false)
{
list($extra, $isSuperReviewer) = explode('|', $extra);
$actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
}
if($action->objectType == 'story' && $action->action == 'synctwins')
{
if(!empty($extra) && strpos($extra, '|') !== false)
{
list($operate, $storyID) = explode('|', $extra);
$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']);
}
}
if($action->objectType == 'module' && strpos(',created,moved,', $action->action) !== false)
{
$moduleNames = $this->loadModel('tree')->getOptionMenu($action->objectID, 'story', 0, 'all', '');
$modules = explode(',', $action->extra);
$moduleNames = array_intersect_key($moduleNames, array_combine($modules, $modules));
$moduleNames = implode(', ', $moduleNames);
$actionDesc = str_replace('$extra', $moduleNames, $desc['main']);
}
elseif($action->objectType == 'module' && $action->action == 'deleted')
{
$module = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($action->objectID)->fetch();
$moduleNames = $this->loadModel('tree')->getOptionMenu($module->root, 'story', 0, 'all', '');
$actionDesc = str_replace('$extra', zget($moduleNames, $action->objectID), $desc['main']);
}
return $actionDesc;
}
/**
* 格式化操作备注。
* Format action comment.
*
* @param string $comment
* @access public
* @return string
*/
public function formatActionComment($comment): string
{
if(str_contains($comment, '<pre class="prettyprint lang-html">'))
{
$before = explode('<pre class="prettyprint lang-html">', $comment);
$after = explode('</pre>', $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.
*
* @param string $objectType
* @param array $histories
* @param bool $canChangeTag
* @access public
* @return void
*/
public function renderChanges($objectType, $histories, $canChangeTag = true)
{
if(empty($histories)) return;
$maxLength = 0; // The max length of fields names.
$historiesWithDiff = array(); // To save histories without diff info.
$historiesWithoutDiff = array(); // To save histories with diff info.
/* Diff histories by hasing diff info or not. Thus we can to make sure the field with diff show at last. */
foreach($histories as $history)
{
$fieldName = $history->field;
$history->fieldLabel = (isset($this->lang->$objectType) && isset($this->lang->$objectType->$fieldName)) ? $this->lang->$objectType->$fieldName : $fieldName;
if($objectType == 'module') $history->fieldLabel = $this->lang->tree->$fieldName;
if($fieldName == 'fileName') $history->fieldLabel = $this->lang->file->$fieldName;
if(($length = strlen($history->fieldLabel)) > $maxLength) $maxLength = $length;
$history->diff ? $historiesWithDiff[] = $history : $historiesWithoutDiff[] = $history;
}
$histories = array_merge($historiesWithoutDiff, $historiesWithDiff);
$content = '';
foreach($histories as $history)
{
$history->fieldLabel = str_pad($history->fieldLabel, $maxLength, $this->lang->action->label->space);
if($history->diff != '')
{
$history->diff = str_replace(array('<ins>', '</ins>', '<del>', '</del>'), array('[ins]', '[/ins]', '[del]', '[/del]'), $history->diff);
$history->diff = ($history->field != 'subversion' && $history->field != 'git') ? htmlSpecialString($history->diff) : $history->diff; // Keep the diff link.
$history->diff = str_replace(array('[ins]', '[/ins]', '[del]', '[/del]'), array('<ins>', '</ins>', '<del>', '</del>'), $history->diff);
$history->diff = nl2br($history->diff);
$history->noTagDiff = $canChangeTag ? preg_replace('/&lt;\/?([a-z][a-z0-9]*)[^\/]*\/?&gt;/Ui', '', $history->diff) : '';
$content .= sprintf($this->lang->action->desc->diff2, $history->fieldLabel, $history->noTagDiff, $history->diff);
}
else
{
$content .= sprintf($this->lang->action->desc->diff1, $history->fieldLabel, $history->old, $history->new);
}
}
return $content;
}
/**
* Print actions of an object.
*
* @param object $action
* @param string $desc
* @access public
* @return void
*/
public function printAction($action, $desc = '')
{
if(!isset($action->objectType) or !isset($action->action)) return false;
$objectType = $action->objectType;
$actionType = strtolower($action->action);
/**
* Set the desc string of this action.
*
* 1. If the module of this action has defined desc of this actionType, use it.
* 2. If no defined in the module language, search the common action define.
* 3. If not found in the lang->action->desc, use the $lang->action->desc->common or $lang->action->desc->extra as the default.
*/
if(empty($desc))
{
if(($action->objectType == 'story' or $action->objectType == 'demand') and $action->action == 'reviewed' and strpos($action->extra, ',') !== false)
{
$desc = $this->lang->$objectType->action->rejectreviewed;
}
elseif($action->objectType == 'productplan' and in_array($action->action, array('startedbychild','finishedbychild','closedbychild','activatedbychild', 'createchild')))
{
$desc = $this->lang->$objectType->action->changebychild;
}
elseif($action->objectType == 'module' and in_array($action->action, array('created', 'moved', 'deleted')))
{
$desc = $this->lang->$objectType->action->{$action->action};
}
elseif(strpos('createmr,editmr,removemr', $action->action) !== false and 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');
$this->app->loadLang('mr');
$desc = sprintf($this->lang->mr->$mrAction, $mrDate, $mrActor, $mrLink);
}
elseif(($this->config->edition == 'max' or $this->config->edition == 'ipd') and strpos($this->config->action->assetType, ",{$action->objectType},") !== false and $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))
{
$desc = $this->lang->$objectType->action->$actionType;
}
elseif(isset($this->lang->action->desc->$actionType))
{
$desc = $this->lang->action->desc->$actionType;
}
else
{
$desc = $action->extra ? $this->lang->action->desc->extra : $this->lang->action->desc->common;
}
}
$action->date = substr($action->date, 0, 19);
if($this->app->getViewType() == 'mhtml') $action->date = date('m-d H:i', strtotime($action->date));
/* Cycle actions, replace vars. */
foreach($action as $key => $value)
{
if($key == 'history') continue;
/* Desc can be an array or string. */
if(is_array($desc))
{
if($key == 'extra') continue;
if($action->objectType == 'story' and $action->action == 'reviewed' and strpos($action->extra, '|') !== false and $key == 'actor')
{
$desc['main'] = str_replace('$actor', $this->lang->action->superReviewer . ' ' . $value, $desc['main']);
}
else
{
$desc['main'] = str_replace('$' . $key, $value, $desc['main']);
}
}
else
{
if($actionType == 'restoredsnapshot' && in_array($action->objectType, array('vm', 'zanode')) && $value == 'defaultSnap') $value = $this->lang->$objectType->snapshot->defaultSnapName;
$desc = str_replace('$' . $key, $value, $desc);
}
}
/* If the desc is an array, process extra. Please bug/lang. */
if(is_array($desc))
{
$extra = strtolower($action->extra);
/* Fix bug #741. */
if(isset($desc['extra'])) $desc['extra'] = $this->lang->$objectType->{$desc['extra']};
$actionDesc = '';
if(isset($desc['extra'][$extra]))
{
$actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
else
{
$actionDesc = str_replace('$extra', $action->extra, $desc['main']);
}
if(($action->objectType == 'story' or $action->objectType == 'demand') and $action->action == 'reviewed')
{
if(strpos($action->extra, ',') !== false)
{
list($extra, $reason) = explode(',', $extra);
$desc['reason'] = $this->lang->$objectType->{$desc['reason']};
$actionDesc = str_replace(array('$extra', '$reason'), array($desc['extra'][$extra], $desc['reason'][$reason]), $desc['main']);
}
if(strpos($action->extra, '|') !== false)
{
list($extra, $isSuperReviewer) = explode('|', $extra);
$actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
}
}
if($action->objectType == 'story' and $action->action == 'synctwins')
{
if(!empty($extra) and strpos($extra, '|') !== false)
{
list($operate, $storyID) = explode('|', $extra);
$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']);
}
}
if($action->objectType == 'module' and strpos(',created,moved,', $action->action) !== false)
{
$moduleNames = $this->loadModel('tree')->getOptionMenu($action->objectID, 'story', 0, 'all', '');
$modules = explode(',', $action->extra);
$moduleNames = array_intersect_key($moduleNames, array_combine($modules, $modules));
$moduleNames = implode(', ', $moduleNames);
$actionDesc = str_replace('$extra', $moduleNames, $desc['main']);
}
elseif($action->objectType == 'module' and $action->action == 'deleted')
{
$module = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($action->objectID)->fetch();
$moduleNames = $this->loadModel('tree')->getOptionMenu($module->root, 'story', 0, 'all', '');
$actionDesc = str_replace('$extra', zget($moduleNames, $action->objectID), $desc['main']);
}
echo $actionDesc;
}
else
{
echo $desc;
}
}
/**
* Get actions as dynamic.
*
* @param string $account
* @param string $period
* @param string $orderBy
* @param int $limit
* @param string|int $productID all|int(like 123)|notzero all => include zero, notzero, greater than 0
* @param string|int $projectID same as productID
* @param string|int $executionID same as productID
* @param string $date
* @param string $direction
* @access public
* @return array
*/
public function getDynamic($account = 'all', $period = 'all', $orderBy = 'date_desc', $limit = 50, $productID = 'all', $projectID = 'all', $executionID = 'all', $date = '', $direction = 'next')
{
/* Computer the begin and end date of a period. */
$beginAndEnd = $this->computeBeginAndEnd($period);
extract($beginAndEnd);
/* Build has priv condition. */
$condition = '1=1';
$executions = array();
if(!$this->app->user->admin)
{
$aclViews = isset($this->app->user->rights['acls']['views']) ? $this->app->user->rights['acls']['views'] : array();
if($productID == 'all') $grantedProducts = empty($aclViews) || !empty($aclViews['product']) ? $this->app->user->view->products : '0';
if($projectID == 'all') $grantedProjects = empty($aclViews) || !empty($aclViews['project']) ? $this->app->user->view->projects : '0';
if($executionID == 'all') $grantedExecutions = empty($aclViews) || !empty($aclViews['execution']) ? $this->app->user->view->sprints : '0';
if(empty($grantedProducts)) $grantedProducts = '0';
if($productID == 'all' and $projectID == 'all')
{
$productCondition = '';
foreach(explode(',', $grantedProducts) as $product) $productCondition = empty($productCondition) ? "(execution = '0' and project = '0' and (product LIKE '%,$product,%'" : "$productCondition OR product LIKE '%,$product,%'";
if(!empty($productCondition)) $productCondition .= '))';
$projectCondition = "(execution = '0' and project != '0' and project " . helper::dbIN($grantedProjects) . ')';
$executionCondition = isset($grantedExecutions) ? "(execution != 0 and execution " . helper::dbIN($grantedExecutions) . ')' : "(execution != 0 and execution = '$executionID')";
}
elseif($productID == 'all' and is_numeric($projectID))
{
$products = $this->loadModel('product')->getProductPairsByProject($projectID);
$executions = $this->loadModel('execution')->fetchPairs($projectID) + array(0 => 0);
$grantedExecutions = isset($grantedExecutions) ? array_intersect(array_keys($executions), explode(',', $grantedExecutions)) : array_keys($executions);
$productCondition = '';
foreach(array_keys($products) as $product) $productCondition = empty($productCondition) ? "(execution = '0' and project = '0' and (product LIKE '%,$product,%'" : "$productCondition OR product LIKE '%,$product,%'";
if(!empty($productCondition)) $productCondition .= '))';
$projectCondition = "(execution = '0' and project = '$projectID')";
$executionCondition = "(execution != '0' and execution " . helper::dbIN($grantedExecutions) . ')';
}
elseif(is_numeric($productID) and $projectID == 'all')
{
$this->loadModel('product');
$projects = $this->product->getProjectPairsByProduct($productID);
$executions = $this->product->getExecutionPairsByProduct($productID) + array(0 => 0);
$grantedProjects = array_intersect(array_keys($projects), explode(',', $grantedProjects));
$grantedExecutions = isset($grantedExecutions) ? array_intersect(array_keys($executions), explode(',', $grantedExecutions)) : array_keys($executions);
$productCondition = "(execution = '0' and project = '0' and product like '%,$productID,%')";
$projectCondition = "(execution = '0' and project != '0' and project " . helper::dbIN($grantedProjects) . ')';
$executionCondition = "(execution != '0' and execution " . helper::dbIN($grantedExecutions) . ')';
}
$condition = "((product =',0,' or product = '0' or product=',,') AND project = '0' AND execution = '0')";
if(!empty($productCondition)) $condition .= " OR $productCondition";
if(!empty($projectCondition)) $condition .= " OR $projectCondition";
if(!empty($executionCondition)) $condition .= " OR $executionCondition";
}
$actionCondition = $this->getActionCondition();
if(!$actionCondition and !$this->app->user->admin and isset($this->app->user->rights['acls']['actions'])) return array();
/* Restrict query data in this year when no limit for big data. */
$beginDate = '';
if($period == 'all')
{
$year = date('Y');
$beginDate = $year . '-01-01';
/* When query all dynamic then query the data of the last two years at most. */
if($this->app->getMethodName() == 'dynamic') $beginDate = $year - 1 . '-01-01';
}
if(is_numeric($projectID))
{
$openedDate = $this->dao->select('openedDate')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch('openedDate');
$beginDate = $openedDate > $beginDate ? $openedDate : $beginDate;
}
$condition = "(($condition) OR `objectType` IN ('doc', 'doclib'))";
$noMultipleExecutions = $this->dao->select('id')->from(TABLE_PROJECT)->where('multiple')->eq(0)->andWhere('type')->in('sprint,kanban')->fetchPairs('id', 'id');
if($noMultipleExecutions) $condition .= " OR (`objectID` NOT " . helper::dbIN($noMultipleExecutions) . " AND `objectType` = 'execution')";
$programCondition = empty($this->app->user->view->programs) ? '0' : $this->app->user->view->programs;
$condition .= " OR (`objectID` in ($programCondition) AND `objectType` = 'program')";
$efforts = $this->dao->select('id')->from(TABLE_EFFORT)
->where("($condition)")
->beginIF($period != 'all')
->beginIF(isset($begin))->andWhere('date')->gt($begin)->fi()
->beginIF(isset($end))->andWhere('date')->lt($end)->fi()
->fi()
->beginIF($beginDate)->andWhere('date')->ge($beginDate)->fi()
->fetchPairs();
$efforts = !empty($efforts) ? implode(',', $efforts) : 0;
$condition .= " OR (`objectID` in ($efforts) AND `objectType` = 'effort')";
$condition = "($condition)";
/* Get actions. */
$actionTable = in_array($period, $this->config->action->latestDateList) ? TABLE_ACTIONRECENT : TABLE_ACTION;
$actions = $this->dao->select('*')->from($actionTable)
->where('objectType')->notIN($this->config->action->ignoreObjectType4Dynamic)
->andWhere('action')->notIN($this->config->action->ignoreActions4Dynamic)
->beginIF($this->config->vision == 'lite')->andWhere('objectType')->notin('product')->fi()
->beginIF($this->config->systemMode == 'light')->andWhere('objectType')->notin('program')->fi()
->andWhere('vision')->eq($this->config->vision)
->beginIF($period != 'all')
->andWhere('date')->gt($begin)
->andWhere('date')->lt($end)
->fi()
->beginIF($date)->andWhere('date' . ($direction == 'next' ? '<' : '>') . "'{$date}'")->fi()
->beginIF($beginDate)->andWhere('date')->ge($beginDate)->fi()
->beginIF($account != 'all')->andWhere('actor')->eq($account)->fi()
->beginIF(is_numeric($productID))->andWhere('product')->like("%,$productID,%")->fi()
->beginIF(is_numeric($projectID))->andWhere('project')->eq($projectID)->fi()
->beginIF(!empty($executions))->andWhere('execution')->in(array_keys($executions))->fi()
->beginIF(is_numeric($executionID))->andWhere('execution')->eq($executionID)->fi()
->beginIF($productID == 'notzero')->andWhere('product')->gt(0)->andWhere('product')->notlike('%,0,%')->fi()
->beginIF($projectID == 'notzero')->andWhere('project')->gt(0)->fi()
->beginIF($executionID == 'notzero')->andWhere('execution')->gt(0)->fi()
->andWhere($condition)
->beginIF($actionCondition)->andWhere("($actionCondition)")->fi()
->orderBy($orderBy)
->limit($limit)
->fetchAll();
if(!$actions) return array();
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'action');
return $this->transformActions($actions);
}
/**
* Get dynamic show action.
*
* @return String
*/
public function getActionCondition()
{
if($this->app->user->admin) return '';
$actionCondition = '';
if(isset($this->app->user->rights['acls']['actions']))
{
if(empty($this->app->user->rights['acls']['actions'])) return '';
foreach($this->app->user->rights['acls']['actions'] as $moduleName => $actions)
{
if(isset($this->lang->mainNav->$moduleName) and !empty($this->app->user->rights['acls']['views']) and !isset($this->app->user->rights['acls']['views'][$moduleName])) continue;
$actionCondition .= "(`objectType` = '$moduleName' and `action` " . helper::dbIN($actions) . ") or ";
}
$actionCondition = trim($actionCondition, 'or ');
}
return $actionCondition;
}
/**
* Get dynamic by search.
*
* @param array $products
* @param array $projects
* @param array $executions
* @param int $queryID
* @param string $orderBy
* @param int $limit
* @param string $date
* @param string $direction
* @access public
* @return array
*/
public function getDynamicBySearch($products, $projects, $executions, $queryID, $orderBy = 'date_desc', $limit = 50, $date = '', $direction = 'next')
{
$query = $queryID ? $this->loadModel('search')->getQuery($queryID) : '';
/* Get the sql and form status from the query. */
if($query)
{
$this->session->set('actionQuery', $query->sql);
$this->session->set('actionForm', $query->form);
}
if($this->session->actionQuery == false) $this->session->set('actionQuery', ' 1 = 1');
$allProducts = "`product` = 'all'";
$allProjects = "`project` = 'all'";
$allExecutions = "`execution` = 'all'";
$actionQuery = $this->session->actionQuery;
$productID = 0;
if(preg_match("/`product` = '(\d*)'/", $actionQuery, $out))
{
$productID = $out[1];
}
/* If the sql not include 'product', add check purview for product. */
if(strpos($actionQuery, $allProducts) !== false)
{
$actionQuery = str_replace($allProducts, '1', $actionQuery);
}
/* If the sql not include 'project', add check purview for project. */
if(strpos($actionQuery, $allProjects) !== false)
{
$actionQuery = str_replace($allProjects, '1', $actionQuery);
}
/* If the sql not include 'execution', add check purview for execution. */
if(strpos($actionQuery, $allExecutions) !== false)
{
$actionQuery = str_replace($allExecutions, '1', $actionQuery);
}
$actionQuery = str_replace("`product` = '$productID'", "`product` LIKE '%,$productID,%'", $actionQuery);
if($date) $actionQuery = "($actionQuery) AND " . ('date' . ($direction == 'next' ? '<' : '>') . "'{$date}'");
/* If this vision is lite, delete product actions. */
if($this->config->vision == 'lite') $actionQuery .= " AND objectType != 'product'";
$actionQuery .= " AND vision = '" . $this->config->vision . "'";
$actions = $this->getBySQL($actionQuery, $orderBy, $limit);
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'action');
if(!$actions) return array();
return $this->transformActions($actions);
}
/**
* Get actions by SQL.
*
* @param string $sql
* @param string $orderBy
* @param int $limit
* @access public
* @return array
*/
public function getBySQL($sql, $orderBy, $limit = 50)
{
$actionCondition = $this->getActionCondition();
if(is_array($actionCondition)) return array();
return $actions = $this->dao->select('*')->from(TABLE_ACTION)
->where($sql)
->beginIF(!empty($actionCondition))->andWhere("($actionCondition)")->fi()
->orderBy($orderBy)
->limit($limit)
->fetchAll();
}
/**
* Transform the actions for display.
*
* @param array $actions
* @access public
* @return object
*/
public function transformActions($actions)
{
$this->app->loadLang('todo');
$this->app->loadLang('stakeholder');
$this->app->loadLang('branch');
$this->app->loadLang('execution');
/* Get commiters and the same department users. */
$commiters = $this->loadModel('user')->getCommiters();
$deptUsers = isset($this->app->user->dept) ? $this->loadModel('dept')->getDeptUserPairs($this->app->user->dept, 'id') : '';
/* Get object names, object projects and requirements by actions. */
$relatedData = $this->getRelatedDataByActions($actions);
$objectNames = $relatedData['objectNames'];
$relatedProjects = $relatedData['relatedProjects'];
$requirements = $relatedData['requirements'];
$projectIdList = array();
foreach($relatedProjects as $objectType => $idList) $projectIdList += $idList;
/* If idList include ',*,' Format ',*,' to '*'. */
foreach($projectIdList as $key => $idList)
{
$idList = explode(',', $idList);
foreach($idList as $id) $projectIdList[] = $id;
unset($projectIdList[$key]);
}
if($projectIdList) $projectIdList = array_unique($projectIdList);
$shadowProducts = $this->dao->select('id')->from(TABLE_PRODUCT)->where('shadow')->eq(1)->fetchPairs();
$projectMultiples = $this->dao->select('id,type,multiple')->from(TABLE_PROJECT)->where('id')->in($projectIdList)->fetchAll('id');
$docList = $this->loadModel('doc')->getPrivDocs('', 0, 'all');
$apiList = $this->loadModel('api')->getPrivApis();
$docLibList = $this->doc->getLibs('hasApi');
foreach($actions as $i => $action)
{
if($action->objectType == 'doc' and !isset($docList[$action->objectID])) unset($actions[$i]);
if($action->objectType == 'api' and !isset($apiList[$action->objectID])) unset($actions[$i]);
if($action->objectType == 'doclib' and !isset($docLibList[$action->objectID])) unset($actions[$i]);
if($action->objectType == 'product' AND isset($shadowProducts[$action->objectID]))
{
unset($actions[$i]);
continue;
}
/* Add name field to the actions. */
$action->objectName = isset($objectNames[$action->objectType][$action->objectID]) ? $objectNames[$action->objectType][$action->objectID] : '';
if($action->objectType == 'program' and strpos('syncexecution,syncproject,syncprogram', $action->action) !== false)
{
$action->objectName .= $this->lang->action->label->startProgram;
}
elseif($action->objectType == 'branch' and $action->action == 'mergedbranch')
{
if($action->objectID == 0) $action->objectName = $this->lang->branch->main;
$action->objectName = '"' . $action->extra . ' "' . $this->lang->action->to . ' "' . $action->objectName . '"';
}
elseif($action->objectType == 'user')
{
$user = $this->dao->select('id,realname')->from(TABLE_USER)->where('id')->eq($action->objectID)->fetch();
if($user) $action->objectName = $user->realname;
}
elseif($action->objectType == 'kanbancard' and strpos($action->action, 'imported') !== false and $action->action != 'importedcard')
{
$objectType = str_replace('imported', '', $action->action);
$objectTable = zget($this->config->objectTables, $objectType);
$objectName = ($objectType == 'productplan' or $objectType == 'ticket') ? 'title' : 'name';
$action->objectName = $this->dao->select($objectName)->from($objectTable)->where('id')->eq($action->extra)->fetch($objectName);
}
elseif(strpos(',module,chartgroup,', ",$action->objectType,") !== false and !empty($action->extra) and $action->action != 'deleted')
{
$modules = $this->dao->select('id,name')->from(TABLE_MODULE)->where('id')->in(explode(',', $action->extra))->fetchPairs('id');
$action->objectName = implode(',', $modules);
}
elseif($action->objectType == 'mr' and $action->action == 'deleted')
{
$action->objectName = $action->extra;
}
elseif($action->objectType == 'pivot')
{
$pivotNames = json_decode($action->objectName, true);
$action->objectName = zget($pivotNames, $this->app->getClientLang(), '');
if(empty($action->objectName))
{
$pivotNames = array_filter($pivotNames);
$action->objectName = reset($pivotNames);
}
}
$projectID = isset($relatedProjects[$action->objectType][$action->objectID]) ? $relatedProjects[$action->objectType][$action->objectID] : 0;
$actionType = strtolower($action->action);
$objectType = strtolower($action->objectType);
$action->originalDate = $action->date;
$action->date = date(DT_MONTHTIME2, strtotime($action->date));
$action->actionLabel = isset($this->lang->$objectType->$actionType) ? $this->lang->$objectType->$actionType : $action->action;
$action->actionLabel = isset($this->lang->action->label->$actionType) ? $this->lang->action->label->$actionType : $action->actionLabel;
$action->objectLabel = $this->getObjectLabel($objectType, $action->objectID, $actionType, $requirements);
/* If action type is login or logout, needn't link. */
if($actionType == 'svncommited' or $actionType == 'gitcommited') $action->actor = zget($commiters, $action->actor);
/* Get gitlab, gitea or gogs objectname. */
if(empty($action->objectName) and (substr($objectType, 0, 6) == 'gitlab' or substr($objectType, 0, 5) == 'gitea' or substr($objectType, 0, 4) == 'gogs')) $action->objectName = $action->extra;
/* Other actions, create a link. */
$this->setObjectLink($action, $deptUsers, $shadowProducts, zget($projectMultiples, $projectID, ''));
/* Set merge request link. */
if((empty($action->objectName) or $action->action == 'deleted') and $action->objectType == 'mr') $action->objectLink = '';
$action->major = (isset($this->config->action->majorList[$action->objectType]) && in_array($action->action, $this->config->action->majorList[$action->objectType])) ? 1 : 0;
}
return $actions;
}
/**
* Get related data by actions.
*
* @param array $actions
* @access public
* @return array
*/
public function getRelatedDataByActions($actions)
{
$this->loadModel('user');
$objectNames = array();
$relatedProjects = array();
$requirements = array();
$objectTypes = array();
foreach($actions as $object) $objectTypes[$object->objectType][$object->objectID] = $object->objectID;
foreach($objectTypes as $objectType => $objectIdList)
{
if(!isset($this->config->objectTables[$objectType]) and $objectType != 'makeup') continue; // If no defination for this type, omit it.
$table = $objectType == 'makeup' ? '`' . $this->config->db->prefix . 'overtime`' : $this->config->objectTables[$objectType];
$field = zget($this->config->action->objectNameFields, $objectType, '');
if(empty($field)) continue;
if($table != TABLE_TODO)
{
$objectName = array();
$relatedProject = array();
if(strpos(",{$this->config->action->needGetProjectType},", ",{$objectType},") !== false)
{
$objectInfo = $this->dao->select("id, project, `$field` AS name")->from($table)->where('id')->in($objectIdList)->fetchAll();
if($objectType == 'gapanalysis') $users = $this->user->getPairs('noletter');
foreach($objectInfo as $object)
{
$objectName[$object->id] = $objectType == 'gapanalysis' ? zget($users, $object->name) : $object->name;
$relatedProject[$object->id] = $object->project;
}
}
elseif($objectType == 'project' or $objectType == 'execution')
{
$objectInfo = $this->dao->select("id, project, `$field` AS name")->from($table)->where('id')->in($objectIdList)->fetchAll();
foreach($objectInfo as $object)
{
$objectName[$object->id] = $object->name;
$relatedProject[$object->id] = $object->project > 0 ? $object->project : $object->id;
}
}
elseif($objectType == 'story')
{
$objectInfo = $this->dao->select('id,title,type')->from($table)->where('id')->in($objectIdList)->fetchAll();
foreach($objectInfo as $object)
{
$objectName[$object->id] = $object->title;
if($object->type == 'requirement') $requirements[$object->id] = $object->id;
}
}
elseif($objectType == 'reviewcl')
{
$objectInfo = $this->dao->select('id,title')->from($table)->where('id')->in($objectIdList)->fetchAll();
foreach($objectInfo as $object) $objectName[$object->id] = $object->title;
}
elseif($objectType == 'team')
{
$objectInfo = $this->dao->select('id,team,type')->from(TABLE_PROJECT)->where('id')->in($objectIdList)->fetchAll();
foreach($objectInfo as $object)
{
$objectName[$object->id] = $object->team;
if($object->type == 'project') $relatedProject[$object->id] = $object->id;
}
}
elseif($objectType == 'stakeholder')
{
$objectName = $this->dao->select("t1.id, t2.realname")->from($table)->alias('t1')
->leftJoin(TABLE_USER)->alias('t2')->on("t1.`$field` = t2.account")
->where('t1.id')->in($objectIdList)
->fetchPairs();
}
elseif($objectType == 'branch')
{
$this->app->loadLang('branch');
$objectName = $this->dao->select("id,name")->from(TABLE_BRANCH)->where('id')->in($objectIdList)->fetchPairs();
if(in_array(BRANCH_MAIN, $objectIdList)) $objectName[BRANCH_MAIN] = $this->lang->branch->main;
}
elseif($objectType == 'privlang')
{
$objectName = $this->dao->select("objectID AS id, `$field` AS name")->from($table)->where('objectID')->in($objectIdList)->andWhere('objectType')->eq('priv')->fetchPairs();
}
else
{
$objectName = $this->dao->select("id, `$field` AS name")->from($table)->where('id')->in($objectIdList)->fetchPairs();
}
$objectNames[$objectType] = $objectName;
$relatedProjects[$objectType] = $relatedProject;
}
else
{
$todos = $this->dao->select("id, $field AS name, account, private, type, idvalue")->from($table)->where('id')->in($objectIdList)->fetchAll('id');
foreach($todos as $id => $todo)
{
if($todo->type == 'task') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_TASK)->fetch('name');
if($todo->type == 'bug') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_BUG)->fetch('title');
$objectNames[$objectType][$id] = $todo->name;
if($todo->private == 1 and $todo->account != $this->app->user->account) $objectNames[$objectType][$id] = $this->lang->todo->thisIsPrivate;
}
}
}
$objectNames['user'][0] = 'guest'; // Add guest account.
$relatedData['objectNames'] = $objectNames;
$relatedData['relatedProjects'] = $relatedProjects;
$relatedData['requirements'] = $requirements;
return $relatedData;
}
/**
* Get object label.
*
* @param string $objectType
* @param int $objectID
* @param string $actionType
* @param array $requirements
* @access public
* @return string
*/
public function getObjectLabel($objectType, $objectID, $actionType, $requirements)
{
$actionObjectLabel = $objectType;
if(isset($this->lang->action->label->$objectType))
{
$objectLabel = $this->lang->action->label->$objectType;
/* Replace story to requirement. */
if(isset($requirements[$objectID]) and is_string($objectLabel)) $objectLabel = str_replace($this->lang->SRCommon, $this->lang->URCommon, $objectLabel);
if(!is_array($objectLabel)) $actionObjectLabel = $objectLabel;
if(is_array($objectLabel) and isset($objectLabel[$actionType])) $actionObjectLabel = $objectLabel[$actionType];
if($objectType == 'module' and $actionType == 'deleted')
{
$moduleType = $this->dao->select('type')->from(TABLE_MODULE)->where('id')->eq($objectID)->fetch('type');
if($moduleType == 'doc')
{
$this->app->loadLang('doc');
$actionObjectLabel = $this->lang->doc->menuTitle;
}
}
}
if(($this->config->edition == 'max' or $this->config->edition == 'ipd') and $objectType == 'assetlib')
{
$libType = $this->dao->select('type')->from(TABLE_ASSETLIB)->where('id')->eq($objectID)->fetch('type');
if(strpos('story,issue,risk,opportunity,practice,component', $libType) !== false) $actionObjectLabel = $this->lang->action->label->{$libType . 'assetlib'};
}
return $actionObjectLabel;
}
/**
* Set objectLink
*
* @param object $action
* @param array $deptUsers
* @param array $shadowProducts
* @param object $project
* @access public
* @return object|bool
*/
public function setObjectLink($action, $deptUsers, $shadowProducts, $project = null)
{
$this->app->loadConfig('doc');
$action->objectLink = '';
$action->objectLabel = zget($this->lang->action->objectTypes, $action->objectLabel);
if(strpos($action->objectLabel, '|') !== false)
{
list($objectLabel, $moduleName, $methodName, $vars) = explode('|', $action->objectLabel);
/* Fix bug #2961. */
$isLoginOrLogout = $action->objectType == 'user' and ($action->action == 'login' or $action->action == 'logout');
$action->objectLabel = $objectLabel;
$action->product = trim($action->product, ',');
$noLinkObjects = array('program', 'project', 'product', 'execution');
if(in_array($action->objectType, $noLinkObjects))
{
$objectTable = zget($this->config->objectTables, $action->objectType);
$objectDeleted = $this->dao->select('deleted')->from($objectTable)->where('id')->eq($action->objectID)->fetch('deleted');
if($objectDeleted) return $action;
}
if(($this->config->edition == 'max' or $this->config->edition == 'ipd')
and strpos($this->config->action->assetType, ",{$action->objectType},") !== false
and empty($action->project) and empty($action->product) and empty($action->execution))
{
if($action->objectType == 'doc')
{
$assetLibType = $this->dao->select('assetLibType')->from(TABLE_DOC)->where('id')->eq($action->objectID)->fetch('assetLibType');
if($assetLibType) $method = $assetLibType == 'practice' ? 'practiceView' : 'componentView';
}
else
{
$method = $this->config->action->assetViewMethod[$action->objectType];
}
$action->objectLink = helper::createLink($moduleName, $methodName, sprintf($vars, $action->objectID));
if(isset($method)) $action->objectLink = helper::createLink('assetlib', $method, sprintf($vars, $action->objectID));
}
else
{
if($action->objectType == 'doclib')
{
$libID = $action->objectID;
$type = 'custom';
if(!empty($action->project)) $type = 'project';
if(!empty($action->execution)) $type = 'execution';
if(!empty($action->product)) $type = 'product';
$libObjectID = $type != 'custom' ? $action->$type : '';
$libObjectID = trim($libObjectID, ',');
if(empty($libObjectID) and $type != 'custom') return false;
$params = sprintf($vars, $type, $libObjectID, $libID);
}
elseif($action->objectType == 'api')
{
$api = $this->dao->select('id,lib,module')->from(TABLE_API)->where('id')->eq($action->objectID)->fetch();
$params = sprintf($vars, $api->lib, $api->id, $api->module);
}
elseif($action->objectType == 'branch')
{
$params = sprintf($vars, trim($action->product, ','));
}
elseif($action->objectType == 'kanbanspace')
{
$kanbanSpace = $this->dao->select('type')->from(TABLE_KANBANSPACE)->where('id')->eq($action->objectID)->fetch();
$params = sprintf($vars, $kanbanSpace->type);
}
elseif($action->objectType == 'kanbancolumn' or $action->objectType == 'kanbanlane')
{
$params = sprintf($vars, $action->extra);
}
elseif($action->objectType == 'module' and $action->action == 'deleted')
{
$params = sprintf($vars, trim($action->product, ','));
}
else
{
$params = sprintf($vars, $action->objectID);
}
$action->objectLink = helper::createLink($moduleName, $methodName, $params);
if($action->objectType == 'execution')
{
$execution = $this->loadModel('execution')->getById($action->objectID);
if(!empty($execution) and $execution->type == 'kanban') $action->objectLink = helper::createLink('execution', 'kanban', "executionID={$action->objectID}");
if($execution->type == 'stage' and $execution->attribute == 'research' and $action->vision == 'or')
{
$action->objectLink = '';
$action->objectLabel = $this->lang->execution->stage;
}
}
if($action->objectType == 'story')
{
$story = $this->loadModel('story')->getByID($action->objectID);
if(!empty($story))
{
$moduleName = $story->type;
$action->objectLink = isset($shadowProducts[$story->product]) ? helper::createLink('projectstory', 'view', "storyID=$story->id") : helper::createLink('story', 'view', "id=$story->id&version=0&param=0&storyType=$story->type");
}
}
if($action->objectType == 'doclib')
{
$docLib = $this->dao->select('type,product,project,execution,deleted')->from(TABLE_DOCLIB)->where('id')->eq($action->objectID)->fetch();
$docLib->objectID = strpos('product,project,execution', $docLib->type) !== false ? $docLib->{$docLib->type} : 0;
$appendLib = $docLib->deleted == '1' ? $action->objectID : 0;
if($docLib->type == 'api')
{
$module = 'api';
$method = 'index';
$params = "libID={$action->objectID}&moduleID=0&apiID=0&version=0&release=0&appendLib={$appendLib}";
if(!empty($docLib->project) or !empty($docLib->product))
{
$module = 'doc';
if(!empty($docLib->product))
{
$objectID = $docLib->product;
$method = 'productspace';
}
if(!empty($docLib->project))
{
$objectID = $docLib->project;
$method = 'projectspace';
}
$params = "objectID={$objectID}&libID={$action->objectID}";
}
$action->objectLink = helper::createLink($module, $method, $params);
}
else
{
$method = 'tablecontents';
if(isset($this->config->doc->spaceMethod[$docLib->type])) $method = $this->config->doc->spaceMethod[$docLib->type];
if($method == 'myspace') $params = "type=mine&libID={$action->objectID}";
if(!in_array($method, array('myspace', 'tablecontents'))) $params = "objectID={$docLib->objectID}&libID={$action->objectID}";
$action->objectLink = helper::createLink('doc', $method, $params);
}
}
elseif($action->objectType == 'user')
{
$action->objectLink = !isset($deptUsers[$action->objectID]) ? 'javascript:void(0)' : helper::createLink($moduleName, $methodName, sprintf($vars, $action->objectID));
}
}
if(!common::hasPriv($moduleName, $methodName) and !$isLoginOrLogout) $action->objectLink = '';
}
elseif($action->objectType == 'team')
{
if($action->project) $action->objectLink = common::hasPriv('project', 'team') ? helper::createLink('project', 'team', 'projectID=' . $action->project) : '';
if($action->execution) $action->objectLink = common::hasPriv('execution', 'team') ? helper::createLink('execution', 'team', 'executionID=' . $action->execution) : '';
}
elseif($action->objectType == 'privpackage')
{
$action->objectLink = '';
}
elseif($action->objectType == 'privlang')
{
$action->objectLink = '';
}
if($action->objectType == 'stakeholder' and $action->project == 0) $action->objectLink = '';
if($action->objectType == 'story' and $action->action == 'import2storylib') $action->objectLink = helper::createLink('assetlib', 'storyView', "storyID=$action->objectID");
if($action->objectType == 'story' and $this->config->vision == 'lite') $action->objectLink = helper::createLink('projectstory', 'view', "storyID=$action->objectID");
if(strpos(',kanbanregion,kanbancard,', ",{$action->objectType},") !== false)
{
$table = $this->config->objectTables[$action->objectType];
$kanbanID = $this->dao->select('kanban')->from($table)->where('id')->eq($action->objectID)->fetch('kanban');
$action->objectLink = helper::createLink('kanban', 'view', "kanbanID=$kanbanID");
}
if(strpos(',kanbanlane,kanbancolumn,', ",{$action->objectType},") !== false and empty($action->extra))
{
$table = $this->config->objectTables[$action->objectType];
$kanbanID = $this->dao->select('t2.kanban')->from($table)->alias('t1')
->leftJoin(TABLE_KANBANREGION)->alias('t2')->on('t1.region=t2.id')
->where('t1.id')->eq($action->objectID)
->fetch('kanban');
$action->objectLink = helper::createLink('kanban', 'view', "kanbanID=$kanbanID");
}
if($action->objectType == 'chartgroup') $action->objectLink = '';
if($action->objectType == 'branch' and $action->action == 'mergedbranch') $action->objectLink = 'javascript:void(0)';
if($action->objectType == 'module')
{
$moduleType = $this->dao->select('type')->from(TABLE_MODULE)->where('id')->eq($action->objectID)->fetch('type');
if($moduleType == 'doc')
{
$this->app->loadLang('doc');
$action->objectLabel = $this->lang->doc->menuTitle;
}
}
if($action->objectType == 'review') $action->objectLink = helper::createLink('review', 'view', "reviewID=$action->objectID");
/* Set app for no multiple project. */
if(!empty($action->objectLink) and !empty($project) and empty($project->multiple)) $action->objectLink .= '#app=project';
if($this->config->vision == 'lite' and $action->objectType == 'module') $action->objectLink .= '#app=project';
if($action->objectType == 'task' and $this->config->vision == 'or')
{
$task = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($action->objectID)->fetch();
if($task->type == 'research') $action->objectLink = helper::createLink('marketresearch', 'viewTask', "objectID=$action->objectID");
}
return $action;
}
/**
* Compute the begin date and end date of a period.
*
* @param string $period all|today|yesterday|twodaysago|latest2days|thisweek|lastweek|thismonth|lastmonth
* @access public
* @return array
*/
public function computeBeginAndEnd($period)
{
$this->app->loadClass('date');
$today = date('Y-m-d');
$tomorrow = date::tomorrow();
$yesterday = date::yesterday();
$twoDaysAgo = date::twoDaysAgo();
$period = strtolower($period);
if($period == 'all') return array('begin' => '1970-1-1', 'end' => LONG_TIME);
if($period == 'today') return array('begin' => $today, 'end' => $tomorrow);
if($period == 'yesterday') return array('begin' => $yesterday, 'end' => $today);
if($period == 'twodaysago') return array('begin' => $twoDaysAgo, 'end' => $yesterday);
if($period == 'latest3days') return array('begin' => $twoDaysAgo, 'end' => $tomorrow);
if($period == 'lastweek') return date::getLastWeek();
if($period == 'thismonth') return date::getThisMonth();
if($period == 'lastmonth') return date::getLastMonth();
return date::getThisWeek();
}
/**
* Print changes of every action.
*
* @param string $objectType
* @param array $histories
* @param bool $canChangeTag
* @access public
* @return void
*/
public function printChanges($objectType, $histories, $canChangeTag = true)
{
if(empty($histories)) return;
$maxLength = 0; // The max length of fields names.
$historiesWithDiff = array(); // To save histories without diff info.
$historiesWithoutDiff = array(); // To save histories with diff info.
/* Diff histories by hasing diff info or not. Thus we can to make sure the field with diff show at last. */
foreach($histories as $history)
{
$fieldName = $history->field;
$history->fieldLabel = (isset($this->lang->$objectType) && isset($this->lang->$objectType->$fieldName)) ? $this->lang->$objectType->$fieldName : $fieldName;
if($objectType == 'module') $history->fieldLabel = $this->lang->tree->$fieldName;
if($fieldName == 'fileName') $history->fieldLabel = $this->lang->file->$fieldName;
if(($length = strlen($history->fieldLabel)) > $maxLength) $maxLength = $length;
$history->diff ? $historiesWithDiff[] = $history : $historiesWithoutDiff[] = $history;
}
$histories = array_merge($historiesWithoutDiff, $historiesWithDiff);
foreach($histories as $history)
{
$history->fieldLabel = str_pad($history->fieldLabel, $maxLength, $this->lang->action->label->space);
if($history->diff != '')
{
$history->diff = str_replace(array('<ins>', '</ins>', '<del>', '</del>'), array('[ins]', '[/ins]', '[del]', '[/del]'), $history->diff);
$history->diff = ($history->field != 'subversion' and $history->field != 'git') ? htmlSpecialString($history->diff) : $history->diff; // Keep the diff link.
$history->diff = str_replace(array('[ins]', '[/ins]', '[del]', '[/del]'), array('<ins>', '</ins>', '<del>', '</del>'), $history->diff);
$history->diff = nl2br($history->diff);
$history->noTagDiff = $canChangeTag ? preg_replace('/&lt;\/?([a-z][a-z0-9]*)[^\/]*\/?&gt;/Ui', '', $history->diff) : '';
printf($this->lang->action->desc->diff2, $history->fieldLabel, $history->noTagDiff, $history->diff);
}
else
{
printf($this->lang->action->desc->diff1, $history->fieldLabel, $history->old, $history->new);
}
}
}
/**
* Delete action by objectType.
*
* @param string $objectType
* @access public
* @return void
*/
public function deleteByType($objectType)
{
$this->dao->delete()->from(TABLE_ACTION)->where('objectType')->eq($objectType)->exec();
}
/**
* Undelete a record.
*
* @param int $actionID
* @access public
* @return void
*/
public function undelete($actionID)
{
$action = $this->getById($actionID);
if($action->action != 'deleted') return;
if($action->objectType == 'execution')
{
$execution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($action->objectID)->fetch();
if($execution->deleted and empty($execution->project)) return print(js::error($this->lang->action->undeletedTips));
$projectCount = $this->dao->select('count(*) as count')->from(TABLE_PROJECT)->where('id')->eq($execution->project)->andWhere('deleted')->eq('0')->fetch('count');
if((int)$projectCount == 0) return print(js::error($this->lang->action->executionNoProject));
}
if($action->objectType == 'repo')
{
$repo = $this->dao->select('*')->from(TABLE_REPO)->where('id')->eq($action->objectID)->fetch();
if($repo and in_array($repo->SCM, array('Gitlab', 'Gitea', 'Gogs')))
{
$server = $this->dao->select('*')->from(TABLE_PIPELINE)->where('id')->eq($repo->serviceHost)->andWhere('deleted')->eq('0')->fetch();
if(empty($server)) return print(js::error($this->lang->action->repoNoServer));
}
}
if($action->objectType == 'product')
{
$product = $this->dao->select('id,name,code,acl')->from(TABLE_PRODUCT)->where('id')->eq($action->objectID)->fetch();
if($product->acl != 'open') $this->loadModel('user')->updateUserView($product->id, 'product');
}
elseif(in_array($action->objectType, array('program', 'project', 'execution')))
{
$project = $this->dao->select('id,acl,name,hasProduct')->from(TABLE_PROJECT)->where('id')->eq($action->objectID)->fetch();
$objecttype = $action->objectType == 'execution' ? 'sprint' : $action->objectType;
if($project->acl != 'open') $this->loadModel('user')->updateUserView($project->id, $objecttype);
/* Reduction shadow product. */
if(!$project->hasProduct and $action->objectType == 'project')
{
$productID = $this->loadModel('product')->getProductIDByProject($project->id);;
$this->dao->update(TABLE_PRODUCT)
->set('name')->eq($project->name)
->set('deleted')->eq(0)
->where('id')->eq($productID)
->exec();
}
}
elseif($action->objectType == 'module')
{
$module = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($action->objectID)->fetch();
$repeatName = $this->loadModel('tree')->checkUnique($module);
if($repeatName) return print(js::alert(sprintf($this->lang->tree->repeatName, $repeatName)));
}
elseif($action->objectType == 'reviewissue')
{
$issue = $this->dao->select('*')->from(TABLE_REVIEWISSUE)->where('id')->eq($action->objectID)->fetch();
if(!empty($issue->review))
{
$review = $this->dao->select('*')->from(TABLE_REVIEW)->where('id')->eq($issue->review)->fetch();
if($review->deleted)
{
$this->app->loadLang('reviewissue');
return print(js::alert($this->lang->reviewissue->undeleteAction));
}
}
}
elseif($action->objectType == 'release')
{
$release = $this->dao->select('*')->from(TABLE_RELEASE)->where('id')->eq($action->objectID)->fetch();
if($release->shadow) $this->dao->update(TABLE_BUILD)->set('deleted')->eq(0)->where('id')->eq($release->shadow)->exec();
}
if($action->objectType == 'case')
{
$caseScene = $this->dao->select('scene')->from(TABLE_CASE)->where('id')->eq($action->objectID)->fetch('scene');
if($caseScene)
{
$scene = $this->loadModel('testcase')->getSceneByID($caseScene);
if($scene->deleted) return print(js::error($this->lang->action->refusecase));
}
}
if($action->objectType == 'scene')
{
$scene = $this->loadModel('testcase')->getSceneByID($action->objectID);
if($scene->parent)
{
$parentScene = $this->testcase->getSceneByID($scene->parent);
if($parentScene->deleted) return print(js::error($this->lang->action->refusescene));
}
}
if($action->objectType == 'doc')
{
$docContent = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->eq($action->objectID)->orderBy('version desc')->limit(1)->fetch();
if($docContent->files) $this->dao->update(TABLE_FILE)->set('deleted')->eq('0')->where('id')->in($docContent->files)->exec();
}
/* Update deleted field in object table. */
$table = $this->config->objectTables[$action->objectType];
$this->dao->update($table)->set('deleted')->eq(0)->where('id')->eq($action->objectID)->exec();
$this->loadModel('product');
/* Revert userView products when undelete project or execution. */
if($action->objectType == 'project' or $action->objectType == 'execution')
{
$products = $this->product->getProducts($project->id, 'all', '', false);
if(!empty($products)) $this->loadModel('user')->updateUserView(array_keys($products), 'product');
if($action->objectType == 'execution')
{
$execution = $this->dao->select('id, type, project, grade, parent, status, deleted')->from(TABLE_EXECUTION)->where('id')->eq($action->objectID)->fetch();
$this->loadModel('common')->syncExecutionByChild($execution);
}
}
/* Revert doclib when undelete product or project. */
if($action->objectType == 'execution' or $action->objectType == 'product')
{
$this->dao->update(TABLE_DOCLIB)->set('deleted')->eq(0)->where($action->objectType)->eq($action->objectID)->exec();
}
if($action->objectType == 'demand')
{
$demand = $this->dao->select('*')->from(TABLE_DEMAND)->where('id')->eq($action->objectID)->fetch();
if($demand->parent) $this->loadModel('demand')->updateParentStatus($action->objectID, $demand->parent);
}
/* Revert productplan parent status. */
if($action->objectType == 'productplan') $this->loadModel('productplan')->changeParentField($action->objectID);
/* Update task status when undelete child task. */
if($action->objectType == 'task') $this->loadModel('task')->updateParentStatus($action->objectID);
/* Update action record in action table. */
$this->dao->update(TABLE_ACTION)->set('extra')->eq(ACTIONMODEL::BE_UNDELETED)->where('id')->eq($actionID)->exec();
$this->create($action->objectType, $action->objectID, 'undeleted');
}
/**
* Hide an object.
*
* @param int $actionID
* @access public
* @return void
*/
public function hideOne($actionID)
{
$action = $this->getById($actionID);
if($action->action != 'deleted') return;
$this->dao->update(TABLE_ACTION)->set('extra')->eq(self::BE_HIDDEN)->where('id')->eq($actionID)->exec();
$this->create($action->objectType, $action->objectID, 'hidden');
}
/**
* Hide all deleted objects.
*
* @access public
* @return void
*/
public function hideAll()
{
$this->dao->update(TABLE_ACTION)
->set('extra')->eq(self::BE_HIDDEN)
->where('action')->eq('deleted')
->andWhere('extra')->eq(self::CAN_UNDELETED)
->exec();
}
/**
* Update comment of a action.
*
* @param int $actionID
* @access public
* @return void
*/
public function updateComment($actionID)
{
$action = $this->getById($actionID);
$action->comment = trim(strip_tags($this->post->lastComment, $this->config->allowedTags));
/* Process action. */
$action = $this->loadModel('file')->processImgURL($action, 'comment', $this->post->uid);
$this->dao->update(TABLE_ACTION)
->set('date')->eq(helper::now())
->set('comment')->eq($action->comment)
->where('id')->eq($actionID)
->exec();
$this->file->updateObjectID($this->post->uid, $action->objectID, $action->objectType);
}
/**
* Build date group by actions
*
* @param array $actions
* @param string $direction
* @param string $type all|today|yesterday|thisweek|lastweek|thismonth|lastmonth
* @param string $orderBy date_desc|date_asc
* @access public
* @return array
*/
public function buildDateGroup($actions, $direction = 'next', $type = 'today', $orderBy = 'date_desc')
{
$dateGroup = array();
foreach($actions as $action)
{
$timeStamp = strtotime(isset($action->originalDate) ? $action->originalDate : $action->date);
$date = $type == 'all' ? date(DT_DATE3, $timeStamp) : date(DT_DATE4, $timeStamp);
$action->time = date(DT_TIME2, $timeStamp);
$dateGroup[$date][] = $action;
}
if($dateGroup)
{
$lastDate = date(DT_DATE1, $timeStamp);
$lastDateActions = $this->dao->select('*')->from(TABLE_ACTION)->where('`date`')->ge($lastDate)->andWhere('`date`')->le("{$lastDate} 23:59:59")->andWhere($this->session->actionQueryCondition)->orderBy($this->session->actionOrderBy)->fetchAll('id');
if(count($dateGroup[$date]) < count($lastDateActions))
{
unset($dateGroup[$date]);
$lastDateActions = $this->transformActions($lastDateActions);
foreach($lastDateActions as $action)
{
$timeStamp = strtotime(isset($action->originalDate) ? $action->originalDate : $action->date);
$date = $type == 'all' ? date(DT_DATE3, $timeStamp) : date(DT_DATE4, $timeStamp);
$action->time = date(DT_TIME2, $timeStamp);
$dateGroup[$date][] = $action;
}
}
}
/* Modify date to the corrret order. */
if($this->app->rawModule != 'company' and $direction != 'next')
{
$dateGroup = array_reverse($dateGroup);
}
elseif($this->app->rawModule == 'company')
{
if($direction == 'pre') $dateGroup = array_reverse($dateGroup);
if(($direction == 'next' and $orderBy == 'date_asc') or ($direction == 'pre' and $orderBy == 'date_desc'))
{
foreach($dateGroup as $key => $dateItem) $dateGroup[$key] = array_reverse($dateItem);
}
}
return $dateGroup;
}
/**
* Check Has pre or next.
*
* @param string $date
* @param string $direction
* @access public
* @return bool
*/
public function hasPreOrNext($date, $direction = 'next')
{
$condition = $this->session->actionQueryCondition;
/* Remove date condition for direction. */
$condition = preg_replace("/AND +[`]?date[`]?[ ]*[\<\>][ |=]*'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
$actions = $this->dao->select('id')->from(TABLE_ACTION)
->where('date' . ($direction == 'next' ? '<' : '>') . "'{$date}'")
->andWhere($condition)
->limit(1)
->fetch('id');
return !empty($actions);
}
/**
* Save global search object index information.
*
* @param string $objectType
* @param int $objectID
* @param string $actionType
* @access public
* @return bool
*/
public function saveIndex($objectType, $objectID, $actionType)
{
$this->loadModel('search');
$actionType = strtolower($actionType);
if(!isset($this->config->search->fields->$objectType)) return true;
if(strpos($this->config->search->buildAction, ",{$actionType},") === false and empty($_POST['comment'])) return true;
if($actionType == 'deleted' or $actionType == 'erased') return $this->search->deleteIndex($objectType, $objectID);
$field = $this->config->search->fields->$objectType;
$query = $this->search->buildIndexQuery($objectType, $testDeleted = false);
$data = $query->andWhere('t1.' . $field->id)->eq($objectID)->fetch();
if(empty($data)) return true;
$data->comment = '';
if($objectType == 'effort' and $data->objectType == 'task') return true;
if($objectType == 'case')
{
$caseStep = $this->dao->select('*')->from(TABLE_CASESTEP)->where('`case`')->eq($objectID)->andWhere('version')->eq($data->version)->fetchAll();
$data->desc = '';
$data->expect = '';
foreach($caseStep as $step)
{
$data->desc .= $step->desc . "\n";
$data->expect .= $step->expect . "\n";
}
}
$actions = $this->dao->select('*')->from(TABLE_ACTION)
->where('objectType')->eq($objectType)
->andWhere('objectID')->eq($objectID)
->orderBy('id asc')
->fetchAll();
foreach($actions as $action)
{
if($action->action == 'opened') $data->{$field->addedDate} = $action->date;
$data->{$field->editedDate} = $action->date;
if(!empty($action->comment)) $data->comment .= $action->comment . "\n";
}
$this->search->saveIndex($objectType, $data);
}
/**
* Print actions of an object for API(JIHU).
*
* @param object $action
* @access public
* @return void
*/
public function printActionForGitLab($action)
{
if(!isset($action->objectType) or !isset($action->action)) return false;
$objectType = $action->objectType;
$actionType = strtolower($action->action);
if(isset($this->lang->action->apiTitle->$actionType) and isset($action->extra))
{
/* If extra column is a username, then assemble link to that. */
if($action->action == "assigned")
{
$userDetails = $this->loadModel('user')->getUserDetailsForAPI($action->extra);
if(isset($userDetails[$action->extra]))
{
$userDetail = $userDetails[$action->extra];
$action->extra = "<a href='{$userDetail->url}' target='_blank'>{$action->extra}</a>";
}
}
echo sprintf($this->lang->action->apiTitle->$actionType, $action->extra);
}
elseif(isset($this->lang->action->apiTitle->$actionType) and !isset($action->extra))
{
echo $this->lang->action->apiTitle->$actionType;
}
else
{
echo $actionType;
}
}
/**
* Process action for API.
*
* @param array $actions
* @param array $users
* @param array $objectLang
* @access public
* @return array
*/
public function processActionForAPI($actions, $users = array(), $objectLang = array())
{
$actions = (array)$actions;
foreach($actions as $action)
{
$action->actor = zget($users, $action->actor);
if($action->action == 'assigned') $action->extra = zget($users, $action->extra);
if(strpos($action->actor, ':') !== false) $action->actor = substr($action->actor, strpos($action->actor, ':') + 1);
ob_start();
$this->printAction($action);
$action->desc = ob_get_contents();
ob_end_clean();
if($action->history)
{
foreach($action->history as $i => $history)
{
$history->fieldName = zget($objectLang, $history->field);
$action->history[$i] = $history;
}
}
}
return array_values($actions);
}
/**
* Process dynamic for API.
*
* @param array $dynamics
* @access public
* @return array
*/
public function processDynamicForAPI($dynamics)
{
$users = $this->loadModel('user')->getList();
$simplifyUsers = array();
foreach($users as $user)
{
$simplifyUser = new stdclass();
$simplifyUser->id = $user->id;
$simplifyUser->account = $user->account;
$simplifyUser->realname = $user->realname;
$simplifyUser->avatar = $user->avatar;
$simplifyUsers[$user->account] = $simplifyUser;
}
$actions = array();
foreach($dynamics as $key => $dynamic)
{
if($dynamic->objectType == 'user') continue;
$simplifyUser = zget($simplifyUsers, $dynamic->actor, '');
$actor = $simplifyUser;
if(empty($simplifyUser))
{
$actor = new stdclass();
$actor->id = 0;
$actor->account = $dynamic->actor;
$actor->realname = $dynamic->actor;
$actor->avatar = '';
}
$dynamic->actor = $actor;
$actions[] = $dynamic;
}
return $actions;
}
/**
* Build search form.
*
* @param int $queryID
* @param string $actionURL
* @access public
* @return void
*/
public function buildTrashSearchForm($queryID, $actionURL)
{
$this->config->trash->search['actionURL'] = $actionURL;
$this->config->trash->search['queryID'] = $queryID;
$this->loadModel('search')->setSearchParams($this->config->trash->search);
}
/**
* Restore stages.
*
* @param array $stageList
* @access public
* @return void
*/
public function restoreStages($stageList)
{
$deletedActions = $this->dao->select('*')->from(TABLE_ACTION)
->where('objectID')->in(array_keys($stageList))
->andWhere('objectType')->eq('execution')
->andWhere('action')->eq('deleted')
->orderBy('id_desc')
->fetchGroup('objectID');
foreach($stageList as $stageID => $stage)
{
$deletedAction = $deletedActions[$stageID][0];
$this->dao->update(TABLE_EXECUTION)->set('deleted')->eq('0')->where('id')->eq($stageID)->exec();
$this->dao->update(TABLE_ACTION)->set('extra')->eq(ACTIONMODEL::BE_UNDELETED)->where('id')->eq($deletedAction->id)->exec();
$this->create($deletedAction->objectType, $deletedAction->objectID, 'undeleted');
}
}
/**
* Clear dynamic records older than one month.
*
* @access public
* @return bool
*/
public function cleanActions()
{
$cleanDate = zget($this->app->config->global, 'cleanActionsDate', '');
$today = helper::today();
if($cleanDate == $today) return true;
$this->loadModel('setting')->setItem('system.common.global.cleanActionsDate', $today);
$lastMonth = date('Y-m-d', strtotime('-1 month'));
$this->dao->delete()->from(TABLE_ACTIONRECENT)->where('date')->lt($lastMonth)->exec();
return !dao::isError();
}
/**
* Get dynamic count.
*
* @access public
* @return void
*/
public function getDynamicCount()
{
$condition = $this->session->actionQueryCondition;
$count = $this->dao->select('count(1) as count')->from(TABLE_ACTION)
->where($condition)
->fetch('count');
return $count;
}
}