| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707 |
- <?php
- /**
- * The model file of action module of ZenTaoPMS.
- *
- * @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
- * @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
- * @author Chunsheng Wang <chunsheng@cnezsoft.com>
- * @package action
- * @version $Id: model.php 5028 2013-07-06 02:59:41Z wyd621@gmail.com $
- * @link https://www.zentao.net
- */
- ?>
- <?php
- class actionModel extends model
- {
- const BE_UNDELETED = 0; // The deleted object has been undeleted.
- const CAN_UNDELETED = 1; // The deleted object can be undeleted.
- const BE_HIDDEN = 2; // The deleted object has been hidden.
- const MAXCOUNT = 1000;
- /**
- * 创建一个操作记录。
- * Create a action.
- *
- * @param string $objectType
- * @param int $objectID
- * @param string $actionType
- * @param string|bool $comment
- * @param string|int|float $extra the extra info of this action, according to different modules and actions, can set different extra.
- * @param string $actor
- * @param bool $autoDelete
- * @access public
- * @return int|bool
- */
- public function create($objectType, $objectID, $actionType, $comment = '', $extra = '', $actor = '', $autoDelete = true)
- {
- if(strtolower($actionType) == 'commented' && empty($comment)) return false;
- $actor = $actor ? $actor : (!empty($this->app->user->account) ? $this->app->user->account : 'system');
- $actionType = strtolower($actionType);
- $actor = ($actionType == 'openedbysystem' || $actionType == 'closedbysystem') ? '' : $actor;
- if($actor == 'guest' && $actionType == 'logout') return false;
- $objectType = str_replace('`', '', $objectType);
- $extra = (string)$extra;
- $action = new stdclass();
- $action->objectType = strtolower($objectType);
- $action->objectID = $objectID;
- $action->actor = $actor;
- $action->action = $actionType;
- $action->date = helper::now();
- $action->extra = (string)$extra;
- if(!$this->app->upgrading) $action->vision = $this->config->vision;
- if($objectType == 'story' && in_array($actionType, array('reviewpassed', 'reviewrejected', 'reviewclarified', 'reviewreverted', 'synctwins'))) $action->actor = $this->lang->action->system;
- /* 使用purifier处理注解。 */
- /* Use purifier to process comment. Fix bug #2683. */
- if(empty($comment)) $comment = '';
- $action->comment = fixer::stripDataTags($comment);
- $uid = $this->post->uid;
- if(is_string($uid)) $uid = array($uid);
- if(!is_array($uid)) $uid = array();
- $this->loadModel('file');
- foreach($uid as $value)
- {
- $action = $this->file->processImgURL($action, 'comment', $value);
- if($autoDelete) $this->file->autoDelete($value);
- }
- /* 获取对象的产品项目以及执行。 */
- /* 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();
- $hasProductTable = true;
- $hasRecentTable = true;
- if($this->app->upgrading)
- {
- $fromVersion = $this->loadModel('setting')->getItem('owner=system&module=common§ion=global&key=version');
- if(is_numeric($fromVersion[0]) && version_compare($fromVersion, '18.6', '<')) $hasRecentTable = false;
- if(strpos($fromVersion, 'pro') !== false) $hasRecentTable = false;
- if(strpos($fromVersion, 'biz') !== false && version_compare($fromVersion, 'biz8.6', '<')) $hasRecentTable = false;
- if(strpos($fromVersion, 'max') !== false && version_compare($fromVersion, 'max4.6', '<')) $hasRecentTable = false;
- if(strpos($fromVersion, 'ipd') !== false && version_compare($fromVersion, 'ipd1.0.1', '<')) $hasRecentTable = false;
- if(is_numeric($fromVersion[0]) && version_compare($fromVersion, '21.7.1', '<=')) $hasProductTable = false;
- if(strpos($fromVersion, 'pro') !== false) $hasProductTable = false;
- if(strpos($fromVersion, 'biz') !== false && version_compare($fromVersion, 'biz12.0', '<=')) $hasProductTable = false;
- if(strpos($fromVersion, 'max') !== false && version_compare($fromVersion, 'max7.0', '<=')) $hasProductTable = false;
- if(strpos($fromVersion, 'ipd') !== false && version_compare($fromVersion, 'ipd4.0', '<=')) $hasProductTable = false;
- }
- if($hasProductTable)
- {
- $productIdList = array_filter(explode(',', $action->product));
- foreach($productIdList as $productID) $this->dao->insert(TABLE_ACTIONPRODUCT)->set('action')->eq($actionID)->set('product')->eq($productID)->exec();
- }
- if($actionType == 'commented')
- {
- $oldAction = new stdclass();
- $oldAction->id = $actionID;
- $newAction = clone $oldAction;
- $this->file->processFileDiffsForObject('comment', $oldAction, $newAction);
- if(!empty($newAction->files))
- {
- $action->files = $newAction->files;
- $this->dao->update(TABLE_ACTION)->set('files')->eq($newAction->files)->where('id')->eq($actionID)->exec();
- }
- $changes = common::createChanges($oldAction, $newAction);
- if($changes) $this->logHistory($actionID, $changes);
- }
- if($hasRecentTable)
- {
- $action->id = $actionID;
- $this->dao->insert(TABLE_ACTIONRECENT)->data($action)->autoCheck()->exec();
- }
- $this->file->updateObjectID($uid, $objectID, $objectType);
- if(empty($this->app->installing) && empty($this->app->upgrading)) $this->loadModel('message')->send(strtolower($objectType), $objectID, $actionType, $actionID, $actor, $extra);
- $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;
- }
- /**
- * 访问任务或者bug时,更新action的read字段。
- * Update read field of action when view a task/bug.
- *
- * @param string $objectType
- * @param int $objectID
- * @access public
- * @return bool
- */
- 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();
- return !dao::isError();
- }
- /**
- * 获取对象的产品项目以及执行。
- * 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 = '')
- {
- /* 处理项目、执行、产品、计划相关。 */
- /* Process project, execution, product, plan related. */
- if(in_array($objectType, array('project', 'execution', 'product', 'program', 'marketresearch')))
- {
- list($product, $project, $execution) = $this->actionTao->getNoFilterRequiredRelation($objectType, $objectID);
- return array('product' => ',' . implode(',', $product) . ',', 'project' => $project, 'execution' => $execution);
- }
- /* 过滤不在配置项中的类型。 */
- /* Filter object types not in configuration items。 */
- $product = array(0);
- $project = $execution = 0;
- if(strpos($this->config->action->needGetRelateField, ",{$objectType},") !== false)
- {
- list($product, $project, $execution) = $this->actionTao->getNeedRelatedFields($objectType, $objectID, $actionType, $extra);
- if($actionType == 'unlinkedfromproject' || $actionType == 'linked2project') $project = (int)$extra ;
- if(in_array($actionType, array('unlinkedfromexecution', 'linked2execution', 'linked2kanban'))) $execution = (int)$extra;
- if($execution && !$project) $project = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($execution)->fetch('project');
- }
- return array('product' => is_array($product) ? ',' . implode(',', $product) . ',': ',' . $product . ',', 'project' => $project, 'execution' => $execution);
- }
- /**
- * 根据对象类型和对象ID获取操作记录。
- * Get actions by objectType and objectID.
- *
- * @param string $objectType
- * @param array|int $objectID
- * @access public
- * @return array
- */
- public function getList($objectType, $objectID)
- {
- $this->loadModel('file');
- $this->loadModel('workflow');
- $modules = $objectType == 'module' ? $this->dao->select('id')->from(TABLE_MODULE)->where('root')->in($objectID)->fetchPairs('id') : array();
- $commiters = $this->loadModel('user')->getCommiters();
- $actions = $this->actionTao->getActionListByTypeAndID($objectType, $objectID, $modules);
- $histories = $this->getHistory(array_keys($actions));
- $files = $this->file->groupByObjectID('comment', array_keys($actions));
- $flowList = array();
- if($objectType == 'project') $actions = $this->processProjectActions($actions);
- foreach($actions as $actionID => $action)
- {
- $actionName = strtolower($action->action);
- if($this->config->edition != 'open' && !isset($flowList[$action->objectType])) $flowList[$action->objectType] = $this->workflow->getByModule($action->objectType);
- if(substr($actionName, 0, 7) == 'linked2') $this->actionTao->getLinkedExtra($action, substr($actionName, 7));
- if(substr($actionName, 0, 12) == 'unlinkedfrom') $this->actionTao->getLinkedExtra($action, substr($actionName, 12));
- if(in_array($actionName, array('svncommited', 'gitcommited')) && isset($commiters[$action->actor])) $action->actor = $commiters[$action->actor];
- if(!in_array($action->objectType, array('feedback', 'ticket')) && $actionName == 'tostory') $this->actionTao->processToStoryActionExtra($action);
- if($actionName == 'moved' && $action->objectType != 'module') $this->actionTao->processActionExtra(TABLE_PROJECT, $action, 'name', 'execution', 'task');
- if($actionName == 'frombug' && common::hasPriv('bug', 'view')) $action->extra = html::a(helper::createLink('bug', 'view', "bugID={$action->extra}"), $action->extra);
- if($actionName == 'importedcard') $this->actionTao->processActionExtra(TABLE_KANBAN, $action, 'name', 'kanban', 'view', true);
- if($actionName == 'createchildren') $this->actionTao->processCreateChildrenActionExtra($action);
- if($actionName == 'createrequirements') $this->actionTao->processCreateRequirementsActionExtra($action);
- if($actionName == 'deletechildrendemand') $this->actionTao->processActionExtra(TABLE_DEMAND, $action, 'title', 'demand', 'view');
- if($actionName == 'createchildrendemand') $this->actionTao->processLinkStoryAndBugActionExtra($action, 'demand', 'view');
- if($actionName == 'buildopened') $this->actionTao->processActionExtra(TABLE_BUILD, $action, 'name', 'build', 'view');
- if($actionName == 'fromlib' && $action->objectType == 'case') $this->actionTao->processActionExtra(TABLE_TESTSUITE, $action, 'name', 'caselib', 'browse', false, helper::hasFeature('caselib'));
- if($actionName == 'changedbycharter' && $action->objectType == 'story') $this->actionTao->processActionExtra(TABLE_CHARTER, $action, 'name', 'charter', 'view');
- if(($actionName == 'finished' && $objectType == 'todo') || ($actionName == 'closed' && in_array($action->objectType, array('story', 'demand'))) || ($actionName == 'resolved' && $action->objectType == 'bug')) $this->actionTao->processAppendLinkByExtra($action);
- if($actionName == 'distributed' && $objectType == 'story') $this->actionTao->processActionExtra(TABLE_DEMAND, $action, 'title', 'demand', 'view', false, $this->config->vision != 'or' ? false : true);
- if(in_array($actionName, array('retracted', 'restored'))) $this->actionTao->processActionExtra(TABLE_STORY, $action, 'title', 'story', 'storyView');
- if(in_array($actionName, array('totask', 'linkchildtask', 'unlinkchildrentask', 'linkparenttask', 'unlinkparenttask', 'deletechildrentask', 'converttotask')) && $action->objectType != 'feedback') $this->actionTao->processActionExtra(TABLE_TASK, $action, 'name', 'task', 'view');;
- if(in_array($actionName, array('linkchildstory', 'unlinkchildrenstory', 'linkparentstory', 'unlinkparentstory', 'deletechildrenstory', 'createchildrenstory'))) $this->actionTao->processActionExtra(TABLE_STORY, $action, 'title', 'story', 'storyView');
- if(in_array($actionName, array('testtaskopened', 'testtaskstarted', 'testtaskclosed'))) $this->actionTao->processActionExtra(TABLE_TESTTASK, $action, 'name', 'testtask', 'view');
- if(in_array($actionName, array('importfromstorylib', 'importfromrisklib', 'importfromissuelib', 'importfromopportunitylib')) && in_array($this->config->edition, array('max', 'ipd'))) $this->actionTao->processActionExtra(TABLE_ASSETLIB, $action, 'name', 'assetlib', $action->objectType);
- if(in_array($actionName, array('opened', 'managed', 'edited')) && in_array($objectType, array('execution', 'project'))) $this->processExecutionAndProjectActionExtra($action);
- if(in_array($actionName, array('linkstory', 'unlinkstory', 'linkur', 'unlinkur', 'linkrelatedstory', 'unlinkrelatedstory'))) $this->actionTao->processLinkStoryAndBugActionExtra($action, 'story', 'storyView');
- if(in_array($actionName, array('linkbug', 'unlinkbug'))) $this->actionTao->processLinkStoryAndBugActionExtra($action, 'bug', 'view');
- if($actionName == 'repocreated') $action->extra = str_replace("class='iframe'", 'data-app="devops"', $action->extra);
- if($actionName == 'createdsnapshot' && in_array($action->objectType, array('vm', 'zanode')) && $action->extra == 'defaultSnap') $action->actor = $this->lang->action->system;
- if($actionName == 'syncgrade') $this->actionTao->processStoryGradeActionExtra($action);
- if(in_array($actionName, array('createdsubtabledata', 'editedsubtabledata', 'deletedsubtabledata'))) $action->extra = !empty($flowList[$action->objectType]->name) ? $flowList[$action->objectType]->name . $action->objectID : '';
- $action->history = zget($histories, $actionID, array());
- foreach($action->history as $history)
- {
- if($history->field == 'subversion' || $history->field == 'git')
- {
- $history->diff = str_replace(array("class='iframe'", '+'), array("data-size='{\"width\": 800, \"height\": 500}' data-toggle='modal'", '%2B'), $history->diff);
- }
- if(in_array($history->field, array('adjustmemory', 'adjustcpu', 'adjustvol', 'adjustmemorychange', 'adjustcpuchange', 'adjustvolchange')))
- {
- if(empty($history->newValue) && empty($history->new)) $history->newValue = $this->lang->action->noLimit;
- if(empty($history->oldValue) && empty($history->old)) $history->oldValue = $this->lang->action->noLimit;
- }
- $history->new = !empty($history->newValue) ? $history->newValue : $history->new;
- $history->old = !empty($history->oldValue) ? $history->oldValue : $history->old;
- $history = $this->file->replaceImgURL($history, 'old,new');
- }
- $action->comment = $this->file->setImgSize($action->comment, $this->config->action->commonImgSize);
- $action->files = !empty($files[$actionID]) ? $files[$actionID] : array();
- $actions[$actionID] = $action;
- }
- return $actions;
- }
- /**
- * 将类型、状态等键值转换为具体的值。
- * Process object type, status and etc.
- *
- * @param object $history
- * @access public
- * @return object
- */
- public function processHistory($history = null)
- {
- if(empty($history)) return $history;
- $users = $this->loadModel('user')->getPairs('noletter');
- $action = $this->dao->select('objectType,objectID')->from(TABLE_ACTION)->where('id')->eq($history->action)->fetch();
- $objectType = $action->objectType == 'story' ? $this->dao->select('type')->from(TABLE_STORY)->where('id')->eq($action->objectID)->fetch('type') : $action->objectType;
- $objectTypeList = array();
- $history->old = (string)$history->old;
- $history->new = (string)$history->new;
- if(!isset($objectTypeList[$objectType])) $this->app->loadLang($objectType);
- $objectTypeList[$objectType] = $objectType;
- if(isset($this->config->action->approvalFields[$history->field]))
- {
- $fieldListVar = $this->config->action->approvalFields[$history->field];
- $fieldList = isset($this->lang->action->{$fieldListVar}) ? $this->lang->action->{$fieldListVar} : array();
- if(isset($fieldList[$history->old])) $history->oldValue = $fieldList[$history->old];
- if(isset($fieldList[$history->new])) $history->newValue = $fieldList[$history->new];
- }
- elseif(isset($this->config->action->multipleObjectFields[$objectType][$history->field]))
- {
- $fieldListVar = $this->config->action->multipleObjectFields[$objectType][$history->field];
- $fieldList = isset($this->lang->{$objectType}->{$fieldListVar}) ? $this->lang->{$objectType}->{$fieldListVar} : array();
- if(!empty($history->old))
- {
- $history->oldValue = '';
- $oldValues = explode(',', $history->old);
- foreach($oldValues as $value) $history->oldValue .= zget($fieldList, $value) . ',';
- $history->oldValue = trim($history->oldValue, ',');
- }
- if(!empty($history->new))
- {
- $history->newValue = '';
- $newValues = explode(',', $history->new);
- foreach($newValues as $value) $history->newValue .= zget($fieldList, $value) . ',';
- $history->newValue = trim($history->newValue, ',');
- }
- }
- elseif(strpos(",{$this->config->action->userFields},", ",{$history->field},") !== false)
- {
- if(isset($users[$history->old])) $history->oldValue = $users[$history->old];
- if(isset($users[$history->new])) $history->newValue = $users[$history->new];
- }
- elseif(strpos(",{$this->config->action->multipleUserFields},", ",{$history->field},") !== false)
- {
- if(!empty($history->old))
- {
- $history->oldValue = '';
- $oldValues = explode(',', $history->old);
- foreach($oldValues as $value) $history->oldValue .= zget($users, $value) . ',';
- $history->oldValue = trim($history->oldValue, ',');
- }
- if(!empty($history->new))
- {
- $history->newValue = '';
- $newValues = explode(',', $history->new);
- foreach($newValues as $value) $history->newValue .= zget($users, $value) . ',';
- $history->newValue = trim($history->newValue, ',');
- }
- }
- else
- {
- $fieldListVar = isset($this->config->action->objectFields[$objectType][$history->field]) ? $this->config->action->objectFields[$objectType][$history->field] : $history->field . 'List';
- $fieldList = isset($this->lang->{$objectType}->{$fieldListVar}) ? $this->lang->{$objectType}->{$fieldListVar} : array();
- if(isset($fieldList[$history->old])) $history->oldValue = $fieldList[$history->old];
- if(isset($fieldList[$history->new])) $history->newValue = $fieldList[$history->new];
- }
- /* 如果是升级, 检查oldValue和newValue字段是否存在。以防止从老版本升级的时候,而这两个字段不存在,导致升级失败。 */
- if(!empty($this->app->upgrading) && (isset($history->oldValue) || isset($history->newValue)))
- {
- $existHistory = $this->dao->select('*')->from(TABLE_HISTORY)->limit(1)->fetch();
- if(!isset($existHistory->oldValue)) unset($history->oldValue, $history->newValue);
- }
- return $history;
- }
- /**
- * 将项目行动类型转换为通用行动类型。
- * 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';
- /* 处理action数据。 */
- /* Process actions data. */
- foreach($actions as $key => $action)
- {
- if($action->objectType != 'project' && !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|bool
- */
- public function getById($actionID)
- {
- $action = $this->actionTao->fetchBaseInfo($actionID);
- if(!$action) return false;
- /* 当action值为repocreated的时候拼接域名。 */
- /* 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 user first action.
- *
- * @param string $account
- * @access public
- * @return object
- */
- public function getAccountFirstAction($account)
- {
- return $this->dao->select('*')->from(TABLE_ACTION)->where('actor')->eq($account)->orderBy('id')->limit(1)->fetch();
- }
- /**
- * 获取已删除的对象。
- * 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 = null)
- {
- $noMultipleExecutions = $this->dao->select('id')->from(TABLE_EXECUTION)->where('multiple')->eq('0')->andWhere('type')->in('sprint,kanban')->fetchPairs();
- $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()
- ->beginIF($objectType == 'execution')->andWhere('objectID')->notIn($noMultipleExecutions)->fi()
- ->beginIF($objectType == 'all')
- ->andWhere('objectType', true)->ne('execution')
- ->orWhere('(objectType')->eq('execution')->andWhere('objectID')->notIn($noMultipleExecutions)
- ->markRight(2)
- ->fi()
- ->andWhere('objectType')->notIn($this->config->action->hiddenTrashObjects)
- ->andWhere('extra')->eq($extra)
- ->andWhere('vision')->eq($this->config->vision)
- ->orderBy($orderBy)
- ->page($pager)
- ->fetchAll('', false);
- if(empty($trashes)) return array();
- /* 按对象类型对已删除的对象进行分组,并获取名称字段。 */
- /* Group trashes by objectType, and get there name field. */
- $auditplanIdList = array();
- foreach($trashes as $object)
- {
- $object->objectType = str_replace('`', '', $object->objectType);
- if($object->objectType == 'auditplan') $auditplanIdList[$object->objectID] = $object->objectID;
- $typeTrashes[$object->objectType][] = $object->objectID;
- }
- $auditplanList = $this->dao->select('id,objectID,objectType')->from(TABLE_AUDITPLAN)->where('id')->in($auditplanIdList)->fetchAll('id');
- foreach($auditplanList as $auditplan) $typeTrashes[$auditplan->objectType][] = $auditplan->objectID;
- foreach($typeTrashes as $objectType => $objectIdList)
- {
- if(!isset($this->config->objectTables[$objectType])) continue;
- if($objectType == 'auditplan') continue;
- if(!isset($this->config->action->objectNameFields[$objectType])) continue;
- $table = $this->config->objectTables[$objectType];
- $field = $this->config->action->objectNameFields[$objectType];
- $objectIdList = array_unique($objectIdList);
- 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();
- }
- elseif($objectType == 'pivot')
- {
- $objectNames[$objectType] = $this->dao->select("t1.id, t2.{$field} AS name")->from($table)->alias('t1')
- ->leftJoin(TABLE_PIVOTSPEC)->alias('t2')->on('t1.id = t2.pivot and t1.version = t2.version')
- ->where('t1.id')->in($objectIdList)
- ->fetchPairs();
- }
- elseif($objectType == 'stakeholder')
- {
- $objectNames[$objectType] = $this->dao->select("t1.id, t2.realname AS name")->from($table)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.user = t2.account')
- ->where('t1.id')->in($objectIdList)
- ->fetchPairs();
- }
- else
- {
- $objectNames[$objectType] = $this->dao->select("id, {$field} AS name")->from($table)->where('id')->in($objectIdList)->filterTpl('skip')->fetchPairs();
- }
- }
- /* 将对象名称字段添加到回收站数据中。 */
- /* Add name field to the trashes. */
- foreach($trashes as $key => $trash)
- {
- if($trash->objectType == 'pipeline' && isset($objectNames['gitlab'][$trash->objectID])) $trash->objectType = 'gitlab';
- if($trash->objectType == 'pipeline' && isset($objectNames['jenkins'][$trash->objectID])) $trash->objectType = 'jenkins';
- if($trash->objectType == 'auditplan')
- {
- $realObjectID = isset($auditplanList[$trash->objectID]) ? $auditplanList[$trash->objectID]->objectID : 0;
- $realObjectType = isset($auditplanList[$trash->objectID]) ? $auditplanList[$trash->objectID]->objectType : '';
- $trash->objectName = isset($objectNames[$realObjectType][$realObjectID]) ? $objectNames[$realObjectType][$realObjectID] : '';
- }
- else
- {
- $trash->objectName = isset($objectNames[$trash->objectType][$trash->objectID]) ? $objectNames[$trash->objectType][$trash->objectID] : '';
- }
- }
- return $trashes;
- }
- /**
- * 通过查询获取回收站内的对象。
- * Get deleted objects by search.
- *
- * @param string $objectType
- * @param string $type all|hidden
- * @param string|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 && $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;
- $table = $this->config->objectTables[$objectType];
- $nameField = isset($this->config->action->objectNameFields[$objectType]) ? 't2.' . '`' . $this->config->action->objectNameFields[$objectType] . '`' : '';
- $trashQuery = $this->session->trashQuery;
- $trashQuery = str_replace(array('`objectID`', '`actor`', '`date`'), array('t1.`objectID`', 't1.`actor`', 't1.`date`'), $trashQuery);
- if($nameField) $trashQuery = preg_replace("/`objectName`/", $nameField, $trashQuery);
- $queryFields = $objectType != 'pipeline' ? "t1.*, {$nameField} AS objectName" : 't1.*, t1.objectType AS type, t2.name AS objectName, t2.type AS objectType';
- $trashes = $this->dao->select($queryFields)->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)
- ->andWhere('objectType')->notIn($this->config->action->hiddenTrashObjects)
- ->beginIF($objectType != 'pipeline' && $objectType != 'all')->andWhere('t1.objectType')->eq($objectType)->fi()
- ->beginIF($objectType == 'pipeline')
- ->andWhere('(t2.type')->eq('gitlab')
- ->orWhere('t2.type')->eq('jenkins')
- ->markRight(1)
- ->fi()
- ->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;
- $noMultipleExecutions = $this->dao->select('id')->from(TABLE_EXECUTION)->where('multiple')->eq('0')->andWhere('type')->in('sprint,kanban')->fetchPairs();
- return $this->dao->select('objectType')->from(TABLE_ACTION)
- ->where('action')->eq('deleted')
- ->andWhere('extra')->eq($extra)
- ->andWhere('objectType', true)->ne('execution')
- ->orWhere('(objectType')->eq('execution')->andWhere('objectID')->notIn($noMultipleExecutions)
- ->markRight(2)
- ->andWhere('vision')->eq($this->config->vision)
- ->andWhere('objectType')->notIn($this->config->action->hiddenTrashObjects)
- ->fetchAll('objectType');
- }
- /**
- * 获取一个操作的历史记录。
- * Get histories of an action.
- *
- * @param array|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 bool
- */
- public function logHistory($actionID, $changes)
- {
- if(empty($actionID) || empty($changes)) return false;
- foreach($changes as $change)
- {
- $change = is_array($change) ? json_decode(json_encode($change)) : $change;
- $change->action = $actionID;
- $change = $this->processHistory($change);
- $this->dao->insert(TABLE_HISTORY)->data($change)->exec();
- if(dao::isError()) return false;
- }
- return true;
- }
- /**
- * 打印一个对象的所有操作记录。
- * Print actions of an object.
- *
- * @param object $action
- * @param string $desc
- * @access public
- * @return void
- */
- public function renderAction($action, $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.
- */
- $this->app->loadLang($objectType == 'module' ? 'tree' : $objectType);
- 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(!$mrActor) $mrActor = $action->actor;
- if(is_numeric($mrLink)) $mrLink = helper::createLink('mr', 'view', "mrID={$mrLink}");
- 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(in_array($action->extra, array('autobyparent', 'autobychild')) && isset($this->lang->{$objectType}) && isset($this->lang->{$objectType}->action->{$action->extra . $actionType}))
- {
- $desc = $this->lang->{$objectType}->action->{$action->extra . $actionType};
- }
- elseif($action->objectType == 'instance' && isset($this->lang->action->desc->{$actionType}))
- {
- $desc = $this->lang->action->desc->{$actionType};
- $extra = json_decode($action->extra);
- if(in_array($actionType, array('adjustmemory', 'adjustcpu', 'adjustvol')))
- {
- $action->newValue = $action->comment;
- $action->comment = '';
- if(!empty($action->history))
- {
- $itemHistory = current($action->history);
- $action->newValue = $itemHistory->new;
- $action->oldValue = $itemHistory->old;
- $desc = $this->lang->action->desc->{$actionType . 'change'};
- }
- }
- 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', '');
- }
- 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($action->action == 'run' && $action->objectType == 'case' && strpos($action->extra, ',') !== false)
- {
- list($testtaskID, $result) = explode(',', $action->extra);
- if($result)
- {
- $link = '';
- $testtask = $this->dao->select('name')->from(TABLE_TESTTASK)->where('id')->eq($testtaskID)->fetch();
- if($testtask) $link = common::hasPriv('testtask', 'cases') ? html::a(helper::createLink('testtask', 'cases', "testtaskID=$testtaskID"), ' ' . $testtask->name) : ' ' . $testtask->name;
- if(!isset($this->lang->testcase->resultList)) $this->app->loadLang('testcase');
- $desc = sprintf($this->lang->action->desc->runresult, "<strong>{$link}</strong>", $result, zget($this->lang->testcase->resultList, $result));
- }
- else
- {
- $desc = $this->lang->action->desc->run;
- }
- }
- 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']);
- }
- elseif(!is_array($value))
- {
- $desc['main'] = str_replace('$' . $key, (string)$value, $desc['main']);
- }
- }
- else
- {
- if(in_array($actionType, array('restoredsnapshot', 'createdsnapshot')) && in_array($action->objectType, array('vm', 'zanode')) && $value == 'defaultSnap') $value = $this->lang->{$objectType}->snapshot->defaultSnapName;
- if(!is_array($value) && !is_object($value)) $desc = str_replace('$' . $key, (string)$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']))
- {
- if($objectType == 'story')
- {
- $story = $this->fetchByID($action->objectID, $objectType);
- if($story->type != 'story') $this->app->loadLang($story->type);
- $desc['extra'] = $this->lang->{$story->type}->{$desc['extra']};
- }
- else
- {
- $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']);
- }
- }
- $isCloseStory = $action->objectType == 'story' && $action->action == 'closed';
- if($isCloseStory && !empty($extra) && strpos($extra, '|') !== false)
- {
- list($extra) = explode('|', $extra);
- if(!empty($desc['extra'][$extra])) $actionDesc = str_replace('$extra', $desc['extra'][$extra], $desc['main']);
- }
- if(($action->objectType == 'story' || $action->objectType == 'demand') && $action->action == 'fromboard')
- {
- $actionExtra = html::a(helper::createLink('board', 'view', "canvasID={$action->extra}"), $action->extra);
- $actionDesc = str_replace('$extra', $actionExtra, $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', (string)zget($moduleNames, $action->objectID), $desc['main']);
- }
- if($action->objectType == 'board' && in_array($action->action, array('importstory', 'importdemand', 'importrequirement', 'importepic', 'convertdemand', 'convertepic', 'convertrequirement', 'convertstory')))
- {
- if($action->action == 'importstory' || $action->action == 'convertstory')
- {
- $story = $this->loadModel('story')->fetchById((int)$action->extra);
- $link = helper::createLink('story', 'view', "storyID={$action->extra}");
- }
- if($action->action == 'convertrequirement' || $action->action == 'importrequirement')
- {
- $story = $this->loadModel('story')->fetchById((int)$action->extra);
- $link = helper::createLink('requirement', 'view', "storyID={$action->extra}");
- }
- if($action->action == 'importdemand' || $action->action == 'convertdemand')
- {
- $story = $this->loadModel('demand')->fetchByID((int)$action->extra);
- $link = helper::createLink('demand', 'view', "demandID={$action->extra}");
- }
- if($action->action == 'importepic' || $action->action == 'convertepic')
- {
- $story = $this->loadModel('story')->fetchByID((int)$action->extra);
- $link = helper::createLink('epic', 'view', "epicID={$action->extra}");
- }
- $link .= $this->config->requestType == 'GET' ? '&onlybody=yes' : '?onlybody=yes';
- $replace = $story ? html::a('', "#$action->extra {$story->title}", '', "class='story-link' data-href='$link'") : '';
- $actionDesc = str_replace('$extra', $replace, $desc['main']);
- }
- return $actionDesc;
- }
- /**
- * 格式化操作备注。
- * Format action comment.
- *
- * @param string $comment
- * @access public
- * @return string
- */
- public function formatActionComment($comment)
- {
- if(strpos($comment, '<pre class="prettyprint lang-html">') !== false)
- {
- $before = explode('<pre class="prettyprint lang-html">', $comment);
- $after = explode('</pre>', $before[1]);
- $htmlCode = $after[0];
- return $before[0] . htmlspecialchars($htmlCode) . $after[1];
- }
- return strip_tags($comment) === $comment
- ? nl2br($comment)
- : $comment;
- }
- /**
- * 构建操作记录列表,便于前端组件进行渲染。
- * Build action list for render by frontend component.
- *
- * @param array $actions
- * @param array $users
- * @param bool $commentEditable
- * @access public
- * @return array
- */
- public function buildActionList($actions, $users = null, $commentEditable = true)
- {
- if(empty($users)) $users = $this->loadModel('user')->getPairs('noletter');
- $list = array();
- $endAction = end($actions);
- $account = $this->app->user->account;
- foreach($actions as $action)
- {
- $item = new stdClass();
- if(strlen(trim(($action->comment))) !== 0)
- {
- $item->comment = $this->formatActionComment($action->comment);
- $currentUserCanEdit = $action->id == $endAction->id && $action->actor == $account && $action->action == 'commented';
- $item->commentEditable = $commentEditable && common::hasPriv('action', 'editComment') && $currentUserCanEdit;
- }
- if($action->action === 'assigned' || $action->action === 'toaudit')
- {
- $action->extra = zget($users, $action->extra);
- if(strpos($action->extra, ':') !== false) $action->extra = substr($action->extra, strpos($action->extra, ':') + 1);
- }
- if(!isset($action->rawActor)) $action->rawActor = $action->actor;
- $action->actor = zget($users, $action->actor);
- if(strpos($action->actor, ':') !== false) $action->actor = substr($action->actor, strpos($action->actor, ':') + 1);
- if(!empty($action->history)) $item->historyChanges = $this->renderChanges($action->objectType, $action->objectID, $action->history);
- $item->id = $action->id;
- $item->action = $action->action;
- $item->hasRendered = true;
- $item->content = $this->renderAction($action);
- if(!empty($action->files)) $item->files = array_values($action->files);
- if(strlen(trim(($action->comment))) !== 0)
- {
- $isCurrentUserCreated = $action->rawActor == $account;
- $item->comment = $this->formatActionComment($action->comment);
- $item->commentEditable = $commentEditable && $endAction->id == $action->id && $isCurrentUserCreated && common::hasPriv('action', 'editComment');
- }
- if($action->objectType == 'instance' && in_array($action->action, array('adjustmemory', 'adjustcpu', 'adjustvol'))) unset($item->comment);
- $list[] = $item;
- }
- return $list;
- }
- /**
- * 打印一个对象的所有操作记录。
- * Print actions of an object.
- *
- * @param object $action
- * @param string $desc
- * @access public
- * @return void
- */
- public function printAction($action, $desc = '')
- {
- $content = $this->renderAction($action, $desc);
- if(is_string($content)) echo $content;
- return;
- }
- /**
- * 获取动态。
- * 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, $date, $direction);
- $actionCondition = $this->getActionCondition();
- if(!$actionCondition && !$this->app->user->admin && isset($this->app->user->rights['acls']['actions']))
- {
- $this->session->set('actionQueryCondition', null, $this->app->tab);
- return array();
- }
- /* 构建权限搜索条件。 */
- /* Build has priv search condition. */
- $condition = "`objectType` != 'effort'";
- $noMultipleExecutions = $this->dao->select('id')->from(TABLE_PROJECT)->where('multiple')->eq(0)->andWhere('type')->in('sprint,kanban')->fetchPairs();
- if($noMultipleExecutions) $condition = "({$condition}) AND (`objectType` != 'execution' OR (`objectType` = 'execution' AND `objectID`" . (count($noMultipleExecutions) == 1 ? ' != ' . reset($noMultipleExecutions) : ' NOT ' . helper::dbIN($noMultipleExecutions)) . "))";
- $actions = $this->actionTao->getActionListByCondition("({$condition})", $beginAndEnd['begin'], $beginAndEnd['end'], $account, $productID, $projectID, $executionID, $actionCondition, $orderBy, $limit);
- if(!$actions) return array();
- $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'action');
- return $this->transformActions($actions);
- }
- /**
- * 通过产品获取动态。
- * Get actions as dynamic by product.
- *
- * @param int $productID
- * @param string $account
- * @param string $period
- * @param string $orderBy
- * @param int $limit
- * @param string $date
- * @param string $direction
- * @access public
- * @return array
- */
- public function getDynamicByProduct($productID, $account = 'all', $period = 'all', $orderBy = 'date_desc', $limit = 50, $date = '', $direction = 'next')
- {
- return $this->getDynamic($account, $period, $orderBy, $limit, (int)$productID, 'all', 'all', $date, $direction);
- }
- /**
- * 通过项目获取动态。
- * Get actions as dynamic by project.
- *
- * @param int $projectID
- * @param string $account
- * @param string $period
- * @param string $orderBy
- * @param int $limit
- * @param string $date
- * @param string $direction
- * @access public
- * @return array
- */
- public function getDynamicByProject($projectID, $account = 'all', $period = 'all', $orderBy = 'date_desc', $limit = 50, $date = '', $direction = 'next')
- {
- return $this->getDynamic($account, $period, $orderBy, $limit, 'all', (int)$projectID, 'all', $date, $direction);
- }
- /**
- * 通过执行获取动态。
- * Get actions as dynamic by execution.
- *
- * @param int $executionID
- * @param string $account
- * @param string $period
- * @param string $orderBy
- * @param int $limit
- * @param string $date
- * @param string $direction
- * @access public
- * @return array
- */
- public function getDynamicByExecution($executionID, $account = 'all', $period = 'all', $orderBy = 'date_desc', $limit = 50, $date = '', $direction = 'next')
- {
- return $this->getDynamic($account, $period, $orderBy, $limit, 'all', 'all', (int)$executionID, $date, $direction);
- }
- /**
- * 通过账户获取动态。
- * Get actions as dynamic by account.
- *
- * @param string $account
- * @param string $period
- * @param string $orderBy
- * @param int $limit
- * @param string $date
- * @param string $direction
- * @access public
- * @return array
- */
- public function getDynamicByAccount($account = '', $period = 'all', $orderBy = 'date_desc', $limit = 50, $date = '', $direction = 'next')
- {
- if(empty($account)) $account = $this->app->user->account;
- return $this->getDynamic($account, $period, $orderBy, $limit, 'all', 'all', 'all', $date, $direction);
- }
- /**
- * 通过视野获取用户可访问的动态类型。
- * Get the action types that the user can access through the vision.
- *
- * @param string $module
- * @access public
- * @return string
- */
- public function getActionCondition($module = '')
- {
- if($this->app->user->admin) return '';
- $actionCondition = array();
- if(isset($this->app->user->rights['acls']['actions']))
- {
- if(empty($this->app->user->rights['acls']['actions'])) return '1 != 1';
- if($module && !isset($this->app->user->rights['acls']['actions'][$module])) return '1 != 1';
- foreach($this->app->user->rights['acls']['actions'] as $moduleName => $actions)
- {
- if($module && $module != $moduleName) continue;
- if(isset($this->lang->mainNav->{$moduleName}) && !empty($this->app->user->rights['acls']['views']) && !isset($this->app->user->rights['acls']['views'][$moduleName])) continue;
- $actionCondition[] = "(`objectType` = '{$moduleName}' AND `action` " . helper::dbIN(array_keys($actions)) . ")";
- }
- }
- return implode(' OR ', $actionCondition);
- }
- /**
- * 搜索获取动态。
- * Get dynamic by search.
- *
- * @param int $queryID
- * @param string $orderBy
- * @param int $limit
- * @param string $date
- * @param string $direction
- * @access public
- * @return array
- */
- public function getDynamicBySearch($queryID, $orderBy = 'date_desc', $limit = 50, $date = '', $direction = 'next')
- {
- $query = $queryID ? $this->loadModel('search')->getQuery($queryID) : '';
- /* 获取sql和表单内容。 */
- /* Get sql and form content. */
- 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 query condition include all products, no limit product. */
- if(strpos($actionQuery, $allProducts) !== false) $actionQuery = str_replace($allProducts, '1 = 1', $actionQuery);
- /* 如果查询条件中包含所有项目的查询条件,不限制项目。 */
- /* If the query condition include all projects, no limit project. */
- if(strpos($actionQuery, $allProjects) !== false) $actionQuery = str_replace($allProjects, '1 = 1', $actionQuery);
- /* 如果查询条件中包含所有执行的查询条件,不限制执行。 */
- /* If the query condition include all executions, no limit execution. */
- if(strpos($actionQuery, $allExecutions) !== false) $actionQuery = str_replace($allExecutions, '1 = 1', $actionQuery);
- if($productID) $actionQuery = str_replace(" `product` = '{$productID}'", " t2.`product` = '{$productID}'", $actionQuery);
- if($date) $actionQuery = "({$actionQuery}) AND " . ('date' . ($direction == 'next' ? '<' : '>') . "'{$date}'");
- /* 如果当前版本为lite,则过滤掉产品相关的动态。 */
- /* 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');
- return $actions ? $this->transformActions($actions) : array();
- }
- /**
- * 通过sql获取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();
- $actionCondition = str_replace(' `action`', ' action.`action`', $actionCondition);
- $hasProduct = preg_match('/t2\.([`"]?)product/', $sql);
- /* 构建权限搜索条件。 */
- /* Build has priv search condition. */
- $condition = "`objectType` != 'effort'";
- $noMultipleExecutions = $this->dao->select('id')->from(TABLE_PROJECT)->where('multiple')->eq(0)->andWhere('type')->in('sprint,kanban')->fetchPairs();
- if($noMultipleExecutions) $condition = "({$condition}) AND (`objectType` != 'execution' OR (`objectType` = 'execution' AND `objectID`" . (count($noMultipleExecutions) == 1 ? ' != ' . reset($noMultipleExecutions) : ' NOT ' . helper::dbIN($noMultipleExecutions)) . "))";
- return $this->dao->select('action.*')->from(TABLE_ACTION)->alias('action')
- ->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
- ->where('objectType')->notIN($this->config->action->ignoreObjectType4Dynamic)
- ->andWhere('action.action')->notIN($this->config->action->ignoreActions4Dynamic)
- ->andWhere("({$sql})")
- ->andWhere("({$condition})")
- ->beginIF(!empty($actionCondition))->andWhere("($actionCondition)")->fi()
- ->orderBy($orderBy)
- ->limit($limit)
- ->fetchAll('id', false);
- }
- /**
- * 转换动态用于显示。
- * Transform the actions for display.
- *
- * @param array $actions
- * @access public
- * @return array
- */
- public function transformActions($actions)
- {
- /* 获取评论用户以及当前登陆用户的本部门用户。 */
- /* Get the commenters and the users of the current user's department. */
- $commiters = $this->loadModel('user')->getCommiters();
- $deptUsers = isset($this->app->user->dept) ? $this->loadModel('dept')->getDeptUserPairs((int)$this->app->user->dept, 'id') : '';
- /* 通过action获取对象名称,所属项目以及需求。 */
- /* Get object names, object projects and requirements by actions. */
- list($objectNames, $relatedProjects, $requirements, $epics) = $this->getRelatedDataByActions($actions);
- $projectGroups = array();
- foreach($relatedProjects as $objectType => $idList) $projectGroups = array_merge($projectGroups, $idList);
- /* If idList include ',*,' Format ',*,' to '*'. */
- $projectIdList = array();
- foreach($projectGroups as $idList)
- {
- $idList = explode(',', (string)$idList);
- foreach($idList as $id) $projectIdList[] = $id;
- }
- if($projectIdList) $projectIdList = array_unique($projectIdList);
- if($projectIdList) $projects = $this->dao->select('id,name,isTpl')->from(TABLE_PROJECT)->where('project')->in($projectIdList)->andWhere('deleted')->eq('0')->fetchAll('id');
- $docIdList = array();
- $apiIdList = array();
- $docLibIdList = array();
- foreach($actions as $action)
- {
- if($action->objectType == 'doc' || $action->objectType == 'doctemplate') $docIdList[$action->objectID] = $action->objectID;
- if($action->objectType == 'doclib') $docLibIdList[$action->objectID] = $action->objectID;
- if($action->objectType == 'api') $apiIdList[$action->objectID] = $action->objectID;
- }
- /* 获取需要验证的元素列表。 */
- /* Get the list of elements that need to be verified. */
- $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 = array();
- $apiList = array();
- $docLibList = array();
- $this->loadModel('doc');
- if($docIdList)
- {
- $docList = $this->dao->select("`id`,`addedBy`,`type`,`lib`,`acl`,`users`,`readUsers`,`groups`,`readGroups`,`status`,`path`,`deleted`")->from(TABLE_DOC)->where('id')->in($docIdList)->fetchAll('id');
- $docList = $this->doc->batchCheckPrivDoc($docList);
- }
- if($docLibIdList)
- {
- $docLibList = $this->dao->select('*')->from(TABLE_DOCLIB)->where('id')->in($docLibIdList)->fetchAll('id');
- foreach($docLibList as $docLib)
- {
- if(!$this->doc->checkPrivLib($docLib)) unset($docLibList[$docLib->id]);
- }
- }
- if($apiIdList)
- {
- $apiList = $this->dao->select('*')->from(TABLE_DOCLIB)->where('id')->in($docLibIdList)->fetchAll('id');
- foreach($apiList as $api)
- {
- if(!$this->doc->checkPrivLib($api)) unset($apiList[$api->id]);
- }
- }
- foreach($actions as $i => $action)
- {
- /* 如果doc,api,doclib,product类型对应的对象不存在,则从actions中删除。*/
- /* If the object corresponding to the doc, api, doclib, and product types does not exist, it will be deleted from actions. */
- if(!$this->actionTao->checkIsActionLegal($action, $shadowProducts, $docList, $apiList, $docLibList))
- {
- unset($actions[$i]);
- continue;
- }
- $actionType = strtolower($action->action);
- $objectType = strtolower($action->objectType);
- $projectID = isset($relatedProjects[$action->objectType][$action->objectID]) ? $relatedProjects[$action->objectType][$action->objectID] : 0;
- $this->loadModel($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, $epics);
- $action->major = isset($this->config->action->majorList[$action->objectType]) && in_array($action->action, $this->config->action->majorList[$action->objectType]) ? 1 : 0;
- if($actionType == 'svncommited' || $actionType == 'gitcommited') $action->actor = zget($commiters, $action->actor);
- /* 设置对象的名称和链接。 */
- /* Set object name and set object link. */
- $this->actionTao->addObjectNameForAction($action, $objectNames, $objectType);
- $this->setObjectLink($action, $deptUsers, $shadowProducts, zget($projectMultiples, $projectID, ''), $projectIdList);
- if($action->objectType == 'project' && !empty($projects[$action->objectID]->isTpl))
- {
- $action->objectLabel = $this->lang->project->template;
- $action->objectLink = helper::createLink('project', 'execution', "status=undone&projectID={$action->objectID}");
- }
- elseif($action->objectType == 'project' && $action->action == 'managedeliverable')
- {
- $action->objectLink = helper::createLink('project', 'deliverable', "projectID={$action->objectID}"); // 交付物链接
- }
- elseif($action->objectType == 'execution' && $action->action == 'managedeliverable')
- {
- $action->objectLink = helper::createLink('execution', 'view', "executionID={$action->objectID}"); // 交付物链接
- }
- }
- return $actions;
- }
- /**
- * 通过actions获取关联的数据。
- * Get related data by actions.
- *
- * @param array $actions
- * @access public
- * @return array
- */
- public function getRelatedDataByActions($actions)
- {
- /* Init object type array. */
- $objectTypes = array();
- foreach($actions as $object) $objectTypes[$object->objectType][$object->objectID] = $object->objectID;
- if(isset($objectTypes['todo'])) $this->app->loadLang('todo');
- if(isset($objectTypes['branch'])) $this->app->loadLang('branch');
- $users = isset($objectTypes['gapanalysis']) || isset($objectTypes['stakeholder']) ? $this->loadModel('user')->getPairs('noletter') : array();
- $objectNames = $relatedProjects = $requirements = $epics = array();
- foreach($objectTypes as $objectType => $objectIdList)
- {
- if(!isset($this->config->objectTables[$objectType]) && strpos(',makeup,pivot,', ",{$objectType},") === false) continue; // If no defination for this type, omit it.
- if(isset($this->config->objectTables[$objectType])) $table = $this->config->objectTables[$objectType];
- if($objectType == 'makeup') $table = TABLE_OVERTIME;
- if($objectType == 'pivot') $table = TABLE_PIVOTSPEC;
- if($objectType == 'auditplan') continue;
- $field = zget($this->config->action->objectNameFields, $objectType, '');
- if(empty($field)) continue;
- /* Get object name, related projects, requirements. */
- list($objectName, $relatedProject, $requirements, $epics) = $this->getObjectRelatedData($table, $objectType, $objectIdList, $field, $users, $requirements, $epics);
- if($objectType == 'branch' && in_array(BRANCH_MAIN, $objectIdList)) $objectName[BRANCH_MAIN] = $this->lang->branch->main;
- $objectNames[$objectType] = $objectName;
- $relatedProjects[$objectType] = $relatedProject;
- }
- $objectNames['user'][0] = 'guest'; // Add guest account.
- return array($objectNames, $relatedProjects, $requirements, $epics);
- }
- /**
- * 获取对象的标签。
- * Get object label.
- *
- * @param string $objectType
- * @param int $objectID
- * @param string $actionType
- * @param array $requirements
- * @param array $epics
- * @access public
- * @return string
- */
- public function getObjectLabel($objectType, $objectID, $actionType, $requirements, $epics)
- {
- $actionObjectLabel = $objectType;
- if(isset($this->lang->action->label->{$objectType}))
- {
- $objectLabel = $this->lang->action->label->{$objectType};
- /* 用户故事替换为需求。 */
- /* Replace story to requirement. */
- if(isset($requirements[$objectID]) && is_string($objectLabel)) $objectLabel = str_replace($this->lang->SRCommon, $this->lang->URCommon, $objectLabel);
- if(isset($epics[$objectID]) && is_string($objectLabel)) $objectLabel = str_replace($this->lang->SRCommon, $this->lang->ERCommon, $objectLabel);
- if(!is_array($objectLabel)) $actionObjectLabel = $objectLabel;
- if(is_array($objectLabel) && isset($objectLabel[$actionType])) $actionObjectLabel = $objectLabel[$actionType];
- if($objectType == 'module')
- {
- $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($objectType == 'system' && strpos(strtolower($actionType), 'backup') !== false) $actionObjectLabel = '';
- }
- if(in_array($this->config->edition, array('max', 'ipd')) && $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;
- }
- /**
- * 检查Action是否设置链接。
- * Check if action has link.
- *
- * @param object $action
- * @param array $projectIdList
- * @access public
- * @return bool
- */
- public function checkForSetLink($action, $projectIdList = array())
- {
- static $parents = array();
- if(empty($parents) && !empty($projectIdList)) $parents = $this->dao->select('parent')->from(TABLE_PROJECT)->where('project')->in($projectIdList)->andWhere('deleted')->eq('0')->fetchPairs('parent', 'parent');
- if($action->objectType == 'execution' && isset($parents[$action->objectID])) return false;
- if($this->app->user->admin) return true;
- if($action->execution && strpos(",{$this->app->user->view->sprints},", ",{$action->execution},") === false) return false;
- if($action->project && strpos(",{$this->app->user->view->projects},", ",{$action->project},") === false) return false;
- $hasPriv = false;
- $products = array();
- if($action->product) $products = array_unique(array_filter(explode(',', $action->product)));
- if($products)
- {
- foreach($products as $productID)
- {
- if(strpos(",{$this->app->user->view->products},", ",{$productID},") !== false) $hasPriv = true;
- }
- if(!$hasPriv) return false;
- }
- return true;
- }
- /**
- * 设置action的objectLink属性。
- * Set the objectLink attribute of action.
- *
- * @param object $action
- * @param array $deptUsers
- * @param array $shadowProducts
- * @param object|string $project
- * @param array $projectIdList
- * @access public
- * @return object|bool
- */
- public function setObjectLink($action, $deptUsers, $shadowProducts, $project = "", $projectIdList = array())
- {
- $action->objectLink = $moduleName = $methodName = $params = '';
- $action->objectLabel = zget($this->lang->action->objectTypes, $action->objectLabel);
- $canSetLink = $this->checkForSetLink($action, $projectIdList);
- $canParseLink = strpos($action->objectLabel, '|') !== false;
- if(!$canParseLink && !$canSetLink) return $action;
- if($canParseLink)
- {
- list($objectLabel, $moduleName, $methodName, $vars) = explode('|', $action->objectLabel);
- $action->objectLabel = $objectLabel;
- $action->product = trim((string)$action->product, ',');
- if($action->objectType == 'module') return $action;
- if(!$canSetLink) return $action;
- if(in_array($action->objectType, array('program', 'project', 'product', 'execution')))
- {
- $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(in_array($this->config->edition, array('max', 'ipd')) && strpos($this->config->action->assetType, ",{$action->objectType},") !== false && empty($action->project) && empty($action->product) && empty($action->execution))
- {
- $this->actionTao->processMaxDocObjectLink($action, $moduleName, $methodName, $vars);
- }
- else
- {
- if($action->objectType !== 'doclib') $params = $this->actionTao->getObjectLinkParams($action, $vars);
- if($action->objectType == 'doclib')
- {
- list($moduleName, $methodName, $params) = $this->actionTao->getDoclibTypeParams($action);
- }
- elseif($action->objectType == 'story')
- {
- $story = $this->loadModel('story')->fetchByID($action->objectID);
- if(!empty($story))
- {
- $moduleName = $story->type;
- $methodName = 'view';
- if(isset($shadowProducts[$story->product]))
- {
- $moduleName = 'projectstory';
- $methodName = 'view';
- }
- }
- if(!empty($action->project) && !$project)
- {
- $project = $this->loadModel('project')->fetchById($action->project);
- if(empty($project->multiple))
- {
- $moduleName = 'execution';
- $methodName = 'storyView';
- }
- }
- }
- }
- }
- if($action->objectType == 'team') list($moduleName, $methodName, $params) = $this->getObjectTypeTeamParams($action);
- if($action->objectType == 'story' && $this->config->vision == 'lite') list($moduleName, $methodName, $params) = array('projectstory', 'view', "storyID={$action->objectID}");
- if($action->objectType == 'review') list($moduleName, $methodName, $params) = array('review', 'view', "reviewID={$action->objectID}");
- if($action->objectType == 'build' && $this->app->tab == 'project') $moduleName = 'projectbuild';
- if(empty($action->hasLink) && $this->actionTao->checkActionClickable($action, $deptUsers, $moduleName, $methodName)) $action->objectLink = helper::createLink($moduleName, $methodName, $params);
- if(!empty($action->objectLink) && !empty($project) && empty($project->multiple)) $action->objectLink .= '#app=project'; // Set app for no multiple project.
- if(!empty($action->objectLink) && $action->objectType == 'meeting') $action->objectLink .= '#app=' . $this->app->tab; // Set app for meeting by open tab.
- if($this->config->vision == 'lite' && $action->objectType == 'module') $action->objectLink .= '#app=project';
- if($action->objectType == 'nc' && !empty($action->execution)) $action->objectLink .= '#app=execution';
- return $action;
- }
- /**
- * 根据给定的一段时间的参数计算日期的开始和结束。
- * Compute the begin date and end date of a period.
- *
- * @param string $period all|today|yesterday|twodaysago|latest2days|thisweek|lastweek|thismonth|lastmonth
- * @param string $date
- * @param string $direction pre|next
- * @access public
- * @return array
- */
- public function computeBeginAndEnd($period, $date, $direction)
- {
- $period = strtolower($period);
- $beginDate = '';
- $year = date('Y');
- /* 查询所有动态时最多查询最后两年的数据。 */
- /* When query all dynamic then query the data of the last two years at most. */
- if($this->app->getMethodName() == 'dynamic') $year = $year - 1;
- $beginDate = $year . '-01-01';
- $beginAndEnd = array('begin' => $beginDate, 'end' => FUTURE_DATE);
- $this->app->loadClass('date');
- $today = date('Y-m-d');
- $tomorrow = date::tomorrow();
- $yesterday = date::yesterday();
- $twoDaysAgo = date::twoDaysAgo();
- if($period == 'today') $beginAndEnd = array('begin' => $today, 'end' => $tomorrow);
- if($period == 'yesterday') $beginAndEnd = array('begin' => $yesterday, 'end' => $today);
- if($period == 'twodaysago') $beginAndEnd = array('begin' => $twoDaysAgo, 'end' => $yesterday);
- if($period == 'latest3days') $beginAndEnd = array('begin' => $twoDaysAgo, 'end' => $tomorrow);
- /* 如果时间段为周,则给结束日期增加结束时间。 */
- /* If the period is by week, add the end time to the end date. */
- if($period == 'thisweek' || $period == 'lastweek')
- {
- $func = "get$period";
- extract(date::$func());
- $beginAndEnd = array('begin' => $begin, 'end' => $end);
- }
- if($period == 'thismonth') $beginAndEnd = date::getThisMonth();
- if($period == 'lastmonth') $beginAndEnd = date::getLastMonth();
- /* 确切的日期。 */
- /* The exact date. */
- if($date)
- {
- if($direction == 'pre') $beginAndEnd['begin'] = $date;
- if($direction == 'next') $beginAndEnd['end'] = $date;
- }
- return $beginAndEnd;
- }
- /**
- * 渲染每一个action的历史记录。
- * Render histories of every action.
- *
- * @param string $objectType
- * @param int $objectID
- * @param array $histories
- * @param bool $canChangeTag
- * @access public
- * @return string
- */
- public function renderChanges($objectType, $objectID, $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.
- /* 加载工作流新增的语言包。*/
- if($this->config->edition != 'open')
- {
- $flow = $this->loadModel('workflow')->getByModule($objectType);
- if(!empty($flow)) $this->loadModel('common')->loadCustomLang($objectType, 'view');
- }
- /* 区别是否有diff信息,以便于将有diff信息的字段放在最后。 */
- /* Diff histories by hasing diff info or not. Thus we can to make sure the field with diff show at last. */
- if($objectType == 'doctemplate') $this->app->loadLang('doc');
- 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($objectType == 'doctemplate') $history->fieldLabel = zget($this->lang->docTemplate, $fieldName, zget($this->lang->doc, $fieldName, $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);
- /* 处理历史记录中的差别。 */
- /* Process the diff of histories. */
- $content = '';
- foreach($histories as $history)
- {
- $history->fieldLabel = str_pad($history->fieldLabel, $maxLength, $this->lang->action->label->space);
- if(strpos(',addDiff,removeDiff,', ",{$history->field},") !== false)
- {
- $fileLabel = isset($this->lang->file->common) ? $this->lang->file->common . ' ' : '';
- $content .= sprintf($this->lang->action->desc->{$history->field}, strpos(',project,execution,', ",{$objectType},") !== false ? '' : $fileLabel, $history->diff) . '<br/>';
- }
- elseif($history->diff != '')
- {
- $history->diff = str_replace(array('<ins>', '</ins>', '<del>', '</del>'), array('[ins]', '[/ins]', '[del]', '[/del]'), $history->diff);
- $history->diff = $history->field != 'subversion' && $history->field != 'git' ? htmlSpecialString($history->diff) : $history->diff; // Keep the diff link.
- $history->diff = str_replace(array('[ins]', '[/ins]', '[del]', '[/del]'), array('<ins>', '</ins>', '<del>', '</del>'), $history->diff);
- $history->diff = nl2br($history->diff);
- $history->noTagDiff = $canChangeTag ? preg_replace('/<\/?([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;
- }
- /**
- * 打印每一个action的历史记录。
- * Print histories of every action.
- *
- * @param string $objectType
- * @param int $objectID
- * @param array $histories
- * @param bool $canChangeTag
- * @access public
- * @return void
- */
- public function printChanges($objectType, $objectID, $histories, $canChangeTag = true)
- {
- $content = $this->renderChanges($objectType, $objectID, $histories, $canChangeTag);
- if(is_string($content)) echo $content;
- }
- /**
- * 通过对象类型删除action。
- * Delete action by objectType.
- *
- * @param string $objectType
- * @access public
- * @return bool
- */
- public function deleteByType($objectType)
- {
- $this->dao->delete()->from(TABLE_ACTION)->where('objectType')->eq($objectType)->exec();
- return !dao::isError();
- }
- /**
- * 恢复一条记录。
- * Undelete a record.
- *
- * @param int $actionID
- * @access public
- * @return string|bool
- */
- public function undelete($actionID)
- {
- if($actionID <= 0) return false;
- $action = $this->getById($actionID);
- if(!$action || $action->action != 'deleted') return false;
- list($table, $orderby, $field, $queryKey) = $this->actionTao->getUndeleteParamsByObjectType($action->objectType);
- if(empty($queryKey)) $queryKey = 'id';
- $object = $this->actionTao->getObjectBaseInfo($table, array($queryKey => $action->objectID), $field, $orderby);
- if(empty($object)) return false;
- $result = $this->checkActionCanUndelete($action, $object);
- if($result !== true) return $result;
- /* 恢复被删除的元素。 */
- /* Resotre deleted object. */
- if($action->objectType == 'doc') $table = TABLE_DOC;
- $this->dao->update($table)->set('deleted')->eq(0)->where('id')->eq($action->objectID)->exec();
- if($action->objectType == 'module')
- {
- $module = $this->loadModel('tree')->getById($action->objectID);
- if($module->type == 'doc' && $module->parent > 0)
- {
- $parentActionID = $this->dao->select('id')->from(TABLE_ACTION)->where('objectType')->eq('module')->andWhere('objectID')->eq($module->parent)->andWhere('action')->eq('deleted')->orderBy('id_desc')->fetch('id');
- if($parentActionID) $this->undelete($parentActionID);
- }
- }
- if($action->objectType == 'projectchange')
- {
- $reviewID = $this->dao->select('id')->from(TABLE_REVIEW)->where('type')->eq('projectchange')->andWhere('object')->eq($action->objectID)->andWhere('deleted')->eq('1')->fetch('id');
- if($reviewID)
- {
- $reviewActionID = $this->dao->select('id')->from(TABLE_ACTION)->where('action')->eq('deleted')->andWhere('objectType')->eq('review')->andWhere('objectID')->eq($reviewID)->andWhere('extra')->eq(self::CAN_UNDELETED)->fetch('id');
- if($reviewActionID) $this->undelete($reviewActionID);
- }
- }
- if($action->objectType == 'review')
- {
- $review = $this->dao->select('*')->from(TABLE_REVIEW)->where('id')->eq($action->objectID)->fetch();
- if($review->type == 'projectchange')
- {
- $projectchangeActionID = $this->dao->select('id')->from(TABLE_ACTION)->where('action')->eq('deleted')->andWhere('objectType')->eq('projectchange')->andWhere('objectID')->eq($review->object)->andWhere('extra')->eq(self::CAN_UNDELETED)->fetch('id');
- if($projectchangeActionID) $this->undelete($projectchangeActionID);
- }
- }
- if($action->objectType == 'deliverable')
- {
- $this->dao->update(TABLE_DELIVERABLE)->set('status')->eq('disabled')->where('id')->eq($action->objectID)->exec();
- }
- $this->recoverRelatedData($action, $object);
- /* 还原已删除的需求时重算OR需求和业用研需的阶段。 */
- /* The stage of recalculating OR requirements and industrial research needs when restoring deleted requirements. */
- if(in_array($action->objectType, array('story', 'epic', 'requirement')))
- {
- $this->loadModel('story')->setStage($action->objectID);
- $this->story->updateParentStatus($action->objectID);
- }
- if($action->objectType == 'demand' && !empty($object->parent)) $this->loadModel('demand')->updateParentDemandStage($object->parent);
- if($action->objectType == 'release') $this->loadModel('system')->setSystemRelease((int)$object->system, $action->objectID);
- if(in_array($action->objectType, array('release', 'build')) && !empty($object->system))
- {
- $systemActionID = $this->dao->select('id')->from(TABLE_ACTION)->where('objectType')->eq('system')->andWhere('objectID')->eq($object->system)->andWhere('action')->eq('deleted')->orderBy('id_desc')->fetch('id');
- if($systemActionID) $this->undelete($systemActionID);
- }
- /* 在action表中更新action记录。 */
- /* 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');
- return true;
- }
- /**
- * 隐藏一个对象。
- * Hide an object.
- *
- * @param int $actionID
- * @access public
- * @return bool
- */
- public function hideOne($actionID)
- {
- $action = $this->getById($actionID);
- if(!$action || $action->action != 'deleted') return false;
- $this->dao->update(TABLE_ACTION)->set('extra')->eq(self::BE_HIDDEN)->where('id')->eq($actionID)->exec();
- $this->create($action->objectType, $action->objectID, 'hidden');
- return !dao::isError();
- }
- /**
- * 隐藏所有被删除的对象。
- * Hide all deleted objects.
- *
- * @access public
- * @return bool
- */
- 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();
- return !dao::isError();
- }
- /**
- * 更新一个action的评论。
- * Update comment of a action.
- *
- * @param int $actionID
- * @param object $newComment
- * @access public
- * @return bool
- */
- public function updateComment($actionID, $newComment)
- {
- $action = $this->getById($actionID);
- if(!$action) return false;
- $action->files = $this->loadModel('file')->getByObject('comment', $actionID);
- /* 只保留允许的标签。 */
- /* Keep only allowed tags. */
- $newComment->comment = fixer::stripDataTags($newComment->lastComment);
- /* 处理评论内的图片。*/
- /* Handle images in comment. */
- $newComment = $this->loadModel('file')->processImgURL($newComment, 'comment', $newComment->uid);
- $this->dao->update(TABLE_ACTION)
- ->set('date')->eq(helper::now())
- ->set('comment')->eq($newComment->comment)
- ->where('id')->eq($actionID)
- ->exec();
- $this->file->updateObjectID($newComment->uid, $action->objectID, $action->objectType);
- $this->file->processFileDiffsForObject('comment', $action, $newComment);
- if(!empty($newComment->files))
- {
- $action->files = $newComment->files;
- $this->dao->update(TABLE_ACTION)->set('files')->eq($newComment->files)->where('id')->eq($actionID)->exec();
- }
- $changes = common::createChanges($action, $newComment);
- if($changes) $this->logHistory($actionID, $changes);
- return true;
- }
- /**
- * 根据actions构建日期组。
- * Build date group by actions
- *
- * @param array $actions
- * @param string $direction
- * @param string $orderBy date_desc|date_asc
- * @access public
- * @return array
- */
- public function buildDateGroup($actions, $direction = 'next', $orderBy = 'date_desc')
- {
- $dateGroup = array();
- $firstTime = 0;
- $timeStamp = 0;
- foreach($actions as $action)
- {
- $timeStamp = strtotime(isset($action->originalDate) ? $action->originalDate : $action->date);
- $date = date(DT_DATE3, $timeStamp);
- $action->time = date(DT_TIME2, $timeStamp);
- $dateGroup[$date][] = $action;
- if(empty($firstTime)) $firstTime = $timeStamp;
- }
- /* 将日期的顺序修改正确。 */
- /* Modify date to the corrret order. */
- if($dateGroup && $firstTime < $timeStamp)
- {
- $dateGroup = array_reverse($dateGroup);
- foreach($dateGroup as $key => $dateItem) $dateGroup[$key] = array_reverse($dateItem);
- }
- return $dateGroup;
- }
- /**
- * 检查是否有上一条或者下一条。
- * Check Has pre or next.
- *
- * @param string $date
- * @param string $direction
- * @param string $period
- * @access public
- * @return bool
- */
- public function hasPreOrNext($date, $direction = 'next', $period = 'all')
- {
- if(empty($date)) return false;
- $condition = $this->session->actionQueryCondition;
- if(empty($condition)) return false;
- $beginAndEnd = $this->computeBeginAndEnd($period, '', $direction);
- $hasProduct = preg_match('/t2\.([`"]?)product/', $condition);
- $condition = preg_replace("/AND +`?date`?\s*(<|>|<=|>=)\s*'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
- $actions = $this->dao->select('action.id')->from(TABLE_ACTION)->alias('action')
- ->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
- ->where($condition)
- ->andWhere('`date`' . ($direction == 'next' ? '<' : '>') . "'{$date}'")
- ->beginIF($direction == 'next' && $beginAndEnd['begin'] != EPOCH_DATE)->andWhere('date')->ge($beginAndEnd['begin'])->fi()
- ->beginIF($direction != 'next' && $beginAndEnd['end'] != FUTURE_DATE)->andWhere('date')->le($beginAndEnd['end'])->fi()
- ->limit(1)
- ->fetchAll();
- return count($actions) > 0;
- }
- /**
- * 保存全局搜索对象索引信息。
- * 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');
- if($this->config->edition != 'open' && $this->app->isServing()) $this->loadModel('workflow')->appendSearchConfig();
- $actionType = strtolower($actionType);
- if(!isset($this->config->search->fields->{$objectType})) return false;
- $isCommentedAction = $actionType == 'commented';
- if(strpos($this->config->search->buildAction, ",{$actionType},") === false && !$isCommentedAction && empty($_POST['comment'])) return false;
- if($isCommentedAction && empty($_POST['actioncomment'])) return false;
- if($actionType == 'deleted' || $actionType == 'erased') return $this->search->deleteIndex($objectType, $objectID);
- $field = $this->config->search->fields->{$objectType};
- $query = $this->search->buildIndexQuery($objectType, false);
- $data = $query->andWhere('t1.' . $field->id)->eq($objectID)->fetch();
- if(empty($data)) return false;
- $data->comment = '';
- if($objectType == 'effort' && $data->objectType == 'task') return false;
- if($objectType == 'case')
- {
- $caseStep = $this->dao->select('`desc`,`expect`')->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);
- return !dao::isError();
- }
- /**
- * 打印API(极狐)对象上的操作。
- * Print actions of an object for API(JIHU).
- *
- * @param object $action
- * @access public
- * @return false|void
- */
- public function printActionForGitLab($action)
- {
- if(!isset($action->objectType) || !isset($action->action)) return false;
- $actionType = strtolower($action->action);
- if(isset($this->lang->action->apiTitle->{$actionType}) && isset($action->extra))
- {
- /* 如果extra列是一个用户名,则组装链接。 */
- /* If extra column is a username, then assemble link to that. */
- if($action->action == "assigned")
- {
- $user = $this->loadModel('user')->getById($action->extra);
- if($user)
- {
- $url = helper::createLink('user', 'profile', "userID={$user->id}");
- $action->extra = "<a href='{$url}' target='_blank'>{$action->extra}</a>";
- }
- }
- echo sprintf($this->lang->action->apiTitle->{$actionType}, $action->extra);
- }
- elseif(isset($this->lang->action->apiTitle->{$actionType}) && !isset($action->extra))
- {
- echo $this->lang->action->apiTitle->{$actionType};
- }
- else
- {
- echo $actionType;
- }
- }
- /**
- * 处理操作记录用于API。
- * Process action for API.
- *
- * @param array|object $actions
- * @param array|object $users
- * @param array|object $objectLang
- * @access public
- * @return array
- */
- public function processActionForAPI($actions, $users = array(), $objectLang = array())
- {
- if(is_object($actions)) $actions = (array)$actions;
- if(is_object($users)) $users = (array)$users;
- if(is_object($objectLang)) $objectLang = (array)$objectLang;
- 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);
- }
- /**
- * 处理动态用于API。
- * Process dynamic for API.
- *
- * @param array $dynamics
- * @access public
- * @return array
- */
- public function processDynamicForAPI($dynamics)
- {
- /* 获取用户列表。 */
- /* Get user list. */
- $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 $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 data.
- *
- * @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 bool
- */
- 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)
- {
- $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');
- if(dao::isError()) return false;
- }
- return true;
- }
- /**
- * 获取属性相同的对象。
- * Get repeat object.
- *
- * @param object $action
- * @param string $table
- * @access public
- * @return array
- */
- public function getRepeatObject($action, $table)
- {
- $object = $this->dao->select('*')->from($table)->where('id')->eq($action->objectID)->fetch();
- if($action->objectType == 'product')
- {
- $programID = isset($object->program) ? $object->program : 0;
- $repeatObject = $this->dao->select('*')->from(TABLE_PRODUCT)
- ->where('id')->ne($action->objectID)
- ->andWhere("(name = '{$object->name}' AND program = {$programID})", true)
- ->beginIF($object->code)->orWhere("code = '{$object->code}'")->fi()
- ->markRight(1)
- ->andWhere('deleted')->eq('0')
- ->fetch();
- }
- elseif(in_array($action->objectType, array('program', 'project', 'execution')))
- {
- $sprintProject = isset($object->project) ? $object->project : 0;
- $repeatObject = $this->dao->select('*')->from(TABLE_PROJECT)
- ->where('id')->ne($action->objectID)
- ->beginIF($action->objectType == 'program' || $action->objectType == 'project')->andWhere("(name = '{$object->name}' AND parent = {$object->parent})", true)->fi()
- ->beginIF($action->objectType == 'execution')->andWhere("(name = '{$object->name}' AND project = {$sprintProject})", true)->fi()
- ->beginIF($action->objectType == 'project' && $object->code)->orWhere("(code = '{$object->code}' AND model = '{$object->model}')")->fi()
- ->beginIF($action->objectType == 'execution' && $object->code)->orWhere("code = '{$object->code}'")->fi()
- ->markRight(1)
- ->beginIF($action->objectType == 'program')->andWhere('type')->eq('program')->fi()
- ->beginIF($action->objectType == 'project')->andWhere('type')->eq('project')->fi()
- ->beginIF($action->objectType == 'execution')->andWhere('type')->in('sprint,stage,kanban')->fi()
- ->andWhere('deleted')->eq('0')
- ->fetch();
- }
- else
- {
- $objectNameFields = $this->config->action->objectNameFields[$action->objectType];
- $repeatObject = $this->dao->select('*')->from($table)->where('id')->ne($action->objectID)->andWhere('deleted')->eq('0')->andWhere($objectNameFields)->eq($object->name)->fetch();
- }
- return array($repeatObject, $object);
- }
- /**
- * 获取和需求属性相近的对象。
- * Get like object.
- *
- * @param string $table
- * @param string $columns
- * @param string $param
- * @param string $value
- * @access public
- * @return array
- */
- public function getLikeObject($table, $columns, $param, $value)
- {
- return $this->dao->select($columns)->from($table)->where($param)->like($value)->fetchPairs();
- }
- /**
- * 通过id更新对象。
- * Update object by id.
- *
- * @param string $table
- * @param int $id
- * @param array $params
- * @access public
- * @return bool
- */
- public function updateObjectByID($table, $id, $params)
- {
- $updateParams = array();
- foreach($params as $key => $value) $updateParams[] = '`' . $key . '`' . '="' . $value . '"';
- $this->dao->update($table)->set(implode(',', $updateParams))->where('id')->eq($id)->exec();
- return !dao::isError();
- }
- /**
- * 根据执行id获取attribute属性。
- * Get attribute by execution id.
- *
- * @param int $executionID
- * @access public
- * @return string|bool
- */
- public function getAttributeByExecutionID($executionID)
- {
- return $this->dao->select('attribute')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('attribute');
- }
- /**
- * 根据id获取已经删除的阶段。
- * Get deleted stage by ids.
- *
- * @param array $list
- * @access public
- * @return array|bool
- */
- public function getDeletedStagedByList($list)
- {
- return $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->in($list)->andWhere('deleted')->eq(1)->andWhere('type')->eq('stage')->orderBy('id_asc')->fetchAll('id');
- }
- /**
- * 更新阶段的attribute属性。
- * Update stage attribute.
- *
- * @param string $attribute
- * @param array $executions
- * @access public
- * @return bool
- */
- public function updateStageAttribute($attribute, $executions)
- {
- $this->dao->update(TABLE_EXECUTION)->set('attribute')->eq($attribute)->where('id')->in($executions)->exec();
- return !dao::isError();
- }
- /**
- * 获取动态对象关联的数据。
- * Get action object related data.
- *
- * @param string $table
- * @param string $objectType
- * @param array $objectIdList
- * @param string $field
- * @param array $users
- * @param array $requirements
- * @param array $epics
- * @access public
- * @return array
- */
- public function getObjectRelatedData($table, $objectType, $objectIdList, $field, $users, $requirements, $epics)
- {
- $objectName = array();
- $relatedProject = array();
- if($table == TABLE_TODO)
- {
- $todos = $this->dao->select("id, {$field} AS name, account, private, type, objectID")->from($table)->where('id')->in($objectIdList)->orderBy('id_asc')->fetchAll();
- foreach($todos as $todo)
- {
- /* Get related object name. */
- if(in_array($todo->type, array('task', 'bug', 'story', 'testtask'))) $todo->name = $this->dao->findById($todo->objectID)->from($this->config->objectTables[$todo->type])->fetch($this->config->action->objectNameFields[$todo->type]);
- $objectName[$todo->id] = $todo->private == 1 && $todo->account != $this->app->user->account ? $this->lang->todo->thisIsPrivate : $todo->name;
- }
- }
- elseif(strpos(",{$this->config->action->needGetProjectType},", ",{$objectType},") !== false || $objectType == 'project' || $objectType == 'execution')
- {
- $objectInfo = $this->dao->select("id, project, {$field} AS name")->from($table)->where('id')->in($objectIdList)->filterTpl(false)->orderBy('id_asc')->fetchAll();
- foreach($objectInfo as $object)
- {
- $objectName[$object->id] = $objectType == 'gapanalysis' ? zget($users, $object->name) : $object->name; // Get user realname if objectType is gapanalysis.
- $relatedProject[$object->id] = $object->project;
- }
- }
- elseif($objectType == 'story' || $objectType == 'team') // Get story or team related data.
- {
- if($objectType == 'team') $table = TABLE_PROJECT;
- $objectField = $objectType == 'story' ? 'id,title,type' : 'id,team AS title,type';
- $objectInfo = $this->dao->select($objectField)->from($table)->where('id')->in($objectIdList)->filterTpl(false)->orderBy('id_asc')->fetchAll();
- foreach($objectInfo as $object)
- {
- $objectName[$object->id] = $object->title;
- if($object->type == 'requirement') $requirements[$object->id] = $object->id;
- if($object->type == 'epic') $epics[$object->id] = $object->id;
- if($object->type == 'project') $relatedProject[$object->id] = $object->id;
- }
- }
- elseif($objectType == 'stakeholder') // Get stakeholder realname.
- {
- $objectName = $this->dao->select("id, {$field} AS name")->from($table)->where('id')->in($objectIdList)->orderBy('id_asc')->fetchPairs();
- foreach($objectName as $id => $name) $objectName[$id] = zget($users, $name);
- }
- elseif($objectType == 'pivot')
- {
- $objectName = $this->dao->select("pivot, {$field} AS name")->from($table)->where('pivot')->in($objectIdList)->orderBy('pivot_asc')->fetchPairs();
- }
- else
- {
- $objectName = $this->dao->select("id, {$field} AS name")->from($table)->where('id')->in($objectIdList)->filterTpl(false)->orderBy('id_asc')->fetchPairs();
- }
- return array($objectName, $relatedProject, $requirements, $epics);
- }
- /**
- * 获取对象类型为team的link元素。
- * Get link element of objecttype team.
- *
- * @param object $action
- * @access protected
- * @return array
- */
- protected function getObjectTypeTeamParams($action)
- {
- if($action->project) return array('project', 'team', 'projectID=' . $action->project);
- if($action->execution) return array('execution', 'team', 'executionID=' . $action->execution);
- return array('', '', '');
- }
- /**
- * 检查action是否可以被还原。
- * Check action can be undeleted.
- *
- * @param object $action
- * @param object $object
- * @access protected
- * @return string|bool
- */
- protected function checkActionCanUndelete($action, $object)
- {
- if($action->objectType == 'execution')
- {
- if($object->deleted && empty($object->project)) return $this->lang->action->undeletedTips;
- $projectCount = $this->dao->select('COUNT(1) AS count')->from(TABLE_PROJECT)->where('id')->eq($object->project)->andWhere('deleted')->eq('0')->fetch('count');
- if((int)$projectCount == 0) return $this->lang->action->executionNoProject;
- }
- elseif($action->objectType == 'repo' && in_array($object->SCM, array('Gitlab', 'Gitea', 'Gogs')))
- {
- $server = $this->dao->select('*')->from(TABLE_PIPELINE)->where('id')->eq($object->serviceHost)->andWhere('deleted')->eq('0')->fetch();
- if(empty($server)) return $this->lang->action->repoNoServer;
- }
- elseif($action->objectType == 'module')
- {
- $repeatName = $this->loadModel('tree')->checkUnique($object);
- if($repeatName) return sprintf($this->lang->tree->repeatName, $repeatName);
- if($object->parent > 0 && $object->type != 'doc')
- {
- $parent = $this->dao->select('*')->from(TABLE_MODULE)->where('id')->eq($object->parent)->fetch();
- if($parent && $parent->deleted == '1') return $this->lang->action->refusemodule;
- }
- }
- elseif($action->objectType == 'case' && $object->scene)
- {
- $scene = $this->dao->select('*')->from(TABLE_SCENE)->where('id')->eq($object->scene)->fetch();
- if($scene->deleted) return $this->lang->action->refusecase;
- }
- elseif($action->objectType == 'scene' && $object->parent)
- {
- $scenerow = $this->dao->select('*')->from(TABLE_SCENE)->where('id')->eq($object->parent)->fetch();
- if($scenerow->deleted) return $this->lang->action->refusescene;
- }
- elseif($action->objectType == 'reviewissue' && !empty($object->review))
- {
- $review = $this->dao->select('*')->from(TABLE_REVIEW)->where('id')->eq($object->review)->fetch();
- if($review->deleted)
- {
- $this->app->loadLang('reviewissue');
- return $this->lang->reviewissue->undeleteAction;
- }
- }
- elseif($action->objectType == 'kanban' && $object->space)
- {
- $kanbanSpace = $this->dao->select('*')->from(TABLE_KANBANSPACE)->where('id')->eq($object->space)->fetch();
- if($kanbanSpace->deleted) return $this->lang->action->refusekanban;
- }
- return true;
- }
- /**
- * 恢复被删除对象的关联数据。
- * Recover related data of deleted object.
- *
- * @param object $action
- * @param object $object
- * @access protected
- * @return void
- */
- protected function recoverRelatedData($action, $object)
- {
- if($action->objectType == 'release' && $object->shadow) $this->dao->update(TABLE_BUILD)->set('deleted')->eq(0)->where('id')->eq($object->shadow)->exec();
- if(in_array($action->objectType, array('program', 'project', 'execution', 'product')))
- {
- $objectType = $action->objectType == 'execution' ? 'sprint' : $action->objectType;
- if($object->acl != 'open') $this->loadModel('user')->updateUserView(array($object->id), $objectType);
- /* 恢复隐藏产品。 */
- /* Resotre hidden products. */
- if($action->objectType == 'project' && !$object->hasProduct)
- {
- $productID = $this->loadModel('product')->getProductIDByProject($object->id);;
- $this->dao->update(TABLE_PRODUCT)->set('name')->eq($object->name)->set('deleted')->eq(0)->where('id')->eq($productID)->exec();
- }
- /* 恢复隐藏执行。 */
- /* Resotre hidden execution. */
- if($action->objectType == 'project' && empty($object->multiple)) $this->dao->update(TABLE_EXECUTION)->set('deleted')->eq('0')->where('project')->eq($object->id)->andWhere('multiple')->eq('0')->exec();
- }
- if($action->objectType == 'doc' && $object->files) $this->dao->update(TABLE_FILE)->set('deleted')->eq('0')->where('id')->in($object->files)->exec();
- /* 当还原项目或者执行的时候恢复用户的产品权限。 */
- /* Revert userView products when undelete project or execution. */
- if($action->objectType == 'project' || $action->objectType == 'execution')
- {
- $products = $this->loadModel('product')->getProducts($object->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' || $action->objectType == 'product') $this->dao->update(TABLE_DOCLIB)->set('deleted')->eq(0)->where($action->objectType)->eq($action->objectID)->exec();
- /* 还原子任务的时候更新任务状态。 */
- /* Update task status when undelete child task. */
- if($action->objectType == 'task')
- {
- $task = $this->loadModel('task')->fetchByID((int)$action->objectID);
- if($task->parent > 0)
- {
- $parentConsumed = $this->dao->select('consumed')->from(TABLE_TASK)->where('id')->eq($task->parent)->fetch('consumed');
- if($parentConsumed)
- {
- $this->dao->update(TABLE_TASK)->set('parent')->eq('0')->set('path')->eq(",{$task->id},")->where('id')->eq($task->id)->exec();
- }
- else
- {
- $this->loadModel('task')->updateParent($task, false);
- }
- }
- }
- }
- /**
- * 执行和项目相关操作记录的extra信息。
- * Build execution and project action extra info.
- *
- * @param object $action
- * @access protected
- * @return void
- */
- protected function processExecutionAndProjectActionExtra($action)
- {
- $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 && $this->config->vision == 'rnd')
- {
- foreach($linkedProducts as $productID => $productName) $linkedProducts[$productID] = html::a(helper::createLink('product', 'browse', "productID={$productID}"), "#{$productID} {$productName}");
- $action->extra = sprintf($this->lang->execution->action->extra, '<strong>' . join(', ', $linkedProducts) . '</strong>');
- }
- }
- /**
- * 获取动态的数量。
- * Get dynamic count.
- *
- * @access public
- * @return int
- */
- public function getDynamicCount($period = 'all')
- {
- $condition = $this->session->actionQueryCondition;
- if(empty($condition)) return 0;
- $table = $this->actionTao->getActionTable($period);
- $hasProduct = preg_match('/t2\.([`"]?)product/', $condition);
- $useIndex = $hasProduct && strpos('mysql,oceanbase', $this->config->db->driver) !== false ? ' USE INDEX (`vision_date`)' : ''; // 关联产品时指定索引以提高查询性能。Specify index to improve query performance when associated with product.
- $beginAndEnd = $this->computeBeginAndEnd($period, '', 'next');
- $condition = preg_replace("/AND +`?date`? +(<|>|<=|>=) +'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
- $actions = $this->dao->select('action.id')->from($table)->alias('action' . $useIndex)
- ->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
- ->where($condition)
- ->beginIF($beginAndEnd['begin'] != EPOCH_DATE)->andWhere('date')->ge($beginAndEnd['begin'])->fi()
- ->beginIF($beginAndEnd['end'] != FUTURE_DATE)->andWhere('date')->le($beginAndEnd['end'])->fi()
- ->limit(self::MAXCOUNT)
- ->fetchAll('id');
- return count($actions);
- }
- /**
- * 清除一个月前的动态记录。
- * 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 the first action.
- *
- * @access public
- * @return object|bool
- */
- public function getFirstAction()
- {
- return $this->dao->select('*')->from(TABLE_ACTION)->orderBy('id')->limit(1)->fetch();
- }
- /**
- * 检查该日期是否还有的更多日志。
- * Check has more actions for this date.
- *
- * @param object $lastAction
- * @access public
- * @return bool
- */
- public function hasMoreAction($lastAction)
- {
- $condition = $this->session->actionQueryCondition;
- if(empty($condition)) return false;
- $hasProduct = preg_match('/t2\.([`"]?)product/', $condition);
- $lastDate = substr($lastAction->originalDate, 0, 10);
- $condition = preg_replace("/AND +`?date`? +(<|>|<=|>=) +'\d{4}\-\d{2}\-\d{2}'/", '', $condition);
- $actions = $this->dao->select('action.id')->from(TABLE_ACTION)->alias('action')
- ->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
- ->where($condition)
- ->andWhere("`date`")->ge($lastDate)
- ->andWhere("`date`")->lt($lastDate . ' 23:59:59')
- ->andWhere("`date` < '{$lastAction->originalDate}'")
- ->orderBy('date_desc')
- ->limit(1)
- ->fetchAll();
- return count($actions) > 0;
- }
- /**
- * 获取该日期的更多日志。
- * Get more actions for this date.
- *
- * @param int $lastActionID
- * @param int $limit
- * @access public
- * @return array
- */
- public function getMoreActions($lastActionID, $limit = 100)
- {
- $condition = $this->session->actionQueryCondition;
- if(empty($condition)) return array();
- $hasProduct = preg_match('/t2\.([`"]?)product/', $condition);
- $lastAction = $this->dao->select('*')->from(TABLE_ACTION)->where('id')->eq($lastActionID)->fetch();
- $lastDate = substr($lastAction->date, 0, 10);
- $actions = $this->dao->select('action.*')->from(TABLE_ACTION)->alias('action')
- ->beginIF($hasProduct)->leftJoin(TABLE_ACTIONPRODUCT)->alias('t2')->on('action.id=t2.action')->fi()
- ->where($condition)
- ->andWhere("`date`")->ge($lastDate)
- ->andWhere("`date`")->lt($lastDate . ' 23:59:59')
- ->andWhere("`date` < '{$lastAction->date}'")
- ->orderBy('date_desc')
- ->limit($limit)
- ->fetchAll('id', false);
- $actions = $this->transformActions($actions);
- foreach($actions as $action)
- {
- $timeStamp = strtotime(isset($action->originalDate) ? $action->originalDate : $action->date);
- $action->time = date(DT_TIME2, $timeStamp);
- }
- return array_values($actions);
- }
- }
|