requestType != 'GET') { $pathInfo = $app->getPathInfo(); if(!empty($pathInfo)) { $dotPos = strrpos((string) $pathInfo, '.'); if($dotPos) { $viewType = substr((string) $pathInfo, $dotPos + 1); } else { $config->default->view = $config->default->view == 'mhtml' ? 'html' : $config->default->view; } } } elseif($config->requestType == 'GET') { if(isset($_GET[$config->viewVar])) { $viewType = $_GET[$config->viewVar]; } else { /* Set default view when url has not module name. such as only domain. */ $config->default->view = ($config->default->view == 'mhtml' and isset($_GET[$config->moduleVar])) ? 'html' : $config->default->view; } } if($source and isset($viewType)) return $viewType; if(isset($viewType) and !str_contains((string) $config->views, ',' . $viewType . ',')) $viewType = $config->default->view; return $viewType ?? $config->default->view; } /** * Encode json for $.parseJSON * * @param array $data * @param int $options * @static * @access public * @return string */ public static function jsonEncode4Parse(array $data, int $options = 0) { $json = json_encode($data); if($options) $json = str_replace(array("'", '"'), array('\u0027', '\u0022'), $json); $escapers = array("\\", "/", "\"", "'", "\n", "\r", "\t", "\x08", "\x0c", "\\\\u"); $replacements = array("\\\\", "\\/", "\\\"", "\'", "\\n", "\\r", "\\t", "\\f", "\\b", "\\u"); return str_replace($escapers, $replacements, $json); } /** * Verify that the system has opened on the feature. * * @param string $feature scrum_risk | risk | scrum * @static * @access public * @return bool */ public static function hasFeature(string $feature): bool { global $config; if(in_array($feature, array('waterfall', 'waterfallplus'))) { return !str_contains(",$config->disabledFeatures,", ",{$feature},"); // 轻量级模式排除瀑布、融合瀑布模型 } if(str_contains($feature, '_')) { $code = explode('_', $feature); $code = $code[0] . ucfirst($code[1]); return !str_contains(",$config->disabledFeatures,", ",{$code},"); } $hasFeature = false; $canConfigFeature = false; foreach($config->featureGroup as $group => $modules) { foreach($modules as $module) { if($feature != $group && $feature != $module) continue; $canConfigFeature = true; $hasFeature |= helper::hasFeature("{$group}_{$module}"); } } return !$canConfigFeature || ($hasFeature && !str_contains(",$config->disabledFeatures,", ",{$feature},")); } /** * Convert encoding. * * @param string $string * @param string $fromEncoding * @param string $toEncoding * @static * @access public * @return string */ public static function convertEncoding(string $string, string $fromEncoding, string $toEncoding = 'utf-8'): string { $toEncoding = str_replace('utf8', 'utf-8', $toEncoding); if(function_exists('mb_convert_encoding')) { /* Remove like utf-8//TRANSLIT. */ $position = strpos($toEncoding, '//'); if($position !== false) $toEncoding = substr($toEncoding, 0, $position); /* Check string encoding. */ $encodings = array_merge(array('GB2312', 'GBK', 'BIG5'), mb_list_encodings()); $encoding = strtolower(mb_detect_encoding($string, $encodings)); if($encoding == $toEncoding) return $string; return mb_convert_encoding($string, $toEncoding, $encoding); } elseif(function_exists('iconv')) { if($fromEncoding == $toEncoding) return $string; $errorlevel = error_reporting(); error_reporting(0); $convertString = iconv($fromEncoding, $toEncoding, $string); error_reporting($errorlevel); /* iconv error then return original. */ if(!$convertString) return $string; return $convertString; } return $string; } /** * Calculate two working days. * * @param string $begin * @param string $end */ public static function workDays(string $begin, string $end): bool|float { $begin = strtotime($begin); $end = strtotime($end); if($end < $begin) return false; $double = floor(($end - $begin) / (7 * 24 * 3600)); $begin = date('w', $begin); $end = date('w', $end); $end = $begin > $end ? $end + 5 : $end; return $double * 5 + $end - $begin; } /** * Unify string to standard chars. * * @param string $string * @param string $to * @static * @access public * @return string */ public static function unify(string $string, string $to = ',') { $labels = array('_', '、', ' ', '-', '?', '@', '&', '%', '~', '`', '+', '*', '/', '\\', ',', '。'); $string = str_replace($labels, $to, $string); return preg_replace("/[{$to}]+/", $to, trim($string, $to)); } /** * Format version to semver formate. * * @param string $version * @static * @access public * @return string */ public static function formatVersion(string $version) { return preg_replace_callback( '/([0-9]+)((?:\.[0-9]+)?)((?:\.[0-9]+)?)(?:[\s\-\+]?)((?:[a-z]+)?)((?:\.?[0-9]+)?)/i', function($matches) { $major = $matches[1]; $minor = $matches[2]; $patch = $matches[3]; $preRelease = $matches[4]; $build = $matches[5]; $versionStrs = array($major, $minor ?: ".0", $patch ?: ".0"); if($preRelease ?: $build) array_push($versionStrs, "-"); if($preRelease) array_push($versionStrs, $preRelease); if($build) { if(!$preRelease) array_push($versionStrs, "build"); if(mb_substr($build, 0, 1) !== ".") array_push($versionStrs, "."); array_push($versionStrs, $build); } return implode('', $versionStrs); }, $version ); } /** * Process traffic. * * @param float $traffic * @param int $precision * @access public * @return string */ public static function formatKB($traffic, $precision = 2) { if(!$traffic) return 0; $base = log((float)$traffic, 1024); $suffixes = array('B', 'KB', 'MB', 'GB', 'TB'); return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; } /** * Trim version to xuanxuan version format. * * @param string $version * @access public * @return string */ public function trimVersion(string $version) { return preg_replace_callback( '/([0-9]+)((?:\.[0-9]+)?)((?:\.[0-9]+)?)(?:[\s\-\+]?)((?:[a-z]+)?)((?:\.?[0-9]+)?)/i', function($matches) { $major = $matches[1]; $minor = $matches[2]; $patch = $matches[3]; $preRelease = $matches[4]; $build = $matches[5]; $versionStrs = array($major, $minor ?: ".0"); if($patch && $patch !== ".0" && $patch !== "0") array_push($versionStrs, $patch); if($preRelease ?: $build) array_push($versionStrs, " "); if($preRelease) array_push($versionStrs, $preRelease); if($build) { if(!$preRelease) array_push($versionStrs, "build"); array_push($versionStrs, mb_substr($build, 0, 1) === "." ? substr($build, 1) : $build); } return implode('', $versionStrs); }, $version ); } /** * Request API. * * @param string $url * @static * @access public * @return string */ public static function requestAPI(string $url) { global $config; $url .= (str_contains($url, '?') ? '&' : '?') . $config->sessionVar . '=' . session_id(); if(isset($_SESSION['user'])) $url .= '&account=' . $_SESSION['user']->account; $response = common::http($url); $jsonDecode = json_decode((string) $response); if(empty($jsonDecode)) return $response; return $jsonDecode; } /** * Get date interval. * * @param string $format %Y-%m-%d %H:%i:%s * @static * @access public */ public static function getDateInterval(string|int $begin, string|int $end = '', string $format = ''): object|string { if(empty($end)) $end = time(); if(is_int($begin)) $begin = date('Y-m-d H:i:s', $begin); if(is_int($end)) $end = date('Y-m-d H:i:s', $end); $begin = date_create($begin); $end = date_create($end); $interval = date_diff($begin, $end); if($format) { $dateInterval = $interval->format($format); } else { $dateInterval = new stdClass(); $dateInterval->year = $interval->format('%y'); $dateInterval->month = $interval->format('%m'); $dateInterval->day = $interval->format('%d'); $dateInterval->hour = $interval->format('%H'); $dateInterval->minute = $interval->format('%i'); $dateInterval->secound = $interval->format('%s'); $dateInterval->year = $dateInterval->year == '00' ? 0 : ltrim($dateInterval->year, '0'); $dateInterval->month = $dateInterval->month == '00' ? 0 : ltrim($dateInterval->month, '0'); $dateInterval->day = $dateInterval->day == '00' ? 0 : ltrim($dateInterval->day, '0'); $dateInterval->hour = $dateInterval->hour == '00' ? 0 : ltrim($dateInterval->hour, '0'); $dateInterval->minute = $dateInterval->minute == '00' ? 0 : ltrim($dateInterval->minute, '0'); $dateInterval->secound = $dateInterval->secound == '00' ? 0 : ltrim($dateInterval->secound, '0'); } return $dateInterval; } /** * 是否是内网。 * Check is intranet. * * @return bool */ public static function isIntranet() { return !defined('USE_INTRANET') ? false : USE_INTRANET; } /** * 检查是否启用缓存。 * Check is enable cache. * * @return bool */ public static function isCacheEnabled() { if(isset($_GET['_nocache']) || isset($_SERVER['HTTP_X_ZT_REFRESH'])) return false; global $config; return $config->cache->enable; } /** * 检查是否启用APCu。 * Check if APCu is enabled. * * @access public * @return bool */ public static function isAPCuEnabled(): bool { return extension_loaded('apcu') && ini_get('apc.enabled') == '1'; } /** * 检查条件是否成立。 * * @param mixed $value1 * @param mixed $value2 * @param string $operator * @static * @access public * @return bool */ public static function checkCondition($value1, $value2, $operator): bool { $operatorList = array('=' => 'equal', '==' => 'equal', '!=' => 'notequal', '>' => 'gt', '>=' => 'ge', '<' => 'lt', '<=' => 'le'); if(!isset($operatorList[$operator]) && !in_array($operator, $operatorList)) return false; $operator = isset($operatorList[$operator]) ? zget($operatorList, $operator) : $operator; $checkFunc = 'check' . $operator; return validater::$checkFunc($value1, $value2); } /** * 替换 Emoji 为指定字符串。 * Replace Emoji to a specified string. * * @param string $subject * @param string $replace * @access public * @return string */ public static function replaceEmoji(string $subject, string $replace = '[Emoji]'): string { /* 匹配大部分常见 Emoji 范围(包括符号、旗帜、交通工具等)。 */ $pattern = '/[\x{1F300}-\x{1F5FF}\x{1F600}-\x{1F64F}\x{1F680}-\x{1F6FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{1F900}-\x{1F9FF}]/u'; return preg_replace($pattern, $replace, $subject); } } /** * 检查是否是onlybody模式。 * Check exist onlybody param. * * @access public * @return bool */ function isonlybody(): bool { return helper::inOnlyBodyMode(); } /** * 检查页面是否是弹窗中。 * Check page is modal. * * @access public * @return bool */ function isInModal(): bool { return helper::isAjaxRequest('modal'); } /** * Format time. * * @param string|null $time * @param string $format * @access public * @return string */ function formatTime(string|null $time, string $format = ''): string { if($time === null) return ''; $time = str_replace('0000-00-00', '', $time); $time = str_replace('00:00:00', '', $time); if(trim($time) == '') return ''; if($format) return date($format, strtotime($time)); return trim($time); } /** * Init page title based on the module name and the method name. * * @access public * @return string */ function initPageTitle(): string { global $app, $lang; $module = $app->rawModule; $method = $app->rawMethod; if(empty($lang->$module)) $app->loadLang($module); if(!empty($lang->$module->{$method . 'Action'})) return $lang->$module->{$method . 'Action'}; if(!empty($lang->$module->$method)) return $lang->$module->$method; return zget($lang, $method); } /** * Init page entity based on configuration of objectNameFields. * * @param object $object * @access public * @return array */ function initPageEntity(object $object): array { if(empty($object)) return array(); global $app, $config; $app->loadModuleConfig('action'); $module = $app->getModuleName(); $idField = isset($config->action->objectIdFields[$module]) ? $config->action->objectIdFields[$module] : 'id'; $titleField = isset($config->action->objectNameFields[$module]) ? $config->action->objectNameFields[$module] : 'title'; return array(zget($object, $titleField, ''), zget($object, $idField, 0)); } /** * Init table data of zin. * * @param array $items * @param array $fieldList * @param object $model * @access public * @return array */ function initTableData(array $items, array &$fieldList, ?object $model = null): array { if(!empty($_GET['orderBy']) && strpos($_GET['orderBy'], '_') !== false) list($orderField, $orderValue) = explode('_', $_GET['orderBy']); if(!empty($orderField) && !empty($orderValue) && !empty($fieldList[$orderField])) { $sortType = false; $col = $fieldList[$orderField]; if(empty($col['sortType']) && !empty($col['type']) && in_array($col['type'], array('id', 'title'))) $sortType = true; if(!empty($col['sortType'])) $sortType = $col['sortType']; if(is_bool($sortType)) $fieldList[$orderField]['sortType'] = $orderValue; } $items = setParent($items); if(empty($fieldList['actions'])) return $items; foreach($fieldList['actions']['menu'] as $actionMenu) { if(is_array($actionMenu)) { foreach($actionMenu as $actionMenuKey => $actionName) { if($actionMenuKey === 'other') { foreach($actionName as $otherActionName) initTableActions($fieldList, $otherActionName); } else { initTableActions($fieldList, $actionName); } } } else { initTableActions($fieldList, $actionMenu); } } global $app, $lang; if(empty($model)) { $module = $app->getModuleName(); $model = $app->control->loadModel($module); } $maxActionCount = 0; foreach($items as $item) { $item->actions = array(); $actionList = zget($fieldList['actions'], 'list', array()); foreach($fieldList['actions']['menu'] as $actionKey => $actionMenu) { if(isset($actionMenu['other'])) { $currentActionMenu = $actionMenu[0]; initItemActions($item, $currentActionMenu, $actionList, $model); $otherActionMenus = $actionMenu['other']; $otherAction = ''; foreach($otherActionMenus as $otherActionMenu) { $otherActions = explode('|', $otherActionMenu); foreach($otherActions as $otherActionName) { if(!checkOtherPriv(zget($actionList, $otherActionName, array()), $otherActionName, $item, $model)) continue; if(in_array($otherActionName, array_column($item->actions, 'name'))) continue; if(method_exists($model, 'isClickable') && !$model->isClickable($item, $otherActionName)) $otherAction .= '-'; $otherAction .= $otherActionName . ','; } } if($otherAction) $item->actions[] = 'other:' . $otherAction; } elseif($actionKey === 'more') { $moreAction = ''; foreach($actionMenu as $moreActionName) { if(!checkOtherPriv(zget($actionList, $moreActionName, array()), $moreActionName, $item, $model)) continue; if(method_exists($model, 'isClickable') && !$model->isClickable($item, $moreActionName)) $moreAction .= '-'; $moreAction .= $moreActionName . ','; } if($moreAction) $item->actions[] = 'more:' . $moreAction; } elseif(is_array($actionMenu)) // Two or more grups. { /* * Menu可能会有多套,如果只有一套可以直接用一维数组。 * There are maybe two or more groups of action menus. */ $item->actions = array(); $isClickable = false; foreach($actionMenu as $actionName) $isClickable |= initItemActions($item, $actionName, zget($fieldList['actions'], 'list', array()), $model); if($isClickable) break; // If the action is clickable, use this group. } else // Only one group of action menus. { initItemActions($item, $actionMenu, zget($fieldList['actions'], 'list', array()), $model); } } if(count($item->actions) > $maxActionCount) $maxActionCount = count($item->actions); } if(isset($fieldList['actions'])) { $fieldList['actions']['minWidth'] = $maxActionCount * 24 + 24; if(empty($fieldList['actions']['title'])) $fieldList['actions']['title'] = $lang->actions; } if($fieldList['actions']['minWidth'] < 48) $fieldList['actions']['minWidth'] = 48; return array_values($items); } /** * Check other action priv. * * @param array $actionList * @param string $actionName * @param object $item * @param object $model * @access public * @return bool */ function checkOtherPriv(array $actionConfig, string $action, object $item, object $model) { global $app; $module = $model->getModuleName(); if($module == 'flow') $module = $app->rawModule; if(!empty($actionConfig['url']['module']) && $module != $actionConfig['url']['module']) $module = $actionConfig['url']['module']; $method = $action; if(!empty($actionConfig['url']['method']) && $method != $actionConfig['url']['method']) $method = $actionConfig['url']['method']; return common::hasPriv($module, $method, $item); } /** * Set the parent property of the data. * * @param array $items * @access public * @return array */ function setParent(array $items) { foreach($items as $item) { if(isset($item->isParent)) continue; /* Set parent attribute. */ $item->isParent = false; if(isset($item->parent) && $item->parent == -1) { /* When the parent is -1, the hierarchical structure is displayed incorrectly. */ $item->parent = 0; $item->isParent = true; } if(!empty($item->parent) && isset($items[$item->parent]) && isset($item->type) && $item->type == 'stage') $items[$item->parent]->isParent = true; } return $items; } /** * Init column actions of a table. * * @param array $fieldList * @param string $actionMenu * @access public * @return void */ function initTableActions(array &$fieldList, string $actionMenu): void { $actions = explode('|', $actionMenu); foreach($actions as $action) { if(!isset($fieldList['actions']['list'][$action])) continue; $actionConfig = $fieldList['actions']['list'][$action]; if(!empty($actionConfig['icon'])) $actionConfig['text'] = ''; if(!empty($actionConfig['url']['module']) && !empty($actionConfig['url']['method'])) { $module = $actionConfig['url']['module']; $method = $actionConfig['url']['method']; $params = !empty($actionConfig['url']['params']) ? $actionConfig['url']['params'] : array(); $actionConfig['url'] = helper::createLink($module, $method, $params, '', !empty($actionConfig['url']['onlybody'])); } $fieldList['actions']['actionsMap'][$action] = $actionConfig; } } /** * Init row actions of a item. * * @param object $item * @param string $actionMenu * @param array $actionList * @param object $model * @access public * @return bool */ function initItemActions(object &$item, string $actionMenu, array $actionList, object $model): bool { if($actionMenu == 'divider') { $item->actions[] = array('name' => 'divider', 'type' => 'divider'); return true; } global $app; $module = $model->getModuleName(); if($module == 'flow') $module = $app->rawModule; $method = ''; $isClickable = false; $actions = explode('|', $actionMenu); foreach($actions as $action) { if(!isset($actionList[$action])) continue; $actionConfig = $actionList[$action]; $notLoadModel = !empty($actionConfig['notLoadModel']) ? $actionConfig['notLoadModel'] : false; if(!empty($actionConfig['url']['module']) && $module != $actionConfig['url']['module']) { $module = $actionConfig['url']['module']; if(!$notLoadModel) { $rawModule = $module == 'projectbuild' ? 'build' : $module; $model = $app->control->loadModel($rawModule); } } $method = $action; if(!empty($actionConfig['url']['method']) && $method != $actionConfig['url']['method']) $method = $actionConfig['url']['method']; if(!method_exists($model, 'isClickable') || $model->isClickable($item, !$notLoadModel ? $method : $action)) { $isClickable = true; break; } } if(!$method || !common::hasPriv($module, $method, $item)) return $isClickable; /* Check flow conditions for this object. */ if($model->config->edition != 'open') { static $flowActions = []; if(empty($flowActions[$module])) $flowActions[$module] = $model->loadModel('workflowaction')->getList($module); $model->loadModel('flow'); foreach($flowActions[$module] as $flowAction) { if($flowAction->action == $method && $flowAction->extensionType != 'none' && $flowAction->status == 'enable' && !empty($flowAction->conditions)) { $isClickable = $model->flow->checkConditions($flowAction->conditions, $item); } } } $item->actions[] = array('name' => $action, 'disabled' => !$isClickable); return $isClickable; } /** * 生成随机数。 * Generate random number. * * @access public * @return int */ function updateSessionRandom(): int { $random = mt_rand(); $_SESSION['rand'] = $random; return $random; } /** * 获取可用的界面列表。 * Get available vision list. * * @access public * @return array */ function getVisions(): array { global $config, $lang; $visions = array_flip(array_unique(array_filter(explode(',', trim($config->visions, ','))))); $visionList = $lang->visionList; return array_intersect_key($visionList, $visions); } /** * Save debug log to the php log file which prefix with 'debug.'. * * @param mixed ...$messages * @return void */ function debug(mixed ...$messages): void { if(count($messages) > 1) { foreach($messages as $message) debug($message); return; } $message = current($messages); static $times = []; $time = microtime(true); $duration = $times ? $time - end($times) : 0; $times[] = $time; static $counts = []; $count = $counts ? end($counts) + 1 : 1; $counts[] = $count; $logFile = dirname(__FILE__, 2) . '/tmp/log/debug.' . date('Ymd') . '.log.php'; $uid = $_SERVER['HTTP_X_ZIN_UID'] ?? ''; $count = sprintf('%04d', $count); $time = date('H:i:s'); $duration = round($duration, 3); if($duration < 0.001) $duration = 0.001; if(is_object($message) || is_array($message)) $message = json_encode($message, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); file_put_contents($logFile, $count . ($uid ? " uid=$uid" : '') . " time=$time duration={$duration}s message=$message\n", FILE_APPEND); } /** * 为数组使用字母排序。 * Use alphabetical sorting for arrays. * * @param array $data * @param string $fieldName * @param string $suffix * @return void */ function addPrefixToField(&$data, $fieldName, $suffix = '. ') { $key = 0; foreach($data as &$item) { $prefix = ''; $index = $key; while($index >= 0) { $prefix = chr(65 + ($index % 26)) . $prefix; $index = floor($index / 26) - 1; } if(is_array($item)) { $item[$fieldName] = $prefix . $suffix . $item[$fieldName]; } else { $item->{$fieldName} = $prefix . $suffix . $item->{$fieldName}; } $key++; } }