* @package doc
* @version $Id: control.php 933 2010-07-06 06:53:40Z wwccss $
* @link https://www.zentao.net
*/
class doc extends control
{
public docModel $doc;
/**
* 构造函数,加载通用模块。
* Construct function, load user, tree, action auto.
*
* @access public
* @return void
*/
public function __construct($moduleName = '', $methodName = '')
{
parent::__construct($moduleName, $methodName);
$this->loadModel('user');
$this->loadModel('tree');
$this->loadModel('action');
$this->loadModel('product');
$this->loadModel('project');
$this->loadModel('execution');
}
/**
* 最近访问的空间首页。
* Last viewed doc space home.
*
* @access public
* @return void
*/
public function lastViewedSpaceHome()
{
$lastViewedSpaceHome = $this->doc->getLastViewed('lastViewedSpaceHome');
if($this->config->vision == 'lite' && $lastViewedSpaceHome == 'api') $lastViewedSpaceHome = 'mine';
if($lastViewedSpaceHome === 'api') return $this->locate($this->createLink('api', 'index'));
$spaceMap = array(
'mine' => 'mySpace',
'product' => 'productSpace',
'project' => 'projectSpace',
'custom' => 'teamSpace'
);
$method = $spaceMap[$lastViewedSpaceHome];
if(empty($method)) return $this->locate($this->createLink('doc', 'mySpace'));
return $this->locate($this->createLink('doc', $method));
}
/**
* 最近访问的空间。
* Last viewed doc space.
*
* @access public
* @return void
*/
public function lastViewedSpace()
{
$lastViewedSpaceHome = $this->doc->getLastViewed('lastViewedSpaceHome');
if($this->config->vision == 'lite' && $lastViewedSpaceHome == 'api') $lastViewedSpaceHome = 'mine';
if($lastViewedSpaceHome === 'api') return $this->locate($this->createLink('api', 'index'));
$spaceMap = array(
'mine' => 'mySpace',
'product' => 'productSpace',
'project' => 'projectSpace',
'custom' => 'teamSpace'
);
$method = $spaceMap[$lastViewedSpaceHome];
if(empty($method) || !common::hasPriv('doc', $method)) return $this->locate($this->createLink('doc', 'mySpace'));
$lastViewedSpace = $this->doc->getLastViewed('lastViewedSpace');
if(!is_numeric($lastViewedSpace)) return $this->locate($this->createLink('doc', 'mySpace'));
return $this->locate($this->createLink('doc', $method, "objectID={$lastViewedSpace}"));
}
/**
* 最近访问的库。
* Last viewed doc lib.
*
* @access public
* @return void
*/
public function lastViewedLib()
{
$lastViewedSpaceHome = $this->doc->getLastViewed('lastViewedSpaceHome');
$lastViewedLib = $this->doc->getLastViewed('lastViewedLib');
if($lastViewedSpaceHome === 'api')
{
if(is_numeric($lastViewedLib)) return $this->locate($this->createLink('api', 'index', "libID={$lastViewedLib}"));
return $this->locate($this->createLink('api', 'index'));
}
$lastViewedSpace = $this->doc->getLastViewed('lastViewedSpace');
$spaceMap = array(
'mine' => 'mySpace',
'product' => 'productSpace',
'project' => 'projectSpace',
'custom' => 'teamSpace'
);
$method = $spaceMap[$lastViewedSpaceHome];
if(empty($method)) return $this->locate($this->createLink('doc', 'mySpace'));
if(!is_numeric($lastViewedSpace) || !is_numeric($lastViewedLib)) return $this->locate($this->createLink('doc', 'mySpace'));
return $this->locate($this->createLink('doc', $method, "objectID={$lastViewedSpace}&libID={$lastViewedLib}"));
}
/**
* 设置上次访问的文档对象。
* Set last viewed doc object.
*
* @access public
* @return void
*/
public function ajaxSetLastViewed()
{
if(empty($_POST)) return $this->send(array('result' => 'fail'));
$this->doc->setLastViewed($_POST);
return $this->send(array('result' => 'success'));
}
/**
* 文档仪表盘。
* Document dashboard.
*
* @access public
* @return void
*/
public function index()
{
$this->session->set('docList', $this->app->getURI(true), 'doc');
echo $this->fetch('block', 'dashboard', 'dashboard=doc');
}
/**
* 禅道数据列表。
* Zentao data list.
*
* @param string $type
* @param int|string $blockID
* @access public
* @return void
*/
public function zentaoList(string $type, int|string $blockID)
{
if(is_string($blockID)) $blockID = (int)str_replace('__TML_ZENTAOLIST__', '', $blockID);
$blockData = $this->doc->getZentaoList($blockID);
if(!$blockData)
{
if(helper::isAjaxRequest('fetch'))
{
echo '
' . $this->lang->notFound . '
';
return;
}
return $this->sendError($this->lang->notFound);
}
$doc = $this->doc->getByID($blockData->doc);
$this->view->isTemplate = !empty($doc->templateType) && !in_array($blockData->extra, array('fromReview', 'fromReport'));
$this->view->fromReport = $blockData->extra == 'fromReport';
$noSupport = false;
if($this->view->fromReport)
{
$project = $this->loadModel('project')->fetchByID((int)$doc->project);
$noSupport = in_array($type, array('HLDS', 'DDS', 'DBDS', 'ADS')) && in_array($project->model, array('scrum', 'agileplus', 'kanban'));
}
$this->view->noSupport = $noSupport;
if($this->view->isTemplate)
{
$this->view->title = sprintf($this->lang->doc->insertTitle, $this->lang->docTemplate->zentaoList[$type]);
$this->view->type = $type;
$this->view->blockID = $blockID;
$this->view->settings = $blockData->settings;
$this->view->searchTab = zget($blockData->content, 'searchTab', '');
if($type == 'productCase' || $type == 'projectCase') $this->view->caseStage = $blockData->content->caseStage;
return $this->display();
}
$fromTemplate = in_array($blockData->extra, array('fromTemplate', 'fromReview', 'fromReport'));
if($fromTemplate)
{
$this->view->title = sprintf($this->lang->doc->insertTitle, $this->lang->docTemplate->zentaoList[$type]);
$this->view->searchTab = zget($blockData->content, 'searchTab', '');
$this->view->isSetted = isset($blockData->content->data) && isset($blockData->content->cols) || isset($blockData->content->ganttOptions);
if($type == 'productCase' || $type == 'projectCase') $this->view->caseStage = $blockData->content->caseStage;
}
else
{
$this->view->title = sprintf($this->lang->doc->insertTitle, $this->lang->doc->zentaoList[$type]);
}
if($type == 'gantt')
{
$this->view->ganttData = (array)zget($blockData->content, 'ganttOptions', array());
$this->view->ganttFields = (array)zget($blockData->content, 'ganttFields', array());
$this->view->showFields = zget($blockData->content, 'showFields', '');
}
else
{
$data = (array)zget($blockData->content, 'data', array());
if($type == 'task')
{
foreach($data as $task) $task->canView = $this->execution->checkPriv($task->execution);
}
$this->app->loadClass('pager', true);
$this->view->idList = (array)zget($blockData->content, 'idList', array());
$this->view->cols = (array)zget($blockData->content, 'cols', array());
$this->view->data = $data;
$this->view->pager = new pager(count($data), 10);
}
$this->view->type = $type;
$this->view->settings = $blockData->settings;
$this->view->users = $this->loadModel('user')->getPairs('noletter|pofirst');
$this->view->blockID = $blockID;
$this->view->fromTemplate = $fromTemplate;
if(strpos(',productStory,ER,UR,planStory,projectStory', $type) !== false) $this->docZen->assignStoryGradeData($type);
if($type == 'productRelease')
{
$releaseList = array();
foreach($this->view->data as $release)
{
if($release->id == $release->rowID) $releaseList[$release->id] = $release;
}
$children = implode(',', array_column($releaseList, 'releases'));
$childReleases = $this->loadModel('release')->getListByCondition(explode(',', $children), 0, true);
$this->view->data = $this->release->processReleaseListData($releaseList, $childReleases, false);
}
$this->display();
}
/**
* 导出禅道数据列表。
* Export Zentao data list.
*
* @param int|string $blockID
* @access public
* @return void
*/
public function ajaxExportZentaoList(int|string $blockID)
{
if(is_string($blockID)) $blockID = (int)str_replace('__TML_ZENTAOLIST__', '', $blockID);
$blockData = $this->doc->getZentaoList($blockID);
if(!$blockData) return $this->lang->notFound;
if(!helper::isAjaxRequest('fetch')) return $this->locate(inlink('zentaoList', "type={$blockData->type}&blockID={$blockID}"));
if(empty($blockData->title))
{
$blockType = $blockData->type;
$blockContent = $blockData->content;
$templateLang = zget($this->lang, 'docTemplate', array());
$searchTab = zget($blockContent, 'searchTab', '');
$blockTitle = '';
if(!empty($templateLang))
{
$blockTitle = empty($templateLang->searchTabList[$blockType][$searchTab]) ? '' : $templateLang->searchTabList[$blockType][$searchTab] . $templateLang->of;
if($blockType == 'bug' && $searchTab == 'overduebugs') $blockTitle = $templateLang->overdue . $templateLang->of;
if(!empty($blockContent->caseStage)) $blockTitle .= $this->app->loadLang('testcase')->testcase->stageList[$blockContent->caseStage];
}
$blockData->title = $blockTitle . zget($this->lang->doc->zentaoList, $blockType, '') . $this->lang->doc->list;
}
$content = $this->docZen->exportZentaoList($blockData);
echo $content;
}
/**
* 构建禅道数据列表。
* Build Zentao data list.
*
* @param int $docID
* @param string $type
* @param int $oldBlockID
* @access public
* @return void
*/
public function buildZentaoList(int $docID, string $type, int $oldBlockID = 0)
{
$docblock = new stdClass();
$docblock->doc = $docID;
$docblock->type = $type;
$docblock->settings = $this->post->url;
if($type == 'gantt')
{
$docblock->content = json_encode(array('ganttOptions' => json_decode($this->post->ganttOptions), 'showFields' => $this->post->showFields, 'ganttFields' => json_decode($this->post->ganttFields)));
}
else
{
$docblock->content = json_encode(array('cols' => json_decode($this->post->cols), 'data' => json_decode($this->post->data), 'idList' => $this->post->idList));
}
$this->dao->insert(TABLE_DOCBLOCK)->data($docblock)->exec();
if(dao::isError()) return print(json_encode(array('result' => 'fail', 'message' => dao::getError())));
$newBlockID = $this->dao->lastInsertId();
return print(json_encode(array('result' => 'success', 'oldBlockID' => $oldBlockID, 'newBlockID' => $newBlockID)));
}
/**
* 我的空间。
* My space.
*
* @param int $objectID
* @param int $libID
* @param int $moduleID
* @param string $browseType all|draft|bysearch
* @param int $param
* @param string $orderBy
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function mySpace(int $objectID = 0, int $libID = 0, int $moduleID = 0, string $browseType = 'all', int $param = 0, string $orderBy = 'order_asc', int $recTotal = 0, int $recPerPage = 20, int $pageID = 1, string $mode = '', int $docID = 0, string $search = '')
{
$this->docZen->initLibForMySpace();
echo $this->fetch('doc', 'app', "type=mine&spaceID=$objectID&libID=$libID&moduleID=$moduleID&docID=$docID&mode=$mode&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&filterType=$browseType&search=$search");
}
/**
* 创建一个空间。
* Create a space.
*
* @param string $type
* @access public
* @return void
*/
public function createSpace(string $type = '')
{
if(!empty($_POST))
{
$this->lang->doc->name = $this->lang->doclib->spaceName;
$space = form::data()->setDefault('addedBy', $this->app->user->account)->get();
if($type) $space->type = $type;
$spaceID = $this->doc->doInsertLib($space);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->loadModel('action')->create('docspace', $spaceID, 'created');
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'load' => $this->createLink('doc', 'teamSpace', "objectID={$spaceID}"), 'docApp' => array('call' => 'selectSpace', 'args' => array($spaceID, true))));
}
$this->display();
}
/**
* 创建一个文档库。
* Create a library.
*
* @param string $type api|project|product|execution|custom|mine
* @param int $objectID
* @param int $libID
* @access public
* @return void
*/
public function createLib(string $type = '', int $objectID = 0, int $libID = 0)
{
$this->app->loadLang('api');
/* 项目模板下setMenu会修改common语言项,所以执行文档要先执行setAclForCreateLib。 */
if($type != 'project') $this->docZen->setAclForCreateLib($type);
$this->doc->setMenuByType($type, (int)$objectID, (int)$libID);
if($type == 'project') $this->docZen->setAclForCreateLib($type);
if(!empty($_POST))
{
$lib = $this->docZen->buildLibForCreateLib();
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$libID = $this->doc->createLib($lib, (string)$this->post->type, (string)$this->post->libType);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
if($type == 'custom' || $type == 'mine')
{
$lib = $this->doc->getLibByID($libID);
$objectID = $lib->parent;
}
return $this->docZen->responseAfterCreateLib($type, $objectID, $libID, $lib->name, $lib->orderBy);
}
$objects = array();
if($type == 'project')
{
$this->view->project = $this->project->getById($objectID);
if($this->app->tab == 'doc') $this->view->executionPairs = $this->execution->getPairs($objectID, 'all', 'multiple,leaf,noprefix');
}
if($type == 'execution')
{
$objects = $this->execution->getPairs(0, 'all', 'multiple,leaf,noprefix,withobject');
$execution = $this->execution->getByID($objectID);
if($execution->type == 'stage') $this->lang->doc->execution = str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->doc->execution);
$this->view->execution = $this->doc->fetchByID($objectID, 'execution');
}
if($type == 'custom' || $type == 'mine' || $type == 'doctemplate')
{
$lib = $this->doc->getLibByID($libID);
$this->view->spaces = $this->doc->getSubSpacesByType($type);
$this->view->spaceID = !empty($lib->parent) ? $lib->parent : $objectID;
}
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->user->getPairs('nocode|noclosed');
$this->view->objects = $objects;
$this->view->type = $type;
$this->view->objectID = $objectID;
$this->display();
}
/**
* 编辑一个文档空间。
* Edit a doc space.
*
* @param int $spaceID
* @access public
* @return void
*/
public function editSpace(int $spaceID)
{
$this->commonEditAction($spaceID);
$this->display();
}
/**
* 编辑一个文档库。
* Edit a library.
*
* @param int $libID
* @access public
* @return void
*/
public function editLib(int $libID)
{
$this->commonEditAction($libID);
$this->display();
}
/**
* 编辑空间和编辑库的公用方法。
* Edit a lib or space.
*
* @param int $libID
* @access public
* @return void
*/
public function commonEditAction($libID)
{
if(!common::hasPriv('doc', 'editSpace') && !common::hasPriv('doc', 'editLib')) return;
$lib = $this->doc->getLibByID($libID);
/* 项目模板下setMenu会修改common语言项,所以执行文档要先执行setAclForCreateLib。 */
if($lib->type != 'project') $this->docZen->setAclForEditLib($lib);
$this->doc->setMenuByType($lib->type, (int)zget($lib, $lib->type, 0), (int)$libID);
if($lib->type == 'project') $this->docZen->setAclForEditLib($lib);
if(!empty($_POST))
{
$this->lang->doc->name = $this->lang->nameAB;
$libData = form::data($this->config->doc->form->editlib)->get();
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$changes = $this->doc->updateLib($libID, $libData);
if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
if($changes)
{
$object = ($lib->type == 'custom' && $lib->parent == 0) ? 'docspace' : 'doclib';
$actionID = $this->action->create($object, $libID, 'edited');
$this->action->logHistory($actionID, $changes);
}
$lib = $this->doc->getLibByID($libID);
$docAppActions = array();
$docAppActions[] = array('update', ($lib->parent == 0) ? 'space' : 'lib', $lib);
$docAppActions[] = array('home:loadHomeLibs');
$docAppActions[] = array('list:load');
return $this->send(array('message' => $this->lang->saveSuccess, 'result' => 'success', 'closeModal' => true, 'docApp' => $docAppActions));
}
if(!empty($lib->product)) $this->view->object = $this->product->getByID($lib->product);
if(!empty($lib->project) && empty($lib->execution)) $this->view->object = $this->project->getByID($lib->project);
if(!empty($lib->execution))
{
$execution = $this->execution->getByID($lib->execution);
if($execution->type == 'stage')
{
if($execution->grade > 1)
{
$parentExecutions = $this->execution->getPairsByList(explode(',', trim($execution->path, ',')), 'stage,kanban,sprint');
$execution->name = implode('/', $parentExecutions);
}
$this->lang->doc->execution = str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->doc->execution);
}
$this->view->object = $execution;
}
$this->view->lib = $lib;
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->user->getPairs('noletter|noclosed', $lib->users);
$this->view->libID = $libID;
}
/**
* 删除一个文档空间。
* Delete a doc space.
*
* @param int $libID
* @access public
* @return void
*/
public function deleteSpace(int $libID)
{
$this->commonDeleteAction($libID);
}
/**
* 删除一个文档库。
* Delete a library.
*
* @param int $libID
* @access public
* @return void
*/
public function deleteLib(int $libID)
{
$this->commonDeleteAction($libID);
}
/**
* 删除空间和删除库的公用方法。
* Delete a lib or space.
*
* @param int $libID
* @access public
* @return void
*/
public function commonDeleteAction(int $libID)
{
if(in_array($libID, array('product', 'execution'))) return;
$lib = $this->doc->getLibByID($libID);
if(!empty($lib->main)) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->errorMainSysLib, 'load' => array('alert' => $this->lang->doc->errorMainSysLib)));
$this->dao->update(TABLE_DOCLIB)->set('deleted')->eq('1')->where('id')->eq($libID)->exec();
$object = ($lib->type == 'custom' && $lib->parent == 0) ? 'docspace' : 'doclib';
$this->loadModel('action')->create($object, $libID, 'deleted', '', ACTIONMODEL::CAN_UNDELETED);
$moduleName = 'doc';
$objectType = $lib->type;
$methodName = zget($this->config->doc->spaceMethod, $objectType);
if($this->app->tab == 'doc') return $this->send(array('result' => 'success', 'load' => $this->createLink($moduleName, $methodName), 'app' => $this->app->tab));
$objectID = strpos(',product,project,execution,', ",$objectType,") !== false ? $lib->{$objectType} : 0;
if($this->app->tab == 'execution' && $objectType == 'execution')
{
$moduleName = 'execution';
$methodName = 'doc';
}
$browseLink = $this->createLink($moduleName, $methodName, "objectID=$objectID");
return $this->send(array('result' => 'success', 'load' => $browseLink, 'app' => $this->app->tab));
}
/**
* 文档模板列表。
* Browse template list.
*
* @param int $libID
* @param string $type
* @param mixed $docID
* @param string $orderBy
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function browseTemplate(int $libID = 0, string $type = 'all', mixed $docID = 0, string $orderBy = 'id_desc', int $recPerPage = 20, int $pageID = 1, string $mode = 'home')
{
/* 添加内置的范围、分类、文档模板。*/
/* Add the built-in scopes and type and doc template. */
$builtInScopes = $this->dao->select('id')->from(TABLE_DOCLIB)->where('type')->eq('template')->andWhere('main')->eq('1')->fetchAll();
if(empty($builtInScopes))
{
$this->doc->addBuiltInScopes();
$this->doc->addBuiltInDocTemplateType();
$this->doc->addBuiltInDocTemplateByType();
}
$this->lang->doc->menu->template['alias'] .= ',' . $this->app->rawMethod;
$libModules = $this->doc->getTemplateModules($libID);
if($mode == 'create' && empty($libModules)) return $this->send(array('result' => 'success', 'load' => array('alert' => $this->lang->docTemplate->createTypeFirst)));
$templateList = $this->doc->getDocTemplateList(0, $type, $orderBy);
$templateList = $this->doc->filterDeletedDocs($templateList);
$templateList = $this->doc->filterPrivDocs($templateList, 'template');
$allModules = $this->doc->getTemplateModules();
$allModules = array_column($allModules, 'fullName', 'id');
foreach($templateList as $template) $template->moduleName = zget($allModules, $template->module);
$docVersion = 0;
if(is_string($docID) && strpos($docID, '_') !== false) list($docID, $docVersion) = explode('_', $docID);
$this->view->title = $this->lang->doc->template;
$this->view->libID = $libID;
$this->view->users = $this->loadModel('user')->getPairs('noclosed,noletter');
$this->view->templateList = $templateList;
$this->view->docID = $docID;
$this->view->docVersion = $docVersion;
$this->view->orderBy = $orderBy;
$this->view->recPerPage = $recPerPage;
$this->view->pageID = $pageID;
$this->view->mode = $mode;
$this->view->hasModules = count($libModules) ? true : false;
$this->view->scopes = $this->doc->getTemplateScopes();
$this->display();
}
/**
* 创建一个模板类型。
* Add a template type.
*
* @param int $scope
* @param int|string $parentModule
* @access public
* @return void
*/
public function addTemplateType(int $scope, int|string $parentModule = '')
{
$moduleList = $this->doc->getTemplateModules($scope, '1');
if(!empty($_POST))
{
$this->lang->doc->name = $this->lang->docTemplate->typeName;
$moduleData = form::data($this->config->doc->form->addTemplateType)
->setDefault('type', 'docTemplate')
->setDefault('path', '')
->get();
$moduleData->grade = $moduleData->parent === 0 ? 1 : 2;
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->doc->addTemplateType($moduleData);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->docZen->responseAfterAddTemplateType($scope);
}
$moduleItems = array();
if($parentModule === 0)
{
$moduleItems[] = array('value' => 0, 'text' => '/');
}
else
{
foreach($moduleList as $module) $moduleItems[] = array('value' => $module->id, 'text' => '/' . $module->name);
$moduleItems[] = array('value' => 0, 'text' => '/');
}
$scopeList = $this->doc->getTemplateScopes();
$this->view->scope = $scope;
$this->view->scopes = $this->doc->getScopeItems($scopeList);
$this->view->moduleItems = $moduleItems;
$this->view->parentModule = $parentModule === '' ? current($moduleItems) : $parentModule;
$this->display();
}
/**
* 删除一个模板类型。
* Delete a template type.
*
* @param int $moduleID
* @access public
* @return void
*/
public function deleteTemplateType(int $moduleID)
{
$templates = $this->doc->getTemplatesByType($moduleID);
if(empty($templates))
{
$this->dao->update(TABLE_MODULE)->set('deleted')->eq('1')->where('id')->eq($moduleID)->andWhere('type')->eq('docTemplate')->exec();
$this->dao->update(TABLE_MODULE)->set('deleted')->eq('1')->where('parent')->eq($moduleID)->andWhere('type')->eq('docTemplate')->exec();
return $this->sendSuccess(array('load' => true));
}
else
{
return $this->send(array('result' => 'fail', 'message' => $this->lang->docTemplate->errorDeleteType));
}
}
/**
* 编辑一个模板类型。
* Edit a template type.
*
* @param int $moduleID
* @access public
* @return void
*/
public function editTemplateType(int $moduleID)
{
echo $this->fetch('tree', 'edit', "moduleID={$moduleID}&type=docTemplate");
}
/**
* 创建一个文档模板。
* Create a doc template.
*
* @param int $moduleID
* @access public
* @return void
*/
public function createTemplate(int $moduleID = 0)
{
if(!empty($_POST))
{
$this->lang->doc->title = $_POST['type'] == 'chapter' ? $this->lang->doc->chapterName : $this->lang->docTemplate->title;
helper::setcookie('lastDocModule', $moduleID);
$docData = form::data()
->setDefault('addedBy', $this->app->user->account)
->setDefault('editedBy', $this->app->user->account)
->get();
if(empty($docData->module)) return $this->sendError(sprintf($this->lang->error->notempty, $this->lang->docTemplate->module));
if(!empty($docData->parent))
{
$parentTemplate = $this->doc->fetchByID((int)$docData->parent);
$docData->module = $parentTemplate->module;
$docData->lib = (int)$parentTemplate->lib;
$docData->acl = $parentTemplate->acl;
$docData->templateType = $parentTemplate->templateType;
}
$docResult = $this->doc->create($docData);
if(!$docResult || dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->docZen->responseAfterCreate($docResult, 'docTemplate');
}
}
/**
* 编辑一个文档。
* Edit a doc.
*
* @param int $docID
* @access public
* @return void
*/
public function editTemplate(int $docID)
{
$doc = $this->doc->getByID($docID);
if(!empty($_POST))
{
$this->lang->doc->module = $this->lang->docTemplate->module;
$changes = $files = array();
$docData = form::data()
->setDefault('editedBy', $this->app->user->account)
->setIF(!isset($_POST['parent']), 'parent', $doc->parent)
->setIF(strpos(",$doc->editedList,", ",{$this->app->user->account},") === false, 'editedList', $doc->editedList . ",{$this->app->user->account}")
->setIF($this->post->type == 'chapter', 'content', $doc->content)
->setIF($this->post->type == 'chapter', 'rawContent', $doc->rawContent)
->remove('fromVersion')
->get();
$result = $this->doc->update($docID, $docData);
if(dao::isError())
{
if(!empty(dao::$errors['lib']) || !empty(dao::$errors['keywords'])) return $this->send(array('result' => 'fail', 'message' => dao::getError(), 'callback' => "zui.Modal.open({id: 'modalBasicInfo'});"));
return $this->send(array('result' => 'fail', 'message' => dao::getError()));
}
$changes = $result['changes'];
$files = $result['files'];
return $this->docZen->responseAfterEditTemplate($doc, $changes, $files);
}
}
/**
* 上传文档。
* Upload docs.
*
* @param int $docID
* @param string $objectType product|project|execution|custom
* @param int $objectID
* @param int|string $libID
* @param int $moduleID
* @param string $docType html|word|ppt|excel|attachment
* @access public
* @return void
*/
public function uploadDocs(int $docID, string $objectType, int $objectID, int $libID, int $moduleID = 0, string $docType = '')
{
if(!empty($_POST))
{
/* parent带‘m_’前缀为目录。*/
if(isset($_POST['parent']) && strpos($_POST['parent'], 'm_') !== false)
{
$_POST['module'] = str_replace('m_', '', $_POST['parent']);
$_POST['parent'] = 0;
}
$libID = (int)$this->post->lib;
if(!$libID) return $this->send(array('result' => 'fail', 'message' => array('lib' => sprintf($this->lang->error->notempty, $this->lang->doc->lib))));
$doclib = $this->loadModel('doc')->getLibByID($libID);
$canVisit = $this->docZen->checkPrivForCreate($doclib, $objectType);
if(!$canVisit) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->accessDenied));
$moduleID = $this->post->module;
helper::setcookie('lastDocModule', $moduleID);
if(!isset($_POST['lib']) && strpos($_POST['module'], '_') !== false) list($_POST['lib'], $_POST['module']) = explode('_', $_POST['module']);
if($_POST['type'] == 'attachment' && empty($_FILES['files']['name'][0]))
{
$this->config->doc->form->create['title']['required'] = false;
$this->config->doc->form->create['title']['skipRequired'] = true;
}
$this->config->doc->create->requiredFields = trim(str_replace(array(',content,', ',keywords,'), ",", ",{$this->config->doc->create->requiredFields},"), ',');
$docData = form::data(!empty($docID) ? $this->config->doc->form->edit : $this->config->doc->form->create)->remove('fromVersion')->get();
if(empty($docID)) $docData->addedBy = $this->app->user->account;
if(!empty($docID)) $docData->editedBy = $this->app->user->account;
if(!empty($docID))
{
$docResult = $this->doc->update($docID, $docData);
$docData->id = $docID;
}
elseif($this->post->uploadFormat == 'combinedDocs')
{
$docResult = $this->doc->create($docData);
}
else
{
$docResult = $this->doc->createSeperateDocs($docData);
}
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return !empty($docID) ? $this->send($this->docZen->responseAfterEdit($docData, $docResult['changes'], $docResult['files'], $docResult['deletedFiles'])) : $this->docZen->responseAfterUploadDocs($docResult);
}
if($objectType == 'execution' && $libID) // 此时传入的objectID是projectID,用lib的信息更改回executionID
{
$lib = $this->doc->getLibByID($libID);
$objectID = $this->doc->getObjectIDByLib($lib);
}
$this->docZen->assignVarsForUploadDocs($docID, $objectType, $objectID, $libID, $moduleID, $docType);
$this->display();
}
/**
* 创建一个文档。
* Create a doc.
*
* @param string $objectType product|project|execution|custom
* @param int $objectID
* @param int|string $libID
* @param int $moduleID
* @param string $docType html|word|ppt|excel
* @param int $appendLib
* @access public
* @return void
*/
public function create(string $objectType, int $objectID, int $libID, int $moduleID = 0, string $docType = '', int $appendLib = 0)
{
if(!empty($_POST))
{
/* parent带‘m_’前缀为目录。*/
if(isset($_POST['parent']) && strpos($_POST['parent'], 'm_') !== false)
{
$_POST['module'] = str_replace('m_', '', $_POST['parent']);
$_POST['parent'] = 0;
}
$libID = (int)$this->post->lib;
$docLib = $this->loadModel('doc')->getLibByID($libID);
if($docLib)
{
$canVisit = $this->docZen->checkPrivForCreate($docLib, $objectType);
if(!$canVisit) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->accessDenied));
}
$moduleID = $this->post->module;
helper::setcookie('lastDocModule', $moduleID);
if(!isset($_POST['lib']) && strpos($_POST['module'], '_') !== false) list($_POST['lib'], $_POST['module']) = explode('_', $_POST['module']);
if($_POST['type'] == 'chapter') $this->lang->doc->title = $this->lang->doc->chapterName;
$docData = form::data()
->setDefault('addedBy', $this->app->user->account)
->setDefault('editedBy', $this->app->user->account)
->get();
if($docData->parent && !$docData->module)
{
$parentDoc = $this->doc->getByID($docData->parent);
$docData->module = $parentDoc->module;
}
$docResult = $this->doc->create($docData);
if(!$docResult || dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->docZen->responseAfterCreate($docResult);
}
$this->docZen->assignVarsForCreate($objectType, $objectID, $libID, $moduleID, $docType);
$lib = $this->view->lib;
if(empty($lib)) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->errorEmptySpaceLib));
$objectID = $this->view->objectID;
$libData = $this->doc->setMenuByType($lib->type, (int)$objectID, (int)$lib->id, (int)$appendLib);
if(is_string($libData)) return $this->locate($libData);
list($libs, $libID, $object, $objectID, $objectDropdown) = $libData;
if($this->config->edition != 'open') $this->loadModel('file');
if($lib->type == 'custom' || $lib->type == 'mine') $this->view->spaces = $this->docZen->getAllSpaces($lib->type == 'mine' ? 'onlymine' : 'nomine');
$this->view->title = zget($lib, 'name', '', $lib->name . $this->lang->hyphen) . $this->lang->doc->create;
$this->view->object = $object;
$this->view->objectDropdown = $objectDropdown;
$this->view->libTree = $this->doc->getLibTree((int)$libID, $libs, $objectType, (int)$moduleID, (int)$objectID);
$this->view->linkParams = "objectID={$objectID}&%s&browseType=&orderBy=status,id_desc¶m=0";
if(!empty($objectType) && $objectType !== 'custom')
{
$objectIDVar = $objectType . 'ID';
$this->view->$objectIDVar = $objectID;
}
$this->display();
}
/**
* 编辑一个文档。
* Edit a doc.
*
* @param int $docID
* @param bool $comment
* @param int $appendLib
* @access public
* @return void
*/
public function edit(int $docID, bool $comment = false, int $appendLib = 0)
{
$doc = $this->doc->getByID($docID);
if(!empty($_POST))
{
/* parent带‘m_’前缀为目录。*/
if(isset($_POST['parent']) && strpos($_POST['parent'], 'm_') !== false)
{
$_POST['module'] = str_replace('m_', '', $_POST['parent']);
$_POST['parent'] = 0;
}
if(!$doc) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->errorNotFound));
$isOpen = $doc->acl == 'open';
$currentAccount = $this->app->user->account;
$isAuthorOrAdmin = $doc->acl == 'private' && ($doc->addedBy == $currentAccount || $this->app->user->admin);
$isInEditUsers = strpos(",$doc->users,", ",$currentAccount,") !== false;
$isInEditGroups = false;
if(!empty($doc->groups))
{
foreach($this->app->user->groups as $groupID)
{
if(strpos(",$doc->groups,", ",$groupID,") !== false) $isInEditGroups = true;
}
}
if(!$isOpen && !$isAuthorOrAdmin && !$isInEditUsers && !$isInEditGroups) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->needEditable));
if($doc->type == 'chapter') $this->lang->doc->title = $this->lang->doc->chapterName;
$changes = $files = array();
if($comment == false)
{
$docData = form::data()
->setDefault('editedBy', $this->app->user->account)
->setDefault('acl', $doc->acl)
->setIF(strpos(",$doc->editedList,", ",{$this->app->user->account},") === false, 'editedList', $doc->editedList . ",{$this->app->user->account}")
->setIF(!isset($_POST['module']), 'module', $doc->module)
->setIF(!isset($_POST['mailto']), 'mailto', $doc->mailto)
->setIF(!isset($_POST['users']), 'users', $doc->users)
->setIF(!isset($_POST['groups']), 'groups', $doc->groups)
->setIF(!isset($_POST['readUsers']), 'readUsers', $doc->readUsers)
->setIF(!isset($_POST['readGroups']), 'readGroups', $doc->readGroups)
->setIF(!isset($_POST['fromVersion']), 'fromVersion', $doc->fromVersion)
->removeIF($this->post->project === false, 'project')
->removeIF($this->post->product === false, 'product')
->removeIF($this->post->execution === false, 'execution')
->get();
$result = $this->doc->update($docID, $docData);
if(dao::isError())
{
if(!empty(dao::$errors['lib']) || !empty(dao::$errors['keywords'])) return $this->send(array('result' => 'fail', 'message' => dao::getError(), 'callback' => "zui.Modal.open({id: 'modalBasicInfo'});"));
return $this->send(array('result' => 'fail', 'message' => dao::getError()));
}
$changes = $result['changes'];
$files = $result['files'];
}
return $this->send($this->docZen->responseAfterEdit($doc, $changes, $files));
}
/* Get doc and set menu. */
$moduleID = (int)$doc->module;
$libID = (int)$doc->lib;
$lib = $this->doc->getLibByID($libID);
$objectType = isset($lib->type) ? $lib->type : 'custom';
$objectID = zget($lib, $lib->type, 0);
if($lib->type == 'custom') $objectID = $lib->parent;
if($doc->type == 'text' || $doc->type == 'article')
{
echo $this->fetch('doc', 'app', "type=$objectType&spaceID=$objectID&libID=$libID&moduleID=$doc->module&docID=$docID&mode=edit");
return;
}
$libPairs = $this->doc->getLibs($lib->type, '', $doc->lib, $objectID);
list($libs, $libID, $object, $objectID, $objectDropdown) = $this->doc->setMenuByType($objectType, $objectID, (int)$doc->lib, (int)$appendLib);
if($lib->type == 'custom' || $lib->type == 'mine') $this->view->spaces = $this->docZen->getAllSpaces($lib->type == 'mine' ? 'onlymine' : 'nomine');
$this->docZen->setObjectsForEdit($lib->type, $objectID);
$this->view->title = $lib->name . $this->lang->hyphen . $this->lang->doc->edit;
$this->view->doc = $doc;
$this->view->optionMenu = $this->loadModel('tree')->getOptionMenu($libID, 'doc', 0);
$this->view->type = $lib->type;
$this->view->libs = $libPairs;
$this->view->lib = $lib;
$this->view->libID = $libID;
$this->view->libTree = $this->doc->getLibTree($libID, $libs, $objectType, $moduleID, (int)$objectID, '', 0, $docID);
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->user->getPairs('noletter|noclosed|nodeleted', $doc->users);
$this->view->files = $this->loadModel('file')->getByObject('doc', $docID);
$this->view->objectType = $objectType;
$this->view->object = $object;
$this->view->objectID = $objectID;
$this->view->objectDropdown = $objectDropdown;
$this->view->moduleID = $moduleID;
$this->view->spaceType = $objectType;
$this->view->productID = $doc->product;
$this->view->projectID = $doc->project;
$this->view->executionID = $doc->execution;
$this->view->linkParams = "objectID={$objectID}&%s&browseType=&orderBy=status,id_desc¶m=0";
$this->view->otherEditing = $this->doc->checkOtherEditing($docID);
$this->display();
}
/**
* 删除一个文档。
* Delete a doc.
*
* @param int $docID
* @access public
* @return void
*/
public function delete(int $docID)
{
$this->doc->delete(TABLE_DOC, $docID);
/* Delete doc files. */
$doc = $this->doc->getByID($docID);
if($doc->files) $this->doc->deleteFiles(array_keys($doc->files));
if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->sendSuccess(array('load' => $this->session->docList ? $this->session->docList : $this->createLink('doc', 'index')));
}
/**
* 删除一个文档模板。
* Delete a doctemplate.
*
* @param int $templateID
* @access public
* @return void
*/
public function deleteTemplate(int $templateID)
{
$this->doc->deleteTemplate($templateID);
/* Delete template files. */
$template = $this->doc->getByID($templateID);
if($template->files) $this->doc->deleteFiles(array_keys($template->files));
if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError()));
$link = $this->createLink('doc', 'browseTemplate');
return $this->sendSuccess(array('load' => $link));
}
/**
* 收藏一个文档。
* Collect a doc.
*
* @param int $objectID
* @access public
* @return void
*/
public function collect(int $objectID)
{
$action = $this->doc->getActionByObject($objectID, 'collect');
if($action)
{
$this->doc->deleteAction($action->id);
$this->action->create('doc', $objectID, 'uncollected');
}
else
{
$this->doc->createAction($objectID, 'collect');
$this->action->create('doc', $objectID, 'collected');
}
if($this->viewType == 'json') $this->send(array('status' => $action ? 'no' : 'yes'));
return $this->send(array('result' => 'success', 'message' => $action ? $this->lang->doc->cancelCollection : $this->lang->doc->collectSuccess, 'load' => true, 'status' => $action ? 'no' : 'yes'));
}
/**
*
* AJAX: 获取对象的目录。
* Ajax get modules by object.
*
* @param string $objectType project|product|custom|mine
* @param int $objectID
* @param string $docType doc|api
* @param int $docID
* @param int $libID
* @access public
* @return void
*/
public function ajaxGetModules(string $objectType, int $objectID, string $docType = 'doc', int $docID = 0, int $libID = 0)
{
if($objectType != 'mine' && empty($objectID)) return print(json_encode(array()));
if($docType == 'doc')
{
$unclosed = strpos($this->config->doc->custom->showLibs, 'unclosed') !== false ? 'unclosedProject' : '';
$libPairs = $this->doc->getLibs($objectType, $unclosed, '', (int)$objectID);
}
elseif($docType == 'api')
{
$libs = $this->doc->getApiLibs(0, $objectType, $objectID);
$libPairs = array();
foreach($libs as $lib) $libPairs[$lib->id] = $lib->name;
}
$libItems = array();
foreach($libPairs as $id => $name) $libItems[] = array('text' => $name, 'value' => $id, 'keys' => $name);
$moduleItems = array();
$libID = $libID ? $libID : key($libPairs);
if($libID)
{
$chapterAndDocs = $this->doc->getDocsOfLibs(array($libID), $objectType, $docID);
$modulePairs = empty($libID) ? array() : $this->loadModel('tree')->getOptionMenu($libID, 'doc', 0);
$nestedDocs = $this->doc->buildNestedDocs($chapterAndDocs, $modulePairs);
$moduleItems = array_merge(array(array('text' => '/', 'value' => 'm_0')), array_values($nestedDocs));
}
return print(json_encode(array('libs' => $libItems, 'modules' => $moduleItems)));
}
/**
*
* AJAX: 获取范围下的类型。
* Ajax get types of scope.
*
* @param int $libID
* @access public
* @return json
*/
public function ajaxGetScopeTypes(int $libID = 0)
{
$optionMenu = $this->loadModel('tree')->getOptionMenu($libID, 'docTemplate', 0, 'all', 'nodeleted', 'all');
$types = array();
foreach($optionMenu as $menuID => $menu)
{
if(empty($menuID)) continue;
$types[] = array('text' => $menu, 'value' => $menuID, 'keys' => $menu);
}
return print(json_encode($types));
}
/**
* AJAX: 通过空间类型获取文档库。
* AJAX: Get libs by type.
*
* @param string $type mine|product|project|nolink|custom
* @param string $docType doc|api
* @access public
* @return void
*/
public function ajaxGetLibsByType(string $type, string $docType = 'doc', string $extra = '')
{
$libPairs = array();
if($docType == 'doc')
{
$unclosed = strpos($this->config->doc->custom->showLibs, 'unclosed') !== false ? 'unclosedProject' : '';
if($extra) $unclosed .= ",$extra";
$libPairs = $this->doc->getLibs($type, $unclosed);
}
elseif($docType == 'api')
{
$libs = $this->doc->getApiLibs(0, 'nolink');
$libPairs = array();
foreach($libs as $libID => $lib) $libPairs[$libID] = $lib->name;
}
$libItems = array();
foreach($libPairs as $id => $name) $libItems[] = array('text' => $name, 'value' => $id, 'keys' => $name);
$moduleItems = array();
$libID = key($libPairs);
if($libID)
{
$optionMenu = $this->loadModel('tree')->getOptionMenu($libID, $docType, 0);
foreach($optionMenu as $id => $name) $moduleItems[] = array('text' => $name, 'value' => $id, 'keys' => $name);
}
return print(json_encode(array('libs' => $libItems, 'modules' => $moduleItems)));
}
/**
* Ajax: 获取白名单。
* Ajax: Get whitelist.
*
* @param int $doclibID
* @param string $acl open|custom|private
* @param string $control user|group
* @param int $docID
* @access public
* @return string
*/
public function ajaxGetWhitelist(int $doclibID, string $acl = '', string $control = '', int $docID = 0)
{
$doclib = $this->doc->getLibByID($doclibID);
$users = $this->user->getPairs('noletter|noempty|noclosed');
if($control == 'group')
{
if($doclib->acl == 'private') return print('private');
$groupItems = array();
$groups = $this->loadModel('group')->getPairs();
foreach($groups as $groupID => $groupName) $groupItems[] = array('text' => $groupName, 'value' => $groupID, 'keys' => $groupName);
return print(json_encode($groupItems));
}
if($doclib->acl == 'private') return print('private');
$userItems = array();
foreach($users as $account => $realname)
{
if($doclib->acl == 'private' && strpos($doclib->users, (string)$account) === false ) unset($users[$account]);
$userItems[] = array('text' => $realname, 'value' => $account, 'keys' => $realname);
}
return print(json_encode($userItems));
}
/**
* 附件库。
* Show files.
*
* @param string $type
* @param int $objectID
* @param string $viewType
* @param string $browseType
* @param int $param
* @param string $orderBy
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @param string $searchTitle
* @access public
* @return void
*/
public function showFiles(string $type, int $objectID, string $viewType = '', string $browseType = '', int $param = 0, string $orderBy = 'id_desc', int $recTotal = 0, int $recPerPage = 20, int $pageID = 1, string $searchTitle = '')
{
$this->loadModel('file');
if(empty($viewType)) $viewType = !empty($_COOKIE['docFilesViewType']) ? $this->cookie->docFilesViewType : 'list';
helper::setcookie('docFilesViewType', $viewType, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, true);
$objects = $this->doc->getOrderedObjects($type, 'nomerge', $objectID);
list($libs, $libID, $object, $objectID, $objectDropdown) = $this->doc->setMenuByType($type, $objectID, 0);
$object = $this->doc->getObjectByID($type, $objectID);
if(empty($_POST) && !empty($searchTitle)) $this->post->title = $searchTitle;
$this->session->set('storyList', $this->app->getURI(true), 'doc');
$this->session->set('bugList', $this->app->getURI(true), 'doc');
/* Load pager. */
$rawMethod = $this->app->rawMethod;
$this->app->rawMethod = 'showFiles';
$this->app->loadClass('pager', $static = true);
$pager = new pager($recTotal, $recPerPage, $pageID);
$this->app->rawMethod = $rawMethod;
$files = $this->doc->getLibFiles($type, $objectID, $browseType, (int)$param, $orderBy, $pager);
$fileIcon = $this->doc->getFileIcon($files);
$sourcePairs = $this->doc->getFileSourcePairs($files);
if($this->app->tab != 'doc')
{
$objectVar = $this->app->tab . 'ID';
$this->view->{$objectVar} = $objectID;
}
$this->docZen->buildSearchFormForShowFiles($type, $objectID, $viewType, $param);
$this->view->title = $object->name;
$this->view->type = $type;
$this->view->object = $object;
$this->view->files = $this->docZen->processFiles($files, $fileIcon, $sourcePairs);
$this->view->fileIcon = $fileIcon;
$this->view->sourcePairs = $sourcePairs;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->pager = $pager;
$this->view->viewType = $viewType;
$this->view->orderBy = $orderBy;
$this->view->objectID = $objectID;
$this->view->canBeChanged = common::canModify($type, $object); // Determines whether an object is editable.
$this->view->summary = $this->doc->summary($files);
$this->view->libTree = $this->doc->getLibTree(0, $libs, $type, 0, $objectID);
$this->view->objectDropdown = $objectDropdown;
$this->view->searchTitle = $searchTitle;
$this->view->linkParams = "objectID=$objectID&%s&browseType=&orderBy=status,id_desc¶m=0";
$this->view->spaceType = $type;
$this->view->objectType = $type;
$this->view->browseType = $browseType;
$this->view->param = $param;
$this->view->libID = 0;
$this->view->moduleID = 0;
$this->display();
}
/**
* 文档详情页面。
* Document details page.
*
* @param string|int $docID
* @param int $version
* @access public
* @return void
*/
public function view(string|int $docID = 0, int $version = 0)
{
$isApi = is_string($docID) && strpos($docID, 'api.') === 0;
$docID = $isApi ? (int)str_replace('api.', '', $docID) : (int)$docID;
$doc = $isApi ? $this->loadModel('api')->getByID($docID, $version) : $this->doc->getByID($docID, $version, true);
$docParam = $version ? ($docID . '_' . $version) : $docID;
if($isApi) $docParam = 'api.' . $docParam;
if(!$doc || !isset($doc->id))
{
if(defined('RUN_MODE') && RUN_MODE == 'api') return $this->send(array('status' => 'fail', 'code' => 404, 'message' => '404 Not found'));
return $this->sendError($this->lang->notFound, $this->inlink('index'));
}
$_SESSION["doc_{$doc->id}_nopriv"] = '';
if(!$isApi && !$this->doc->checkPrivDoc($doc))
{
$user = $this->loadModel('user')->getByID($doc->addedBy);
$realname = $user->deleted ? $user->account : $user->realname;
$errorMessage = empty($_SESSION["doc_{$doc->id}_nopriv"]) ? sprintf($this->lang->doc->cannotView, $realname) : $_SESSION["doc_{$doc->id}_nopriv"];
unset($_SESSION["doc_{$doc->id}_nopriv"]);
return $this->sendError($errorMessage, inlink('index'));
}
unset($_SESSION["doc_{$doc->id}_nopriv"]);
if(!empty($doc->templateType))
{
echo $this->fetch('doc', 'browseTemplate', "libID=$doc->lib&type=all&docID=$docParam&orderBy=id_desc&recPerPage=20&pageID=1&mode=view");
return;
}
$lib = $this->doc->getLibByID((int)$doc->lib);
$objectType = isset($lib->type) ? $lib->type : 'custom';
if($objectType == 'api')
{
if(isset($lib->product) && $lib->product) list($objectType, $objectID) = array('product', $lib->product);
if(isset($lib->project) && $lib->project) list($objectType, $objectID) = array('project', $lib->project);
}
else
{
if($this->app->tab === 'project' && $objectType === 'execution') $objectType = 'project';
$objectID = $this->doc->getObjectIDByLib($lib, $objectType);
}
/* Get doc. */
if($docID) $this->doc->createAction($docID, 'view');
$this->view->title = $doc->title;
$libID = isset($lib->id) ? $lib->id : 0;
echo $this->fetch('doc', 'app', "type=$objectType&spaceID=$objectID&libID=$libID&moduleID=$doc->module&docID=$docParam&mode=view");
}
/**
* 产品/项目/执行/团队空间。
* ProductSpace/ProjectSpace/ExecutionSpace/TeamSpace.
*
* @param string $type custom|product|project|execution|doctemplate
* @param int $objectID
* @param int $libID
* @param int $moduleID
* @param string $browseType all|draft|bysearch
* @param string $orderBy
* @param int $param
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function tableContents(string $type = 'custom', int $objectID = 0, int $libID = 0, int $moduleID = 0, string $browseType = 'all', string $orderBy = 'order_asc', int $param = 0, int $recTotal = 0, int $recPerPage = 20, int $pageID = 1)
{
$this->docZen->setSpacePageStorage($type, $browseType, $objectID, $libID, $moduleID, $param);
if($moduleID) $libID = $this->tree->getById($moduleID)->root;
$isFirstLoad = $libID == 0 ? true : false;
if(empty($browseType)) $browseType = 'all';
$libData = $this->doc->setMenuByType($type, $objectID, $libID);
if(is_string($libData)) return $this->locate($libData);
list($libs, $libID, $object, $objectID, $objectDropdown) = $libData;
$libID = (int)$libID;
$lib = $this->doc->getLibByID($libID);
$libType = isset($lib->type) && $lib->type == 'api' ? 'api' : 'lib';
/* Build the search form. */
$queryID = $browseType == 'bySearch' ? $param : 0;
$queryID = $queryID == 'myQueryID' ? 0 : $queryID;
$params = "objectID={$objectID}&libID={$libID}&moduleID=0&browseType=bySearch&orderBy={$orderBy}¶m=myQueryID";
if($this->app->rawMethod == 'tablecontents') $params = "type={$type}&" . $params;
$actionURL = $this->createLink($this->app->rawModule, $this->app->rawMethod, $params);
if($libType == 'api') $this->loadModel('api')->buildSearchForm($lib, $queryID, $actionURL, $libs, $type);
if($libType != 'api') $this->doc->buildSearchForm($libID, $libs, $queryID, $actionURL, $type);
$this->docZen->assignApiVarForSpace($type, $browseType, $libType, $libID, $libs, $objectID, $moduleID, $queryID, $orderBy, $param, $recTotal, $recPerPage, $pageID);
/* For product drop menu. */
if(in_array($type, array('product', 'project', 'execution')))
{
$objectKey = $type . 'ID';
$this->view->$objectKey = $objectID;
}
$isOrderByOrder = $orderBy == 'order_asc';
$isNotCustomLib = $lib && !($lib->type == 'custom' && $lib->parent == 0);
$canUpdateOrder = $isOrderByOrder && $isNotCustomLib && common::hasPriv('doc', 'sortDoc');
$this->view->title = $type == 'custom' ? $this->lang->doc->tableContents : $object->name . $this->lang->hyphen . $this->lang->doc->tableContents;
$this->view->type = $type;
$this->view->objectType = $type;
$this->view->spaceType = $type;
$this->view->browseType = $browseType;
$this->view->isFirstLoad = $isFirstLoad;
$this->view->param = $queryID;
$this->view->users = $this->user->getPairs('noletter');
$this->view->libTree = $this->doc->getLibTree($libID, $libs, $type, $moduleID, $objectID, $browseType, (int)$param);
$this->view->libID = $libID;
$this->view->moduleID = $moduleID;
$this->view->objectDropdown = $objectDropdown;
$this->view->lib = $lib;
$this->view->libType = $libType;
$this->view->objectID = $objectID;
$this->view->orderBy = $orderBy;
$this->view->release = $browseType == 'byrelease' ? $param : 0;
$this->view->exportMethod = $libType == 'api' ? 'export' : $type . '2export';
$this->view->linkParams = "objectID={$objectID}&%s&browseType=&orderBy={$orderBy}¶m=0";
$this->view->canUpdateOrder = $canUpdateOrder;
$this->display();
}
/**
* 产品空间。
* Product space.
*
* @param int $objectID
* @param int $libID
* @param int $moduleID
* @param string $browseType all|draft|bysearch
* @param string $orderBy
* @param int $param
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function productSpace(int $objectID = 0, int $libID = 0, int $moduleID = 0, string $browseType = 'all', string $orderBy = '', int $param = 0, int $recTotal = 0, int $recPerPage = 20, int $pageID = 1, string $mode = '', int $docID = 0, string $search = '')
{
$noSpace = $this->app->tab != 'doc';
$mode = empty($mode) && $noSpace ? 'list' : $mode;
echo $this->fetch('doc', 'app', "type=product&spaceID=$objectID&libID=$libID&moduleID=$moduleID&docID=$docID&mode=$mode&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&filterType=$browseType&search=$search&noSpace=$noSpace");
}
/**
* 项目空间。
* Project space.
*
* @param int $objectID
* @param int $libID
* @param int $moduleID
* @param string $browseType all|draft|bysearch
* @param string $orderBy
* @param int $param
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function projectSpace(int $objectID = 0, int $libID = 0, int $moduleID = 0, string $browseType = 'all', string $orderBy = '', int $param = 0, int $recTotal = 0, int $recPerPage = 20, int $pageID = 1, string $mode = '', int $docID = 0, string $search = '')
{
$noSpace = $this->app->tab != 'doc';
$mode = empty($mode) && $noSpace ? 'list' : $mode;
echo $this->fetch('doc', 'app', "type=project&spaceID=$objectID&libID=$libID&moduleID=$moduleID&docID=$docID&mode=$mode&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&filterType=$browseType&search=$search&noSpace=$noSpace");
}
/**
* 团队空间。
* Team space.
*
* @param int $objectID
* @param int $libID
* @param int $moduleID
* @param string $browseType all|draft|bysearch
* @param string $orderBy
* @param int $param
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function teamSpace(int $objectID = 0, int $libID = 0, int $moduleID = 0, string $browseType = 'all', string $orderBy = '', int $param = 0, int $recTotal = 0, int $recPerPage = 20, int $pageID = 1, string $mode = '', int $docID = 0, string $search = '')
{
$this->docZen->initLibForTeamSpace();
echo $this->fetch('doc', 'app', "type=custom&spaceID=$objectID&libID=$libID&moduleID=$moduleID&docID=$docID&mode=$mode&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&filterType=$browseType&search=$search");
}
/**
* 设置选择文档库类型的范围。
* Set the scope of the document library type to be selected.
*
* @param string $objectType
* @param string $params
* @param string $from
* @access public
* @return void
*/
public function selectLibType($objectType = 'mine', $params = '', $from = '')
{
if($_POST)
{
$response = array();
$libID = $_POST['lib'];
$moduleID = $_POST['module'];
$rootSpace = $_POST['rootSpace'];
$docType = isset($_POST['type']) ? $_POST['type'] : 'doc';
if(empty($libID)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->error->notempty, $this->lang->doc->lib)));
$spaceID = 0;
if(in_array($rootSpace, array('mine', 'custom', 'product', 'project')))
{
$methodList = array('mine' => 'mySpace', 'custom' => 'teamSpace', 'product' => 'productSpace', 'project' => 'projectSpace');
$spaceID = $_POST[$rootSpace];
$method = $methodList[$rootSpace];
if($docType == 'doc') $url = $this->createLink('doc', $method, "objectID=$spaceID&libID=$libID&moduleID=$moduleID&browseType=all¶ms=0&orderBy=&recTotal=0&recPerPage=20&pageID=1&mode=create");
else $url = helper::createLink('api', 'create', "libID=$libID&moduleID=$moduleID&space=$rootSpace");
}
elseif($rootSpace == 'api')
{
$url = helper::createLink('api', 'create', "libID=$libID&moduleID=$moduleID&space=$rootSpace");
}
$response['result'] = 'success';
$response['closeModal'] = true;
$response['callback'] = "redirectParentWindow(\"{$url}\", \"{$from}\", \"{$spaceID}\", \"{$libID}\", \"{$moduleID}\")";
return $this->send($response);
}
$spaceList = $this->lang->doc->spaceList;
$typeList = $this->lang->doc->types;
if(!common::hasPriv('doc', 'create')) unset($spaceList['mine'], $spaceList['custom'], $typeList['doc']);
if(!common::hasPriv('doc', 'mySpace')) unset($spaceList['mine']);
if(!common::hasPriv('doc', 'productSpace')) unset($spaceList['product']);
if(!common::hasPriv('doc', 'projectSpace')) unset($spaceList['project']);
if(!common::hasPriv('doc', 'teamSpace')) unset($spaceList['custom']);
if(!common::hasPriv('api', 'index')) unset($spaceList['api']);
if(!common::hasPriv('api', 'create')) unset($spaceList['api'], $typeList['api']);
if($this->config->vision == 'lite') unset($spaceList['api'], $spaceList['product'], $typeList['api']);
$params = helper::safe64Decode($params);
parse_str($params, $params);
$this->view->params = $params;
$this->view->objectType = $objectType;
$this->view->spaceList = $spaceList;
$this->view->typeList = $typeList;
$this->view->from = $from;
$this->display();
}
/**
* Ajax: 根据对象类型设置下拉菜单。
* Ajax: Set the drop-down menu according to the object type.
*
* @param string $objectType product|project
* @param int $objectID
* @param string $module
* @param string $method
* @access public
* @return void
*/
public function ajaxGetDropMenu(string $objectType, int $objectID, string $module, string $method)
{
list($myObjects, $normalObjects, $closedObjects) = $this->doc->getOrderedObjects($objectType, 'nomerge', $objectID);
$libData = $this->doc->setMenuByType($objectType, $objectID, 0);
$this->view->objectType = $objectType;
$this->view->objectID = $objectID;
$this->view->libID = !empty($libData[1]) ? $libData[1] : 0;
$this->view->module = $module;
$this->view->method = $method == 'view' ? $objectType.'space' : $method;
$this->view->normalObjects = $myObjects + $normalObjects;
$this->view->closedObjects = $closedObjects;
$this->view->objectsPinYin = common::convert2Pinyin($myObjects + $normalObjects + $closedObjects);
$this->display();
}
/**
* Ajax: 获取空间的下拉数据。
* Ajax: Get the space drop down.
*
* @param int $libID
* @param string $module
* @param string $method
* @access public
* @return void
*/
public function ajaxGetSpaceMenu(int $libID, string $module, string $method, string $extra = 'nomine')
{
$this->view->libID = $libID;
$this->view->module = $module;
$this->view->method = $method;
$this->view->extra = $extra;
$this->view->spaces = $this->docZen->getAllSpaces($extra);
$this->display();
}
/**
* Ajax: 获取执行下拉数据。
* Ajax: Get the execution drop down by the projectID.
*
* @param int $projectID
* @access public
* @return string
*/
public function ajaxGetExecution(int $projectID)
{
$json = array();
$items = array();
$executionPairs = $this->execution->getPairs($projectID, 'all', 'multiple,leaf,noprefix');
foreach($executionPairs as $id => $name) $items[] = array('text' => $name, 'value' => $id, 'keys' => $name);
$json['items'] = $items;
$json['project'] = $this->project->getByID($projectID);
return print(json_encode($json));
}
/**
* 编辑一个目录。
* Edit a catalog.
*
* @param int $moduleID
* @param string $type doc|api
* @access public
* @return void
*/
public function editCatalog(int $moduleID, string $type)
{
echo $this->fetch('tree', 'edit', "moduleID={$moduleID}&type={$type}");
}
/**
* 删除一个目录。
* Delete a catalog.
*
* @param int $moduleID
* @access public
* @return void
*/
public function deleteCatalog(int $moduleID)
{
echo $this->fetch('tree', 'delete', "moduleID={$moduleID}&confirm=yes");
}
/**
* 设置左侧树中是否显示文档。
* Set documents are show or hidden in the left tree.
*
* @access public
* @return void
*/
public function displaySetting()
{
$this->loadModel('setting');
if($_POST)
{
$this->setting->setItem($this->app->user->account . '.doc.showDoc', $this->post->showDoc);
return $this->sendSuccess(array('load' => true, 'closeModal' => true));
}
$showDoc = $this->setting->getItem('owner=' . $this->app->user->account . '&module=doc&key=showDoc');
$this->view->showDoc = $showDoc === '0' ? '0' : '1';
$this->display();
}
/**
* 文档库排序
* Doclib sorting.
*
* @access public
* @return void
*/
public function sortDoclib()
{
if($_POST)
{
$orders = $_POST['orders'];
if(is_string($orders)) $orders = json_decode($orders, true);
foreach($orders as $id => $order) $this->doc->updateDoclibOrder($id, (int)$order);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->send(array('result' => 'success', 'orders' => $orders));
}
}
/**
* 文档排序
* Doc sorting.
*
* @access public
* @return void
*/
public function sortDoc()
{
if($_POST)
{
$orders = json_decode($this->post->orders, true);
asort($orders);
$orders = array_flip($orders);
/* Sort by sorted id list. */
$this->doc->updateDocOrder($orders);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->send(array('result' => 'success'));
}
}
/**
* 目录排序
* Catalog sorting.
*
* @access public
* @return void
*/
public function sortCatalog()
{
if($_POST)
{
$orders = $_POST['orders'];
$type = isset($_POST['type']) ? $_POST['type'] : 'doc';
if(is_string($orders)) $orders = json_decode($orders, true);
foreach($orders as $id => $order) $this->doc->updateOrder($id, (int)$order, $type);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->send(array('result' => 'success'));
}
}
/**
* 删除文档附件。
* Delete file for doc.
*
* @param int $docID
* @param int $fileID
* @param string $confirm
* @access public
* @return void
*/
public function deleteFile(int $docID, int $fileID, string $confirm = 'no')
{
$this->loadModel('file');
if($confirm == 'no')
{
$formUrl = inlink('deleteFile', "docID={$docID}&fileID={$fileID}&confirm=yes");
return $this->send(array('result' => 'fail', 'callback' => "zui.Modal.confirm('{$this->lang->file->confirmDelete}').then((res) => {if(res) $.ajaxSubmit({url: '$formUrl'});});"));
}
else
{
$this->doc->updateDocFile($docID, $fileID);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$file = $this->file->getById($fileID);
$this->action->create($file->objectType, $file->objectID, 'deletedFile', '', $file->title);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => true));
}
}
/**
* 移动文档库
* Move Lib.
*
* @param int $libID
* @param string $targetSpace [int]
* @access public
* @return void
*/
public function moveLib(int $libID, string $targetSpace = '')
{
$lib = $this->doc->getLibByID($libID);
if(empty($targetSpace)) $targetSpace = $this->doc->getLibTargetSpace($lib);
$spaceType = $this->doc->getParamFromTargetSpace($targetSpace, 'type');
if(!empty($lib->main)) return $this->send(array('result' => 'fail', 'message' => $this->lang->doc->errorEditSystemDoc));
if(!empty($_POST))
{
$data = form::data()
->setIF($this->post->acl == 'open', 'groups', '')
->setIF($this->post->acl == 'open', 'users', '')
->get();
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->doc->moveLib($libID, $data);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$spaceTypeChanged = $spaceType != $lib->type;
return $this->docZen->responseAfterMove($this->post->space, $libID, 0, $spaceTypeChanged);
}
$this->docZen->setAclForCreateLib($spaceType);
$this->view->title = $this->lang->doc->moveLibAction;
$this->view->spaces = $this->doc->getAllSubSpaces('all');
$this->view->lib = $lib;
$this->view->targetSpace = $targetSpace;
$this->view->libType = $spaceType;
$this->view->hasOthersDoc = $this->doc->hasOthersDoc($lib);
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->loadModel('user')->getPairs('nocode|noclosed');
$this->display();
}
/**
* 移动文档
* Move Document.
*
* @param int $docID
* @param int $libID
* @param string $spaceType
* @param string $space
* @access public
* @return void
*/
public function moveDoc(int $docID, int $libID = 0, string $spaceType = '', string $space = '')
{
if($spaceType == 'quick')
{
$libID = 0;
$space = $spaceType = '';
}
$doc = $this->doc->getByID($docID);
if(empty($libID)) $libID = (int)$doc->lib;
$lib = $this->doc->getLibByID($libID);
if(empty($space))
{
if($lib->parent)
{
$space = $lib->parent;
}
else
{
if($lib->product) $space = $lib->product;
if($lib->project) $space = $lib->project;
}
}
if(empty($spaceType))
{
if($lib->parent)
{
$spaceType = $this->doc->getSpaceType($space);
}
else
{
if($lib->product && $lib->type == 'product') $spaceType = 'product';
if($lib->project && $lib->type == 'project') $spaceType = 'project';
}
}
if(!empty($_POST))
{
/* parent带‘m_’前缀为目录。*/
if(isset($_POST['parent']) && strpos($_POST['parent'], 'm_') !== false)
{
$_POST['module'] = str_replace('m_', '', $_POST['parent']);
$_POST['parent'] = 0;
}
$data = form::data()
->setIF($this->post->acl == 'open', 'groups', '')
->setIF($this->post->acl == 'open', 'users', '')
->setIF($this->post->acl == 'open', 'readGroups', '')
->setIF($this->post->acl == 'open', 'readUsers', '')
->setIF(in_array($spaceType, array('project', 'product')), $spaceType, $space)
->setIF($spaceType != 'project', 'project', 0)
->setIF($spaceType != 'product', 'product', 0)
->get();
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$changes = common::createChanges($doc, $data);
if($changes)
{
$this->doc->doUpdateDoc($docID, $data);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$actionID = $this->loadModel('action')->create('doc', $docID, 'Moved', '', json_encode(array('from' => $doc->lib, 'to' => $data->lib)));
$this->action->logHistory($actionID, $changes);
}
$spaceTypeChanged = $spaceType != $lib->type;
return $this->docZen->responseAfterMove($this->post->space, $data->lib, $docID, $spaceTypeChanged);
}
$projects = $this->loadModel('project')->getPairsByProgram(0, 'all', false, 'order_asc');
$products = $this->loadModel('product')->getPairs();
$executions = $this->loadModel('execution')->getPairs(0, 'all', 'multiple,leaf');
$libPairs = $this->doc->getLibPairs($spaceType, 'withObject', (int)$space, '', $products, $projects, $executions);
if($spaceType == 'project') $libPairs += $this->doc->getExecutionLibPairsByProject((int)$space, 'withObject', $executions);
if(!isset($libPairs[$libID])) $libID = (int)key($libPairs);
$chapterAndDocs = $this->doc->getDocsOfLibs(array($libID), $spaceType, $docID);
$modulePairs = empty($libID) ? array() : $this->loadModel('tree')->getOptionMenu($libID, 'doc', 0);
if(isset($doc) && !empty($doc->parent) && !isset($chapterAndDocs[$doc->parent])) $chapterAndDocs[$doc->parent] = $this->doc->fetchByID($doc->parent);
$chapterAndDocs = $this->doc->buildNestedDocs($chapterAndDocs, $modulePairs);
$this->view->docID = $docID;
$this->view->libID = $libID;
$this->view->spaceType = $spaceType;
$this->view->space = $space;
$this->view->doc = $doc;
$this->view->spaces = $this->doc->getAllSubSpaces($this->app->tab != 'doc' ? $this->app->tab : 'all');
$this->view->libPairs = $libPairs;
$this->view->optionMenu = $chapterAndDocs;
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->loadModel('user')->getPairs('nocode|noclosed');
$this->display();
}
/**
* 移动文档模板
* Move document template.
*
* @param int $docID
* @access public
* @return void
*/
public function moveTemplate(int $docID)
{
$doc = $this->doc->getByID($docID);
if(!empty($_POST))
{
$this->lang->doc->module = $this->lang->docTemplate->module;
$data = form::data()
->setIF(!isset($_POST['lib']), 'lib', $doc->lib)
->setIF(!isset($_POST['module']), 'module', $doc->module)
->setIF(!isset($_POST['parent']), 'parent', $doc->parent)
->setIF(!isset($_POST['acl']), 'acl', $doc->acl)
->get();
$changes = common::createChanges($doc, $data);
if($changes)
{
$basicInfoChanged = false;
foreach($changes as $change)
{
if(in_array($change['field'], array('module', 'lib', 'acl'))) $basicInfoChanged = true;
}
$this->doc->doUpdateDoc($docID, $data, $basicInfoChanged);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$actionID = $this->loadModel('action')->create('docTemplate', $docID, 'Moved', '', json_encode(array('from' => $doc->lib, 'to' => $data->lib)));
$this->action->logHistory($actionID, $changes);
}
$newDoc = $this->doc->getByID($docID);
if(!$this->doc->checkPrivDoc($newDoc))
{
$link = $this->createLink('doc', 'browseTemplate', "libID={$doc->lib}&type=all&docID=0&orderBy=id_desc&recPerPage=20&pageID=1&mode=list");
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'load' => $link, 'doc' => $newDoc));
}
$docAppAction = array('executeCommand', 'handleMovedDoc', array($docID, '1', $data->lib));
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'docApp' => $docAppAction));
}
if(!empty($doc->parent))
{
$parentDoc = $this->doc->fetchByID($doc->parent);
$chapterAndDocs = $this->doc->getDocsOfLibs(array($doc->lib), 'template', $docID, true);
if(!isset($chapterAndDocs[$doc->parent])) $chapterAndDocs[$doc->parent] = $parentDoc;
$parentPath = trim($parentDoc->path, ',') . ",";
$topTemplate = substr($parentPath, 0, strpos($parentPath, ',')) ?: $doc->parent;
foreach($chapterAndDocs as $key => $template)
{
if(strpos(",{$template->path},", ",{$topTemplate},") === false) unset($chapterAndDocs[$key]);
}
$this->view->chapterAndDocs = $this->doc->buildNestedDocs($chapterAndDocs);
}
$scopeList = $this->doc->getTemplateScopes();
$this->view->scopeItems = $this->doc->getScopeItems($scopeList);
$this->view->doc = $doc;
$this->view->docID = $docID;
$this->view->modules = $this->loadModel('tree')->getOptionMenu((int)$doc->lib, 'docTemplate', 0, 'all', 'nodeleted', 'all');
$this->display();
}
/**
* Batch move document.
*
* @param string $type
* @param string $encodeDocIdList
* @param int $spaceID
* @param int $libID
* @param int $moduleID
* @access public
* @return void
*/
public function batchMoveDoc(string $type, string $encodeDocIdList, int $spaceID, int $libID = 0, int $moduleID = 0)
{
$docIdList = json_decode(base64_decode($encodeDocIdList), true);
if(!$docIdList) $this->locate($this->createLink('doc', 'app', "type={$type}&spaceID={$spaceID}&libID={$libID}&moduleID={$moduleID}"));
$spaces = $this->doc->getAllSubSpaces();
if($type == 'quick')
{
list($type, $spaceID) = explode('.', key($spaces));
$spaceID = (int) $spaceID;
}
if($_POST)
{
$oldDocList = $this->doc->getDocsByIdList($docIdList);
$data = form::data()
->setIF($type == 'mine' || $this->post->acl == 'open', 'groups', '')
->setIF($type == 'mine' || $this->post->acl == 'open', 'users', '')
->setIF(in_array($type, array('project', 'product')), $type, $spaceID)
->get();
$this->doc->batchMoveDoc($data, $docIdList);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$this->docZen->recordBatchMoveActions($oldDocList, $data);
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'docApp' => array('load', null, null, null, array('noLoading' => true, 'picks' => 'doc'))));
}
$projects = $this->loadModel('project')->getPairsByProgram(0, 'all', false, 'order_asc');
$products = $this->loadModel('product')->getPairs();
$executions = $this->loadModel('execution')->getPairs(0, 'all', 'multiple,leaf');
$libPairs = $this->doc->getLibPairs($type, 'withObject', $spaceID, '', $products, $projects, $executions);
if($type == 'project') $libPairs += $this->doc->getExecutionLibPairsByProject($spaceID, 'withObject', $executions);
if(!isset($libPairs[$libID])) $libID = (int)key($libPairs);
$docList = $this->doc->getByIdList($docIdList);
$docAclList = array_column($docList, 'acl');
$this->view->title = $this->lang->doc->batchMove;
$this->view->encodeDocIdList = $encodeDocIdList;
$this->view->type = $type;
$this->view->spaceID = $spaceID;
$this->view->libID = $libID;
$this->view->moduleID = $moduleID;
$this->view->spaces = $spaces;
$this->view->libPairs = $libPairs;
$this->view->optionMenu = $this->loadModel('tree')->getOptionMenu($libID, 'doc', 0);
$this->view->groups = $this->loadModel('group')->getPairs();
$this->view->users = $this->loadModel('user')->getPairs('nocode|noclosed');
$this->view->hasOpenDoc = in_array('open', $docAclList);
$this->display();
}
/**
* Ajax: 检查对象权限。
* Ajax check object priv.
*
* @param string $objectType
* @param int $objectID
* @access public
* @return void
*/
public function ajaxCheckObjectPriv(string $objectType, int $objectID)
{
$accounts = $this->post->users;
$object = $this->doc->getObjectByID($objectType, $objectID);
if(empty($object)) return print('');
if($object->acl == 'open') return print('');
$this->loadModel('user');
$userViews = $this->dao->select('*')->from(TABLE_USERVIEW)->where('account')->in($accounts)->fetchAll('account');
$userPairs = $this->dao->select('account,realname')->from(TABLE_USER)->where('account')->in($accounts)->fetchPairs('account', 'realname');
$denyUsers = array();
foreach($accounts as $account)
{
if(strpos(",{$this->app->company->admins}", ",{$account},") !== false) continue;
$userView = zget($userViews, $account, '');
if(empty($userView)) $userView = $this->user->computeUserView($account, true);
if($objectType == 'product' && isset($userView->products) && strpos(",{$userView->products},", ",{$objectID},") === false) $denyUsers[$account] = zget($userPairs, $account);
if($objectType == 'project' && isset($userView->projects) && strpos(",{$userView->projects},", ",{$objectID},") === false) $denyUsers[$account] = zget($userPairs, $account);
if($objectType == 'execution' && isset($userView->sprints) && strpos(",{$userView->sprints},", ",{$objectID},") === false) $denyUsers[$account] = zget($userPairs, $account);
}
if(empty($denyUsers)) return print('');
if(isset($this->lang->doc->whitelistDeny[$objectType])) return printf($this->lang->doc->whitelistDeny[$objectType], implode('、', $denyUsers));
}
/**
* Ajax: 检查库权限。
* Ajax check lib priv.
*
* @param int $libID
* @access public
* @return void
*/
public function ajaxCheckLibPriv(int $libID)
{
$accounts = $this->post->users;
$lib = $this->doc->getLibByID($libID);
if(empty($lib)) return print('');
if($lib->acl == 'open') return print('');
if($lib->acl == 'default' && strpos("product,project,execution", $lib->type) !== false) return $this->ajaxCheckObjectPriv($lib->type, zget($lib, $lib->type, 0));
$authAccounts[$lib->addedBy] = $lib->addedBy;
if($lib->groups) $authAccounts += $this->loadModel('group')->getGroupAccounts(explode(',', $lib->groups));
if($lib->users)
{
foreach(array_filter(explode(',', $lib->users)) as $account) $authAccounts[$account] = $account;
}
$userPairs = $this->dao->select('account,realname')->from(TABLE_USER)->where('account')->in($accounts)->fetchPairs('account', 'realname');
$denyUsers = array();
foreach($accounts as $account)
{
if(strpos(",{$this->app->company->admins}", ",{$account},") !== false) continue;
if(!isset($authAccounts[$account])) $denyUsers[$account] = zget($userPairs, $account);
}
if(empty($denyUsers)) return print('');
if(isset($this->lang->doc->whitelistDeny['doc'])) return printf($this->lang->doc->whitelistDeny['doc'], implode('、', $denyUsers));
}
/**
* Doc app view.
* 文档应用视图。
*
* @param string $type
* @param int $spaceID
* @param int $libID
* @param int $moduleID
* @param mixed $docID
* @param string $mode
* @param string $orderBy
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @param string $filterType
* @param string $search
* @param string $param
* @access public
* @access public
* @return void
*/
public function app(string $type = 'mine', int $spaceID = 0, int $libID = 0, int $moduleID = 0, mixed $docID = 0, string $mode = '', string $orderBy = '', int $recTotal = 0, int $recPerPage = 20, int $pageID = 1, string $filterType = '', string $search = '', bool $noSpace = false)
{
$isNotDocTab = $this->app->tab != 'doc';
if(empty($mode))
{
$showList = $noSpace || !empty($spaceID);
$mode = ($isNotDocTab || $type == 'execution' || $showList) ? 'list' : 'home';
}
$this->app->loadLang('file');
/* For product drop menu. */
if(!$isNotDocTab && $type == 'execution')
{
$execution = $this->loadModel('execution')->getByID($spaceID);
$type = 'project';
$spaceID = $execution->project;
}
if($isNotDocTab && in_array($type, array('product', 'project', 'execution')))
{
if($type == 'product' && $spaceID == 0) $spaceID = (int)$this->cookie->preProductID;
$this->doc->setMenuByType($type, $spaceID, $libID);
$objectKey = $type . 'ID';
$this->view->$objectKey = $spaceID;
$this->view->$type = $this->doc->fetchByID($spaceID, $type);
}
if($type == 'mine') $menuType = 'my';
else $menuType = $type == 'custom' ? 'team' : $type;
if(isset($this->lang->doc->menu->{$menuType})) $this->lang->doc->menu->{$menuType}['alias'] .= ',' . $this->app->rawMethod;
$docVersion = 0;
$isApi = is_string($docID) && strpos($docID, 'api.') === 0;
if($isApi) $docID = str_replace('api.', '', $docID);
if(is_string($docID) && strpos($docID, '_') !== false) list($docID, $docVersion) = explode('_', $docID);
$this->view->type = $type;
$this->view->spaceID = $spaceID;
$this->view->libID = $libID;
$this->view->moduleID = $moduleID;
$this->view->docID = $isApi ? "api.$docID" : (int)$docID;
$this->view->docVersion = (int)$docVersion;
$this->view->mode = $mode;
$this->view->filterType = $filterType;
$this->view->search = $search;
$this->view->recTotal = $recTotal;
$this->view->recPerPage = $recPerPage;
$this->view->pageID = $pageID;
$this->view->orderBy = $orderBy;
$this->view->objectType = $type;
$this->view->noSpace = $noSpace;
$this->view->objectID = $spaceID;
$this->view->users = $this->dao->select('account,realname,avatar')->from(TABLE_USER)->where('deleted')->eq('0')->fetchAll('account');
$this->view->title = isset($this->lang->doc->spaceList[$type]) ? $this->lang->doc->spaceList[$type] : $this->lang->doc->common;
$this->display();
}
/**
* Doc quick access page.
* 快捷访问页面。
*
* @param string $type 'view'|'collect'|'createdby'|'editedby'
* @param int $docID
* @param string $orderBy
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function quick(string $type = 'view', int $docID = 0, string $orderBy = '', int $recPerPage = 20, int $pageID = 1)
{
if(empty($orderBy)) $orderBy = 'id_desc';
if(!isset($this->config->doc->quickMenu[$type])) $type = 'view';
$menu = $this->config->doc->quickMenu[$type];
$currentUser = $this->app->user->account;
$docs = $this->doc->getMineList($type, 'all', 0, $orderBy);
$order = 0;
foreach($docs as $doc)
{
$order++;
unset($doc->draft);
$doc->originLIb = $doc->lib;
$doc->lib = $menu['id'];
$doc->order = $order;
$doc->isCollector = strpos($doc->collector, ',' . $currentUser . ',') !== false;
}
$this->view->docs = $docs;
$this->view->menu = $menu;
$this->view->type = $type;
$this->view->docID = $docID;
$this->view->orderBy = $orderBy;
$this->view->recPerPage = $recPerPage;
$this->view->pageID = $pageID;
$this->view->users = $this->dao->select('account,realname,avatar')->from(TABLE_USER)->where('deleted')->eq('0')->fetchAll('account');
$this->view->title = $this->lang->doc->quick;
$this->display();
}
/**
* Ajax: Get doc space data.
* Ajax: 获取文档空间数据。
*
* @param string $type
* @param int $space
* @access public
* @return void
*/
public function ajaxGetSpaceData(string $type = 'custom', int $spaceID = 0, string $picks = '', int $libID = 0)
{
$this->doc->setMenuByType($type, (int)$spaceID, 0);
if($type === 'template')
{
$modules = $this->doc->getTemplateModules();
$moduleNames = array_column($modules, 'fullName', 'id');
$templateList = $this->doc->getDocTemplateList(0, 'all');
$templateList = $this->doc->filterDeletedDocs($templateList);
$templateList = $this->doc->filterPrivDocs($templateList, 'template');
foreach($templateList as $template)
{
$template->moduleName = zget($moduleNames, $template->module);
}
$data = array('spaceID' => $spaceID, 'docs' => array_values($templateList));
$data['spaces'][] = array('name' => $this->lang->doc->template, 'id' => $spaceID);
$data['modules'] = $modules;
$scopeList = $this->doc->getTemplateScopes();
foreach($scopeList as $scope) $data['libs'][] = array('id' => $scope->id, 'name' => $scope->name, 'space' => $spaceID);
}
else
{
$noPicks = empty($picks);
$picks = $noPicks ? '' : ",$picks,";
list($spaces, $spaceID) = $this->doc->getSpaces($type, $spaceID);
$data = array('spaceID' => (int)$spaceID);
$libs = $this->doc->getLibsOfSpace($type, $spaceID);
$libIds = array_keys($libs);
foreach($libs as $lib) $lib->order = (int)$lib->order;
if($noPicks || strpos($picks, ',space,') !== false) $data['spaces'] = $spaces;
if($noPicks || strpos($picks, ',lib,') !== false) $data['libs'] = array_values($libs);
if($noPicks || strpos($picks, ',module,') !== false) $data['modules'] = array_values($this->doc->getModulesOfLibs($libIds));
if($noPicks || strpos($picks, ',doc,') !== false) $data['docs'] = array_values($this->doc->getDocsOfLibs($libIds + array($spaceID), $type));
}
$this->send($data);
}
/**
* Ajax: Get doc data.
* Ajax: 获取文档数据。
*
* @param string|int $docID
* @param int $version
* @access public
* @return void
*/
public function ajaxGetDoc(string|int $docID, int $version = 0, $details = 'no')
{
if(is_string($docID) && strpos($docID, 'api.') === 0)
{
$apiID = (int)str_replace('api.', '', $docID);
echo $this->fetch('api', 'ajaxGetApi', "apiID=$apiID&version=$version");
return;
}
$docID = (int)$docID;
$doc = $this->doc->getByID($docID, $version);
$doc->lib = (int)$doc->lib;
$doc->module = (int)$doc->module;
$doc->privs = array('edit' => common::hasPriv('doc', 'edit', $doc) && $doc->acl == 'open');
$doc->editors = $this->doc->getEditors($docID);
$doc->draft = $doc->status == 'draft' ? $this->doc->getContent($docID, 0) : null;
$lib = $this->doc->getLibByID((int)$doc->lib);
$objectType = $lib->type;
if(empty($objectType))
{
if($lib->execution) $objectType = 'execution';
elseif($lib->project) $objectType = 'project';
elseif($lib->product) $objectType = 'product';
}
$this->doc->setDocPriv($doc, $objectType);
if($details == 'yes')
{
$objectID = $this->doc->getObjectIDByLib($lib, $objectType);
$object = in_array($objectType, array('product', 'project', 'execution')) ? $this->doc->getObjectByID($objectType, $objectID) : $this->doc->getLibByID((int)$objectID);
$doc->libInfo = $lib;
$doc->objectType = $objectType;
$doc->object = $object;
}
if(!empty($doc->rawContent))
{
$doc->html = $doc->content;
$doc->content = $doc->rawContent;
unset($doc->rawContent);
if(empty($doc->templateType) && $doc->contentType == 'doc' && preg_match('/ src="{([0-9]+)(\.(\w+))?}" /', $doc->html))
{
$doc->contentType = 'html';
$doc->content = preg_replace('/ src="{([0-9]+)(\.(\w+))?}" /', ' src="' . helper::createLink('file', 'read', "fileID=$1", "$3") . '" ', $doc->html);
}
}
if($docID) $this->doc->createAction($docID, 'view');
$this->send($doc);
}
/**
* Get files by type and objectID.
* 根据类型和对象ID获取文件列表。
*
* @param string $type
* @param int $objectID
* @param string $browseType
* @param string $orderBy
* @param int $recTotal
* @param int $recPerPage
* @param int $pageID
* @access public
* @return void
*/
public function ajaxGetFiles(string $type, int $objectID, string $browseType = '', string $orderBy = 'id_desc', int $recTotal = 0, int $recPerPage = 9999, int $pageID = 1)
{
if(common::hasPriv('doc', 'showFiles'))
{
/* Load pager. */
$rawMethod = $this->app->rawMethod;
$this->app->rawMethod = 'showFiles';
$this->app->loadClass('pager', true);
$pager = new pager($recTotal, $recPerPage, $pageID);
$this->app->rawMethod = $rawMethod;
$files = $this->doc->getLibFiles($type, $objectID, $browseType, 0, $orderBy, $pager);
$sourcePairs = $this->doc->getFileSourcePairs($files);
$files = $this->docZen->processFiles($files, array(), $sourcePairs, true);
echo json_encode(array_values($files)); // $this->send($files); not work.
}
else
{
echo '[]';
}
}
/**
* Get lib summaries by space type and space list.
*
* @param string $spaceType
* @param string $spaceList
* @access public
* @return void
*/
public function ajaxGetLibSummaries(string $spaceType, string $spaceList)
{
$libsMap = $this->doc->getLibsOfSpaces($spaceType, $spaceList, 0);
echo json_encode($libsMap);
}
/**
* Ajax modal for setting doc basic info.
* 设置文档基本信息的模态框。
*
* @param string $objectType
* @param int $objectID
* @param int $libID
* @param int $moduleID
* @param int $docID
* @param string $isDraft
* @access public
*/
public function setDocBasic(string $objectType, int $objectID, int $libID = 0, int $moduleID = 0, int $parentID = 0, int $docID = 0, string $isDraft = 'no', string $modalType = 'doc')
{
$this->doc->setMenuByType($objectType, (int)$objectID, (int)$libID);
$lib = $libID ? $this->doc->getLibByID($libID) : '';
$isCreate = empty($docID);
if($docID) $doc = $this->doc->getByID($docID);
$title = $this->lang->settings;
if($modalType == 'doc')
{
$title = $parentID ? $this->lang->doc->addSubDoc : $this->lang->doc->create;
if($objectType == 'template') $title = $parentID ? $this->lang->docTemplate->addSubDocTemplate : $this->lang->docTemplate->create;
if($isDraft == 'no') $title = $this->lang->settings;
}
if($modalType == 'chapter') $title = $isCreate ? $this->lang->doc->addChapter : $this->lang->doc->editChapter;
if($isCreate)
{
if(!$moduleID && $docID)
{
$parentDoc = $this->doc->getByID($parentID);
$moduleID = $parentDoc->module;
}
if(empty($objectID) && $lib) $objectID = $this->doc->getObjectIDByLib($lib);
if($lib && $objectType != 'template') $objectType = $lib->type;
$unclosed = strpos($this->config->doc->custom->showLibs, 'unclosed') !== false ? 'unclosedProject' : '';
$libPairs = $this->doc->getLibs($objectType, "{$unclosed}", $libID, $objectID);
$moduleID = $moduleID ? (int)$moduleID : (int)$this->cookie->lastDocModule;
if(!$libID && !empty($libPairs)) $libID = key($libPairs);
if(empty($lib) && $libID) $lib = $this->doc->getLibByID($libID);
if($objectType == 'custom' || $objectType == 'mine') $this->view->spaces = $this->doc->getSubSpacesByType($objectType, true);
$this->docZen->setObjectsForCreate($objectType, empty($lib) ? null : $lib, $unclosed, $objectID);
$this->view->optionMenu = empty($libID) ? array() : $this->loadModel('tree')->getOptionMenu($libID, 'doc', 0);
}
else
{
$parentID = (int)$doc->parent;
$moduleID = (int)$doc->module;
$libID = (int)$doc->lib;
$lib = $this->doc->getLibByID($libID);
if($lib && $objectType != 'template') $objectType = $lib->type;
$objectID = $this->doc->getObjectIDByLib($lib);
$libPairs = $this->doc->getLibs($objectType, '', $libID, $objectID);
if($objectType == 'custom' || $objectType == 'mine') $this->view->spaces = $this->doc->getSubSpacesByType($objectType, true);
$this->docZen->setObjectsForEdit($objectType, $objectID);
$this->view->doc = $doc;
$this->view->optionMenu = $this->loadModel('tree')->getOptionMenu($libID, 'doc', 0);
}
$chapterAndDocs = $this->doc->getDocsOfLibs(array($libID), $objectType, $docID, $objectType == 'template' ? true : false);
if($objectType == 'template' && !empty($moduleID)) $chapterAndDocs = array_filter($chapterAndDocs, fn($docInfo) => $docInfo->module == $moduleID);
if(isset($doc) && !empty($doc->parent) && !isset($chapterAndDocs[$doc->parent])) $chapterAndDocs[$doc->parent] = $this->doc->fetchByID($doc->parent);
$modulePairs = empty($libID) || $modalType == 'chapter' || $objectType == 'template' ? array() : $this->loadModel('tree')->getOptionMenu($libID, 'doc', 0);
if($objectType == 'template' && !empty($parentID))
{
$parentDoc = $this->doc->fetchByID($parentID);
$parentPath = trim($parentDoc->path, ',') . ",";
$topTemplate = substr($parentPath, 0, strpos($parentPath, ',')) ?: $parentID;
foreach($chapterAndDocs as $key => $template)
{
if(strpos(",{$template->path},", ",{$topTemplate},") === false) unset($chapterAndDocs[$key]);
}
}
$this->view->chapterAndDocs = $this->doc->buildNestedDocs($chapterAndDocs, $modulePairs);
if($objectType == 'template')
{
$scopeList = $this->doc->getTemplateScopes();
$this->view->scopeItems = $this->doc->getScopeItems($scopeList);
}
else
{
$this->view->users = $this->user->getPairs('nocode|noclosed|nodeleted');
$this->view->groups = $this->loadModel('group')->getPairs();
}
$docTitle = '';
if(!empty($doc))
{
$docTitle = $doc->title;
}
elseif($this->post->title)
{
$docTitle = $this->post->title;
}
$this->view->docID = $docID;
$this->view->docTitle = $docTitle;
$this->view->mode = empty($docID) ? 'create' : 'edit';
$this->view->libID = $libID;
$this->view->moduleID = $moduleID;
$this->view->objectID = $objectID;
$this->view->objectType = $objectType;
$this->view->lib = $lib;
$this->view->libs = $libPairs;
$this->view->parentID = $parentID;
$this->view->isDraft = $isDraft == 'yes';
$this->view->title = $title;
$this->view->modalType = $modalType;
$this->view->isCreate = $isCreate;
$this->display();
}
/**
* AJAX: 通过文档ID获取Confluence子文档。
* AJAX: Get confluence subdocuments by doc ID.
*
* @param int $originPageID 当前Confluence文档ID
* @param string $title 选择的父页面标题
* @param int $level 展示层级
* @access public
* @return void
*/
public function ajaxGetConfluenceChildren(int $originPageID, string $title, int $level = PHP_INT_MAX)
{
$parentID = $this->doc->getDocIdByTitle($originPageID, $title);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
$docs = $this->docZen->getDocChildrenByRecursion((int)$parentID, $level);
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
return $this->send(array('result' => 'success', 'data' => $docs));
}
/**
* Ajax: Retrieve corresponding user information in the Zen path through Confluence user ID.
* Ajax: 通过Confluence用户ID获取在禅道中的对应用户信息。
*
* @param string $username
* @access public
* @return void
*/
public function ajaxGetConfluenceUser(string $username)
{
$user = $this->doc->getUserByConfluenceUserID($username);
return $this->send(array('result' => 'success', 'data'=> $user));
}
/**
* Manage scope.
* 维护范围。
*
* @access public
* @return void
*/
public function manageScope()
{
$scopeList = $this->doc->getTemplateScopes();
if(!empty($_POST))
{
$scopes = $this->post->scopes;
$oldScopes = $newScopes = array();
foreach($scopes as $index => $name)
{
if(empty(trim($name))) continue;
$scopeID = $_POST['id'][$index];
if($scopeID)
{
$oldScopes[$scopeID] = $name;
}
else
{
$newScopes[$index] = $name;
}
}
$deletedScopes = array_diff(array_keys($scopeList), array_keys($oldScopes));
if(!empty($deletedScopes)) $this->doc->deleteTemplateScopes($deletedScopes);
if(dao::isError()) return $this->sendError(array('message' => dao::getError()));
if(!empty($oldScopes)) $this->doc->updateTemplateScopes($oldScopes);
if(dao::isError()) return $this->sendError(array('message' => dao::getError()));
if(!empty($newScopes)) $this->doc->insertTemplateScopes($newScopes);
if(dao::isError()) return $this->sendError(array('message' => dao::getError()));
return $this->sendSuccess(array('closeModal' => true, 'load' => true));
}
$this->view->scopeList = $scopeList;
$this->display();
}
/**
* AJAX: Judge can be deleted scope.
* AJAX: 判断范围是否可以被删除。
*
* @param int $scopeID
* @access public
* @return array
*/
public function ajaxJudgeCanBeDeleted(int $scopeID = 0)
{
if(empty($scopeID)) return $this->sendSuccess(array('message' => 'success'));
$templates = $this->doc->getScopeTemplates(array($scopeID));
if(!empty($templates[$scopeID])) return $this->sendError($this->lang->docTemplate->scopeHasTemplateTips);
$modules = $this->doc->getTemplateModules($scopeID);
if(!empty($modules)) return $this->sendError($this->lang->docTemplate->scopeHasModuleTips);
return $this->sendSuccess(array('message' => 'success'));
}
}