* @package common * @version $Id$ * @link http://www.zentao.net */ class commonModel extends model { public static $requestErrors = array(); /** * Set config of user. * * @access public * @return void */ public function setUserConfig() { $this->sendHeader(); $this->setCompany(); $this->setUser(); $this->setApproval(); $this->loadConfigFromDB(); $this->loadCustomFromDB(); if(!$this->checkIP()) return print($this->lang->ipLimited); } /** * Set the status of execution, project, and program to doing. * * @param int $objectID * @access public * @return void */ public function syncPPEStatus(int $objectID) { global $app; $rawModule = $app->rawModule; if($rawModule == 'task' or $rawModule == 'effort') { $taskID = $objectID; $execution = $this->syncExecutionStatus($taskID); $project = $this->syncProjectStatus($execution); $this->syncProgramStatus($project); } if($rawModule == 'execution') { $executionID = $objectID; $execution = $this->dao->select('id, project, grade, parent, status, deleted')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch(); $this->syncExecutionByChild($execution); $project = $this->syncProjectStatus($execution); $this->syncProgramStatus($project); } if($rawModule == 'project') { $projectID = $objectID; $project = $this->dao->select('id, parent, path')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch(); $this->syncProgramStatus($project); } if($rawModule == 'program' and $this->config->systemMode == 'ALM') { $programID = $objectID; $program = $this->dao->select('id, parent, path')->from(TABLE_PROGRAM)->where('id')->eq($programID)->fetch(); $this->syncProgramStatus($program); } } /** * Set the status of the program to which theproject is linked as Ongoing. * * @param object $project * @access public * @return void */ public function syncProgramStatus(object $project) { if($project->parent == 0) return; $parentPath = str_replace(",{$project->id},", '', $project->path); $parentPath = explode(',', trim($parentPath, ',')); $waitList = $this->dao->select('id')->from(TABLE_PROGRAM) ->where('id')->in($parentPath) ->andWhere('status')->eq('wait') ->orderBy('id_desc') ->fetchPairs(); $this->dao->update(TABLE_PROGRAM)->set('status')->eq('doing')->set('realBegan')->eq(helper::now())->where('id')->in($waitList)->exec(); $this->loadModel('action'); foreach($waitList as $programID) { $this->action->create('program', $programID, 'syncprogram'); } } /** * Set the status of the project to which the execution is linked as Ongoing. * * @param object $execution * @access public * @return object $project */ public function syncProjectStatus(object $execution): object { $projectID = $execution->project; $project = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($projectID)->fetch(); $today = helper::today(); if($project->status == 'wait') { $this->dao->update(TABLE_PROJECT) ->set('status')->eq('doing') ->beginIf(helper::isZeroDate($project->realBegan))->set('realBegan')->eq($today)->fi() ->where('id')->eq($projectID) ->exec(); $this->loadModel('project')->recordFirstEnd($projectID); $actionType = $project->multiple ? 'syncproject' : 'syncmultipleproject'; $this->loadModel('action')->create('project', $projectID, $actionType); } return $project; } /** * Set the status of the execution to which the sub execution is linked as Ongoing. * * @param object $execution * @access public * @return object|false $parentExecution */ public function syncExecutionByChild(object $execution): object|false { if($execution->grade == 1) return false; $today = helper::today(); $parentExecution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($execution->parent)->fetch(); if($execution->deleted == '0' and $execution->status == 'doing' and in_array($parentExecution->status, array('wait', 'closed'))) { $this->dao->update(TABLE_EXECUTION) ->set('status')->eq('doing') ->beginIf(helper::isZeroDate($parentExecution->realBegan))->set('realBegan')->eq($today)->fi() ->where('id')->eq($execution->parent) ->exec(); $this->loadModel('action')->create('execution', $execution->parent, 'syncexecutionbychild'); } $project = $this->loadModel('project')->getByID($execution->project); if($project->model == 'waterfall' or $project->model == 'waterfallplus') $this->loadModel('programplan')->computeProgress($execution->id); return $parentExecution; } /** * Set the status of the execution to which the task is linked as Ongoing. * * @param int $taskID * @access public * @return object $execution */ public function syncExecutionStatus(int $taskID): object { $execution = $this->dao->select('t1.*')->from(TABLE_EXECUTION)->alias('t1') ->leftJoin(TABLE_TASK)->alias('t2')->on('t1.id=t2.execution') ->where('t2.id')->eq($taskID) ->fetch(); $today = helper::today(); if($execution->status == 'wait') { $this->dao->update(TABLE_EXECUTION)->set('status')->eq('doing')->set('realBegan')->eq($today)->where('id')->eq($execution->id)->exec(); $this->loadModel('project')->recordFirstEnd($execution->id); $this->loadModel('action')->create('execution', $execution->id, 'syncexecution'); if($execution->parent) { $execution = $this->dao->select('*')->from(TABLE_EXECUTION)->where('id')->eq($execution->id)->fetch(); // Get updated execution. $this->syncExecutionByChild($execution); } } return $execution; } /** * Set the header info. * * @access public * @return void */ public function sendHeader() { helper::header('Content-Type', "text/html; Language={$this->config->charset}"); helper::header('Cache-Control', 'private'); /* Send HTTP header. */ if($this->config->framework->sendXCTO) helper::header('X-Content-Type-Options', 'nosniff'); if($this->config->framework->sendXXP) helper::header('X-XSS-Protection', '1; mode=block'); if($this->config->framework->sendHSTS) helper::header('Strict-Transport-Security', 'max-age=3600; includeSubDomains'); if($this->config->framework->sendRP) helper::header('Referrer-Policy', 'no-referrer-when-downgrade'); if($this->config->framework->sendXPCDP) helper::header('X-Permitted-Cross-Domain-Policies', 'master-only'); if($this->config->framework->sendXDO) helper::header('X-Download-Options', 'noopen'); /* Set Content-Security-Policy header. */ if($this->config->CSPs) { foreach($this->config->CSPs as $CSP) helper::header('Content-Security-Policy', "$CSP;"); } if($this->loadModel('setting')->getItem('owner=system&module=sso&key=turnon')) { if(isset($_SERVER["HTTPS"]) and $_SERVER["HTTPS"] == 'on') { $session = $this->config->sessionVar . '=' . session_id(); helper::header('Set-Cookie', "$session; SameSite=None; Secure=true", false); } } else { if(!empty($this->config->xFrameOptions)) helper::header('X-Frame-Options', $this->config->xFrameOptions); } } /** * Set the company. * * First, search company by the http host. If not found, search by the default domain. Last, use the first as the default. * After get the company, save it to session. * @access public * @return void */ public function setCompany() { if($this->session->company) { $this->app->company = $this->session->company; } else { $httpHost = $this->server->http_host; $company = $this->loadModel('company')->getFirst(); if(!$company) $this->app->triggerError(sprintf($this->lang->error->companyNotFound, $httpHost), __FILE__, __LINE__, true); $this->session->set('company', $company); $this->app->company = $company; } } /** * Set the user info. * * @access public * @return void */ public function setUser() { if($this->session->user) { if(!$this->app->upgrading) $this->session->user->view = $this->loadModel('user')->grantUserView(); $this->app->user = $this->session->user; } elseif($this->app->company->guest || (PHP_SAPI == 'cli' && (!isset($_SERVER['RR_MODE']) || $_SERVER['RR_MODE'] == 'jobs'))) { $user = new stdClass(); $user->id = 0; $user->account = 'guest'; $user->realname = 'guest'; $user->dept = 0; $user->avatar = ''; $user->role = 'guest'; $user->admin = false; $user->rights = $this->loadModel('user')->authorize('guest'); $user->groups = array('group'); $user->visions = $this->config->vision; if(!$this->app->upgrading) $user->view = $this->user->grantUserView($user->account, $user->rights['acls']); $this->session->set('user', $user); $this->app->user = $this->session->user; } } /** * Set approval config. * * @access public * @return void */ public function setApproval() { $this->config->openedApproval = (in_array($this->config->edition, array('max', 'ipd'))) && ($this->config->vision == 'rnd'); } /** * 获取表单的配置项。 * Obtain the config for the form. * * @param string $module * @param string $method * @static * @access public * @return array */ public static function formConfig(string $module, string $method): array { global $config, $app; if($config->edition == 'open') return array(); $required = $app->dbQuery("SELECT * FROM " . TABLE_WORKFLOWRULE . " WHERE `type` = 'system' and `rule` = 'notempty'")->fetch(); $fields = $app->control->loadModel('flow')->getExtendFields($module, $method); $formConfig = array(); $type = 'string'; foreach($fields as $fieldObject) { if(strpos($fieldObject->type, 'int') !== false) $type = 'int'; if(strpos($fieldObject->type, 'date') !== false) $type = 'date'; if(in_array($fieldObject->type, array('float', 'decimal'))) $type = 'float'; $formConfig[$fieldObject->field] = array('type' => $type, 'default' => $fieldObject->default, 'control' => $fieldObject->control, 'rules' => $fieldObject->rules); $formConfig[$fieldObject->field]['required'] = strpos(",{$fieldObject->rules},", ",{$required->id},") !== false; if(in_array($fieldObject->control, array('multi-select', 'checkbox'))) $formConfig[$fieldObject->field]['filter'] = 'join'; } return $formConfig; } /** * Load configs from database and save it to config->system and config->personal. * * @access public * @return void */ public function loadConfigFromDB() { /* Get configs of system and current user. */ $account = isset($this->app->user->account) ? $this->app->user->account : ''; if($this->config->db->name) $config = $this->loadModel('setting')->getSysAndPersonalConfig($account); $this->config->system = isset($config['system']) ? $config['system'] : array(); $this->config->personal = isset($config[$account]) ? $config[$account] : array(); /* Override the items defined in config/config.php and config/my.php. */ if(isset($this->config->system->common)) $this->app->mergeConfig($this->config->system->common, 'common'); if(isset($this->config->personal->common)) $this->app->mergeConfig($this->config->personal->common, 'common'); $this->config->disabledFeatures = $this->config->disabledFeatures . ',' . $this->config->closedFeatures; } /** * Load custom lang from db. * * @access public * @return void */ public function loadCustomFromDB() { $this->loadModel('custom'); if($this->app->upgrading) return; if(!$this->config->db->name) return; $records = $this->custom->getAllLang(); if(!$records) return; $this->lang->db = new stdclass(); $this->lang->db->custom = $records; } /** * Judge a method of one module is open or not. * * @param string $module * @param string $method * @access public * @return bool */ public function isOpenMethod($module, $method): bool { if(in_array("$module.$method", $this->config->openMethods)) return true; if($module == 'block' and $method == 'main' and isset($_GET['hash'])) return true; if($this->loadModel('user')->isLogon() or ($this->app->company->guest and $this->app->user->account == 'guest')) { if(stripos($method, 'ajax') !== false) return true; if($module == 'block') return true; if($module == 'index' and $method == 'app') return true; if($module == 'my' and $method == 'guidechangetheme') return true; if($module == 'product' and $method == 'showerrornone') return true; if($module == 'misc' and in_array($method, array('downloadclient', 'changelog'))) return true; if($module == 'tutorial' and in_array($method, array('start', 'index', 'quit', 'wizard'))) return true; } return false; } /** * Deny access. * * @param string $module * @param string $method * @param bool $reload * @access public * @return mixed */ public function deny(string $module, string $method, bool $reload = true) { if($reload) { /* Get authorize again. */ $user = $this->app->user; $user->rights = $this->loadModel('user')->authorize($user->account); $user->groups = $this->user->getGroups($user->account); $user->admin = strpos($this->app->company->admins, ",{$user->account},") !== false; $this->session->set('user', $user); $this->app->user = $this->session->user; if(commonModel::hasPriv($module, $method)) return true; } $vars = "module=$module&method=$method"; if(isset($this->server->http_referer)) { $referer = helper::safe64Encode($this->server->http_referer); $vars .= "&referer=$referer"; } $denyLink = helper::createLink('user', 'deny', $vars); echo json_encode(array('load' => $denyLink)); helper::end(); } /** * Print the run info. * * @param mixed $startTime the start time. * @access public * @return array the run info array. */ public function printRunInfo($startTime) { $info['timeUsed'] = round(getTime() - $startTime, 4) * 1000; $info['memory'] = round(memory_get_peak_usage() / 1024, 1); $info['querys'] = count(dao::$querys); vprintf($this->lang->runInfo, $info); return $info; } /** * Create menu item link * * @param object $menuItem * * @static * @access public * @return string */ public static function createMenuLink($menuItem) { $link = $menuItem->link; if(is_array($menuItem->link)) { $vars = isset($menuItem->link['vars']) ? $menuItem->link['vars'] : ''; if(isset($menuItem->tutorial) and $menuItem->tutorial) { if(!empty($vars)) $vars = helper::safe64Encode($vars); $link = helper::createLink('tutorial', 'wizard', "module={$menuItem->link['module']}&method={$menuItem->link['method']}¶ms=$vars"); } else { $link = helper::createLink($menuItem->link['module'], $menuItem->link['method'], $vars); } } return $link; } /** * Create sub menu by settings in lang files. * * @param object $items * @param mixed $replace * @static * @access public * @return array */ public static function createDropMenu($items, $replace) { $dropMenu = array(); foreach($items as $dropMenuKey => $dropMenuLink) { if(is_array($dropMenuLink) and isset($dropMenuLink['link'])) $dropMenuLink = $dropMenuLink['link']; if(is_array($replace)) { $dropMenuLink = vsprintf($dropMenuLink, $replace); } else { $dropMenuLink = sprintf($dropMenuLink, $replace); } list($dropMenuName, $dropMenuModule, $dropMenuMethod, $dropMenuParams) = explode('|', $dropMenuLink); $link = array(); $link['module'] = $dropMenuModule; $link['method'] = $dropMenuMethod; $link['vars'] = $dropMenuParams; $dropMenuItem = isset($items->$dropMenuKey) ? $items->$dropMenuKey : array(); $menu = new stdclass(); $menu->name = $dropMenuKey; $menu->link = $link; $menu->text = $dropMenuName; $menu->subModule = isset($dropMenuItem['subModule']) ? $dropMenuItem['subModule'] : ''; $menu->alias = isset($dropMenuItem['alias']) ? $dropMenuItem['alias'] : ''; $menu->hidden = false; $dropMenu[$dropMenuKey] = $menu; } return $dropMenu; } /** * Print admin dropMenu. * * @param string $dropMenu * @static * @access public * @return void */ public static function printAdminDropMenu($dropMenu) { global $app, $lang; $currentModule = $app->getModuleName(); $currentMethod = $app->getMethodName(); if(isset($lang->admin->dropMenuOrder->$dropMenu)) { ksort($lang->admin->dropMenuOrder->$dropMenu); foreach($lang->admin->dropMenuOrder->$dropMenu as $type) { if(isset($lang->admin->dropMenu->$dropMenu->$type)) { $subModule = ''; $alias = ''; $link = $lang->admin->dropMenu->$dropMenu->$type; if(is_array($lang->admin->dropMenu->$dropMenu->$type)) { $dropMenuType = $lang->admin->dropMenu->$dropMenu->$type; if(isset($dropMenuType['subModule'])) $subModule = $dropMenuType['subModule']; if(isset($dropMenuType['alias'])) $alias = $dropMenuType['alias']; if(isset($dropMenuType['link'])) $link = $dropMenuType['link']; } list($text, $moduleName, $methodName)= explode('|', $link); if(!common::hasPriv($moduleName, $methodName)) continue; $active = ($currentModule == $moduleName and $currentMethod == $methodName) ? 'btn-active-text' : ''; if($subModule and strpos(",{$subModule}," , ",{$currentModule},") !== false) $active = 'btn-active-text'; if($alias and $currentModule == $moduleName and strpos(",$alias,", ",$currentMethod,") !== false) $active = 'btn-active-text'; echo html::a(helper::createLink($moduleName, $methodName), "$text", '', "class='btn btn-link {$active}' id='{$type}Tab'"); } } } } /** * Print the main nav. * * @param string $moduleName * * @static * @access public * @return void */ public static function printMainNav($moduleName) { $items = common::getMainNavList($moduleName); foreach($items as $item) { if($item == 'divider') { echo "
"; } else { $active = $item->active ? ' class="active"' : ''; echo "