* @package action * @version $Id: model.php 5028 2013-07-06 02:59:41Z wyd621@gmail.com $ * @link http://www.zentao.net */ ?> 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§ion=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, '' . join(', ', $linkedProducts) . ''); } } 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, '
'))
{
$before = explode('', $comment);
$after = explode('', $before[1]);
$htmlCode = $after[0];
return $before[0] . htmlspecialchars($htmlCode) . $after[1];
}
return strip_tags($comment) === $comment
? nl2br($comment)
: $comment;
}
/**
* 构建操作记录列表,便于前端组件进行渲染。
* Build action list for render by frontend component.
*
* @param array $actions
* @param array $users
* @param bool $commentEditable
* @access public
* @return array
*/
public function buildActionList(array $actions, array $users = null, $commentEditable = true): array
{
if(empty($users)) $users = $this->loadModel('user')->getPairs('noletter');
$list = array();
foreach($actions as $action)
{
$item = new stdClass();
if(strlen(trim(($action->comment))) !== 0)
{
$item->comment = $this->formatActionComment($action->comment);
$item->commentEditable = $commentEditable && end($actions) == $action && $action->actor == $this->app->user->account && common::hasPriv('action', 'editComment');
}
if($action->action === 'assigned' || $action->action === 'toaudit') $action->extra = zget($users, $action->extra);
$action->actor = zget($users, $action->actor);
if(str_contains($action->actor, ':')) $action->actor = substr($action->actor, strpos($action->actor, ':') + 1);
if(!empty($action->history)) $item->historyChanges = $this->renderChanges($action->objectType, $action->history);
$item->id = $action->id;
$item->action = $action->action;
$item->content = $this->renderAction($action);
$list[] = $item;
}
return $list;
}
/**
* Print changes of every action.
*
* @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('', '', '', ''), 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('', '', '', ''), $history->diff);
$history->diff = nl2br($history->diff);
$history->noTagDiff = $canChangeTag ? preg_replace('/<\/?([a-z][a-z0-9]*)[^\/]*\/?>/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¶m=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('', '', '', ''), 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('', '', '', ''), $history->diff);
$history->diff = nl2br($history->diff);
$history->noTagDiff = $canChangeTag ? preg_replace('/<\/?([a-z][a-z0-9]*)[^\/]*\/?>/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 = "{$action->extra}";
}
}
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;
}
}