Merge remote-tracking branch 'origin/18.x' into sprint/4352

This commit is contained in:
mayue
2023-09-05 16:14:08 +08:00
909 changed files with 119369 additions and 3823 deletions
+353 -62
View File
@@ -298,6 +298,38 @@ class commonModel extends model
if(($this->config->edition == 'max' or $this->config->edition == 'ipd') && $this->config->vision == 'rnd') $this->config->openedApproval = true;
}
/**
* 获取表单的配置项。
* 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 = (new self())->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.
*
@@ -574,12 +606,12 @@ class commonModel extends model
$productID = isset($_SESSION['product']) ? $_SESSION['product'] : 0;
if($productID)
{
$product = $app->dbh->query("SELECT id FROM " . TABLE_PRODUCT . " WHERE `deleted` = '0' and vision = '{$config->vision}' and id = '{$productID}'")->fetch();
$product = $app->dbQuery("SELECT id FROM " . TABLE_PRODUCT . " WHERE `deleted` = '0' and vision = '{$config->vision}' and id = '{$productID}'")->fetch();
if(empty($product)) $productID = 0;
}
if(!$productID and $app->user->view->products)
{
$product = $app->dbh->query("SELECT id FROM " . TABLE_PRODUCT . " WHERE `deleted` = '0' and vision = '{$config->vision}' and id " . helper::dbIN($app->user->view->products) . " order by `order` desc limit 1")->fetch();
$product = $app->dbQuery("SELECT id FROM " . TABLE_PRODUCT . " WHERE `deleted` = '0' and vision = '{$config->vision}' and id " . helper::dbIN($app->user->view->products) . " order by `order` desc limit 1")->fetch();
if($product) $productID = $product->id;
}
@@ -588,7 +620,7 @@ class commonModel extends model
$condition = " WHERE `deleted` = '0' AND `vision` = 'lite' AND `model` = 'kanban'";
if(!$app->user->admin) $condition .= " AND `id` " . helper::dbIN($app->user->view->projects);
$object = $app->dbh->query("select id from " . TABLE_PROJECT . $condition . ' LIMIT 1')->fetch();
$object = $app->dbQuery("select id from " . TABLE_PROJECT . $condition . ' LIMIT 1')->fetch();
if(empty($object)) unset($lang->createIcons['story'], $lang->createIcons['task'], $lang->createIcons['execution']);
}
@@ -656,7 +688,7 @@ class commonModel extends model
if($config->vision == 'lite')
{
$projectID = isset($_SESSION['project']) ? $_SESSION['project'] : 0;
$projects = $app->dbh->query("SELECT t2.id FROM " . TABLE_PROJECTPRODUCT . " AS t1 LEFT JOIN " . TABLE_PROJECT . " AS t2 ON t1.project = t2.id WHERE t1.`product` = '{$productID}' and t2.`type` = 'project' and t2.id " . helper::dbIN($app->user->view->projects) . " ORDER BY `order` desc")->fetchAll();
$projects = $app->dbQuery("SELECT t2.id FROM " . TABLE_PROJECTPRODUCT . " AS t1 LEFT JOIN " . TABLE_PROJECT . " AS t2 ON t1.project = t2.id WHERE t1.`product` = '{$productID}' and t2.`type` = 'project' and t2.id " . helper::dbIN($app->user->view->projects) . " ORDER BY `order` desc")->fetchAll();
$projectIdList = array();
foreach($projects as $project) $projectIdList[$project->id] = $project->id;
@@ -742,7 +774,7 @@ class commonModel extends model
* @access public
* @return string
*/
public static function createMenuLink($menuItem, $group)
public static function createMenuLink($menuItem)
{
global $app;
$link = $menuItem->link;
@@ -893,6 +925,7 @@ class commonModel extends model
if(!in_array($tab, array('program', 'product', 'project')))
{
if(!isset($lang->mainNav->$tab)) return;
$nav = $lang->mainNav->$tab;
list($title, $currentModule, $currentMethod, $vars) = explode('|', $nav);
if($tab == 'execution') $currentMethod = 'all';
@@ -1108,11 +1141,12 @@ class commonModel extends model
/**
* Print the main menu.
*
* @param bool $printHtml
* @static
* @access public
* @return string
*/
public static function printMainMenu()
public static function printMainMenu(bool $printHtml = true): string
{
global $app, $lang, $config;
@@ -1128,19 +1162,18 @@ class commonModel extends model
$currentModule = $app->rawModule;
$currentMethod = $app->rawMethod;
if($isTutorialMode and defined('WIZARD_MODULE')) $currentModule = WIZARD_MODULE;
if($isTutorialMode and defined('WIZARD_METHOD')) $currentMethod = WIZARD_METHOD;
if($isTutorialMode && isset($_SESSION['wizardModule'])) $currentModule = $_SESSION['wizardModule'];
if($isTutorialMode && isset($_SESSION['wizardMethod'])) $currentMethod = $_SESSION['wizardMethod'];
/* Print all main menus. */
$menu = customModel::getMainMenu();
$lastMenu = end($menu);
$menu = customModel::getMainMenu();
echo "<ul class='nav nav-default'>\n";
$menuHtml = "<ul class='nav nav-default'>\n";
foreach($menu as $menuItem)
{
if(isset($menuItem->hidden) and $menuItem->hidden and (!isset($menuItem->tutorial) or !$menuItem->tutorial)) continue;
if(empty($menuItem->link)) continue;
if($menuItem->divider) echo "<li class='divider'></li>";
if($menuItem->divider) $menuHtml .= "<li class='divider'></li>";
/* Init the these vars. */
$alias = isset($menuItem->alias) ? $menuItem->alias : '';
@@ -1163,11 +1196,11 @@ class commonModel extends model
if($menuItem->link['module'] == 'execution' and $menuItem->link['method'] == 'more')
{
$executionID = $menuItem->link['vars'];
commonModel::buildMoreButton($executionID);
$menuHtml .= commonModel::buildMoreButton((int)$executionID, false);
}
elseif($menuItem->link['module'] == 'app' and $menuItem->link['method'] == 'serverlink')
{
commonModel::buildAppButton();
$menuHtml .= commonModel::buildAppButton(false);
}
else
{
@@ -1176,7 +1209,7 @@ class commonModel extends model
$target = '';
$module = '';
$method = '';
$link = commonModel::createMenuLink($menuItem, $tab);
$link = commonModel::createMenuLink($menuItem);
if($menuItem->link['module'] == 'project' and $menuItem->link['method'] == 'other') $link = 'javascript:void(0);';
@@ -1240,22 +1273,23 @@ class commonModel extends model
$label .= "<span class='caret'></span>";
$dropMenu = "<ul class='dropdown-menu'>{$dropMenu}</ul>";
echo "<li class='$class $active' data-id='$menuItem->name'>" . html::a($link, $label, $target, $misc) . $dropMenu . "</li>\n";
$menuHtml .= "<li class='$class $active' data-id='$menuItem->name'>" . html::a($link, $label, $target, $misc) . $dropMenu . "</li>\n";
}
else
{
echo "<li class='$class $active' data-id='$menuItem->name'>" . html::a($link, $label, $target, $misc) . "</li>\n";
$menuHtml .= "<li class='$class $active' data-id='$menuItem->name'>" . html::a($link, $label, $target, $misc) . "</li>\n";
}
}
else
{
echo "<li class='$class $active' data-id='$menuItem->name'>$menuItem->text</li>\n";
$menuHtml .= "<li class='$class $active' data-id='$menuItem->name'>$menuItem->text</li>\n";
}
}
}
echo "</ul>\n";
$menuHtml .= "</ul>\n";
if($printHtml) echo $menuHtml;
return $activeMenu;
}
@@ -1688,10 +1722,6 @@ EOF;
global $app, $lang, $config;
/* Add data-app attribute. */
if(strpos($misc, 'data-app') === false) $misc .= ' data-app="' . $app->tab . '"';
if($onlyBody && strpos($misc, 'data-toggle') === false) $misc .= ' data-toggle="modal"';
/* Judge the $method of $module clickable or not, default is clickable. */
$clickable = true;
if(is_bool($extraEnabled))
@@ -1710,11 +1740,17 @@ EOF;
}
}
/* Add data-app attribute. */
if(strpos($misc, 'data-app') === false) $misc .= ' data-app="' . $app->tab . '"';
if($onlyBody && strpos($misc, 'data-toggle') === false && $clickable) $misc .= ' data-toggle="modal"';
/* Set module and method, then create link to it. */
if(strtolower($module) == 'story' and strtolower($method) == 'createcase') ($module = 'testcase') and ($method = 'create');
if(strtolower($module) == 'bug' and strtolower($method) == 'tostory') ($module = 'story') and ($method = 'create');
if(strtolower($module) == 'bug' and strtolower($method) == 'createcase') ($module = 'testcase') and ($method = 'create');
if(!commonModel::hasPriv($module, $method, $object, $vars)) return false;
$currentModule = strtolower($module);
$currentMethod = strtolower($method);
if(!commonModel::hasPriv($module, $method, $object, $vars) and !in_array("$currentModule.$currentMethod", $config->openMethods)) return false;
$link = helper::createLink($module, $method, $vars, '', $onlyBody, $programID);
@@ -1779,24 +1815,25 @@ EOF;
* Build more executions button.
*
* @param int $executionID
* @param bool $printHtml
* @static
* @access public
* @return void
* @return bool
*/
public static function buildMoreButton($executionID)
public static function buildMoreButton(int $executionID, bool $printHtml = true): string
{
if(defined('TUTORIAL')) return;
global $lang, $app;
$object = $app->dbh->query('SELECT project,`type` FROM ' . TABLE_EXECUTION . " WHERE `id` = '$executionID'")->fetch();
if(empty($object)) return;
if(commonModel::isTutorialMode()) return '';
$object = $app->dbQuery('SELECT project,`type` FROM ' . TABLE_EXECUTION . " WHERE `id` = '$executionID'")->fetch();
if(empty($object)) return '';
$executionPairs = array();
$userCondition = !$app->user->admin ? " AND `id` " . helper::dbIN($app->user->view->sprints) : '';
$orderBy = $object->type == 'stage' ? 'ORDER BY `id` ASC' : 'ORDER BY `id` DESC';
$executionList = $app->dbh->query("SELECT id,name,parent,project FROM " . TABLE_EXECUTION . " WHERE `project` = '{$object->project}' AND `deleted` = '0' $userCondition $orderBy")->fetchAll();
$executionList = $app->dbQuery("SELECT id,name,parent,project FROM " . TABLE_EXECUTION . " WHERE `project` = '{$object->project}' AND `deleted` = '0' $userCondition $orderBy")->fetchAll();
$executions = array();
foreach($executionList as $execution) $executions[$execution->id] = $execution;
@@ -1808,7 +1845,7 @@ EOF;
$executionPairs[$execution->id] = $execution->name;
}
if(empty($executionPairs)) return;
if(empty($executionPairs)) return '';
$html = "<li class='divider'></li><li class='dropdown dropdown-hover'><a href='javascript:;' data-toggle='dropdown'>{$lang->more}<span class='caret'></span></a>";
$html .= "<ul class='dropdown-menu'>";
@@ -1826,22 +1863,24 @@ EOF;
$html .= "</ul></li>\n";
echo $html;
if($printHtml) echo $html;
return $html;
}
/**
* Build devops app button.
*
* @param bool $printHtml
* @static
* @access public
* @return void
* @return bool
*/
public static function buildAppButton()
public static function buildAppButton(bool $printHtml = true): string
{
if(defined('TUTORIAL')) return;
global $app, $config, $lang;
$pipelinePairs = array();
if(commonModel::isTutorialMode()) return '';
$condition = '';
if(!$app->user->admin)
{
@@ -1850,11 +1889,11 @@ EOF;
{
if(commonModel::hasPriv($pipelineType, 'browse')) $types .= "'$pipelineType',";
}
if(empty($types)) return;
if(empty($types)) return '';
$condition .= ' AND `type` in (' . trim($types, ',') . ')';
}
$pipelineList = $app->dbh->query("SELECT `type`,name,url FROM " . TABLE_PIPELINE . " WHERE `deleted` = '0' $condition order by type")->fetchAll();
if(empty($pipelineList)) return;
$pipelineList = $app->dbQuery("SELECT `type`,name,url FROM " . TABLE_PIPELINE . " WHERE `deleted` = '0' $condition order by type")->fetchAll();
if(empty($pipelineList)) return '';
$appCommon = isset($lang->db->custom['devopsMenu']['menu']['app']) ? $lang->db->custom['devopsMenu']['menu']['app'] : $lang->app->common;
$html = "<li class='dropdown dropdown-hover'><a href='javascript:;' data-toggle='dropdown'>{$appCommon}<span class='caret'></span></a>";
@@ -1866,7 +1905,8 @@ EOF;
}
$html .= "</ul></li>\n";
echo $html;
if($printHtml) echo $html;
return $html;
}
/**
@@ -2031,12 +2071,12 @@ EOF;
if($oldID and $oldStatus and $newStatus and !$newSubStatus and $oldStatus != $newStatus)
{
$field = $app->dbh->query('SELECT options FROM ' . TABLE_WORKFLOWFIELD . " WHERE `module` = '$moduleName' AND `field` = 'subStatus'")->fetch();
$field = $app->dbQuery('SELECT options FROM ' . TABLE_WORKFLOWFIELD . " WHERE `module` = '$moduleName' AND `field` = 'subStatus'")->fetch();
if(!empty($field->options)) $field->options = json_decode($field->options, true);
if(!empty($field->options[$newStatus]['default']))
{
$flow = $app->dbh->query('SELECT `table` FROM ' . TABLE_WORKFLOW . " WHERE `module`='$moduleName'")->fetch();
$flow = $app->dbQuery('SELECT `table` FROM ' . TABLE_WORKFLOW . " WHERE `module`='$moduleName'")->fetch();
$default = $field->options[$newStatus]['default'];
$app->dbh->exec("UPDATE `$flow->table` SET `subStatus` = '$default' WHERE `id` = '$oldID'");
@@ -2047,7 +2087,7 @@ EOF;
$dateFields = array();
$sql = "SELECT `field` FROM " . TABLE_WORKFLOWFIELD . " WHERE `module` = '{$moduleName}' and `control` in ('date', 'datetime')";
$stmt = $app->dbh->query($sql);
$stmt = $app->dbQuery($sql);
while($row = $stmt->fetch()) $dateFields[$row->field] = $row->field;
}
@@ -2504,17 +2544,17 @@ EOF;
*/
public function checkIframe()
{
/**
/*
* 忽略如下情况:非 HTML 请求、Ajax 请求、特殊 GET 参数 _single。
* Ignore the following situations: non-HTML request, Ajax request, special GET parameter _single.
*/
if($this->app->getViewType() != 'html' || helper::isAjaxRequest() || isset($_GET['_single'])) return;
if($this->app->getViewType() != 'html' || helper::isAjaxRequest() || isset($_GET['_single'])) return true;
/**
* 忽略无请求头 HTTP_SEC_FETCH_DEST 或者 HTTP_SEC_FETCH_DEST 为 iframe 的请求,较新的浏览器在启用 https 的情况下才会正确发送该请求头。
* Ignore the request without HTTP_SEC_FETCH_DEST or HTTP_SEC_FETCH_DEST is iframe, the latest browser will send this request header correctly when enable https.
*/
if(!isset($_SERVER['HTTP_SEC_FETCH_DEST']) || $_SERVER['HTTP_SEC_FETCH_DEST'] == 'iframe') return;
if(!isset($_SERVER['HTTP_SEC_FETCH_DEST']) || $_SERVER['HTTP_SEC_FETCH_DEST'] == 'iframe') return true;
/**
* 当有 HTTP_REFERER 请求头时,忽略 safari 浏览器,因为 safari 浏览器不会正确发送 HTTP_SEC_FETCH_DEST 请求头。
@@ -2523,9 +2563,16 @@ EOF;
if(isset($_SERVER['HTTP_REFERER']) && !empty($_SERVER['HTTP_REFERER']))
{
$userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
if(strpos($userAgent, 'chrome') === false && strpos($userAgent, 'safari') !== false) return;
if(strpos($userAgent, 'chrome') === false && strpos($userAgent, 'safari') !== false) return true;
}
/**
* 忽略所有方法名以 ajax 开头的请求。
* Ignore all requests which it's method name starts with 'ajax'.
*/
$method = $this->app->getMethodName();
if(strpos($method, 'ajax') === 0) return true;
/**
* 以下页面可以允许在非 iframe 中打开,所以要忽略这些页面。
* The following pages can be allowed to open in non-iframe, so ignore these pages.
@@ -2533,7 +2580,7 @@ EOF;
$module = $this->app->getModuleName();
$method = $this->app->getMethodName();
$whitelist = '|index|tutorial|install|upgrade|sso|cron|misc|user-login|user-deny|user-logout|user-reset|user-forgetpassword|user-resetpassword|my-changepassword|my-preference|file-read|file-download|file-preview|file-uploadimages|file-ajaxwopifiles|report-annualdata|misc-captcha|execution-printkanban|traincourse-ajaxuploadlargefile|traincourse-playvideo|screen-view|zanode-create|screen-ajaxgetchart|';
if(strpos($whitelist, "|{$module}|") !== false || strpos($whitelist, "|{$module}-{$method}|") !== false) return;
if(strpos($whitelist, "|{$module}|") !== false || strpos($whitelist, "|{$module}-{$method}|") !== false) return true;
/**
* 如果以上条件都不满足,则视为当前页面必须在 iframe 中打开,使用 302 跳转实现。
@@ -2542,7 +2589,8 @@ EOF;
$url = helper::safe64Encode($_SERVER['REQUEST_URI']);
$redirectUrl = helper::createLink('index', 'index');
$redirectUrl .= strpos($redirectUrl, '?') === false ? "?open=$url" : "&open=$url";
die(header("location: $redirectUrl"));
helper::header('location', $redirectUrl);
return false;
}
/**
@@ -2584,6 +2632,9 @@ EOF;
/* Check the parent object is closed. */
if(!empty($method) and strpos('close|batchclose', $method) === false and !commonModel::canBeChanged($module, $object)) return false;
/* Check the method is openMethod. */
if(in_array("$module.$method", $config->openMethods)) return true;
/* Check is the super admin or not. */
if(!empty($app->user->admin) or strpos($app->company->admins, ",{$app->user->account},") !== false) return true;
@@ -3246,8 +3297,12 @@ EOF;
commonModel::$requestErrors = array();
$requestType = 'GET';
if(!is_array($headers)) $headers = (array)$headers;
$headers[] = "API-RemoteIP: " . helper::getRemoteIp();
$headers[] = 'API-RemoteIP: ' . helper::getRemoteIp(); /* Real IP of real user. */
$headers[] = 'API-LocalIP: ' . zget($_SERVER, 'SERVER_ADDR', ''); /* Server IP of self. */
if($dataType == 'json')
{
$headers[] = 'Content-Type: application/json;charset=utf-8';
@@ -3260,11 +3315,11 @@ EOF;
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_ENCODING, "");
curl_setopt($curl, CURLOPT_ENCODING,'');
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_HEADER, $httpCode);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 2);
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
@@ -3275,14 +3330,36 @@ EOF;
if(is_object($data)) $data = (array) $data;
if($method == 'POST') curl_setopt($curl, CURLOPT_POST, true);
if(in_array($method, array('PATCH', 'PUT'))) curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
$requestType = $method;
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
if($options) curl_setopt_array($curl, $options);
$response = curl_exec($curl);
$errors = curl_error($curl);
if($httpCode) $httpCode = curl_getinfo($curl,CURLINFO_HTTP_CODE);
$errno = curl_errno($curl);
$errors = empty($errno) ? 0 : curl_error($curl);
$info = curl_getinfo($curl);
if($httpCode)
{
$httpCode = $info['http_code'] ?? curl_getinfo($curl, CURLINFO_HTTP_CODE);
$headerSize = $info['header_size'] ?? curl_getinfo($curl, CURLINFO_HEADER_SIZE);
$headerString = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
/* Parse header. */
$header = explode("\n", $headerString);
$newHeader = array();
foreach($header as $item)
{
$field = explode(':', $item);
if(count($field) < 2) continue;
$headerKey = array_shift($field);
$newHeader[$headerKey] = implode('', $field);
}
}
curl_close($curl);
if($log or $app->config->debug)
@@ -3290,21 +3367,28 @@ EOF;
$logFile = $app->getLogRoot() . 'saas.'. date('Ymd') . '.log.php';
if(!file_exists($logFile)) file_put_contents($logFile, '<?php die(); ?' . '>');
$fh = @fopen($logFile, 'a');
$fh = fopen($logFile, 'a');
if($fh)
{
fwrite($fh, date('Ymd H:i:s') . ": " . $app->getURI() . "\n");
fwrite($fh, "url: " . $url . "\n");
if(!empty($data)) fwrite($fh, "data: " . print_r($data, true) . "\n");
fwrite($fh, "results:" . print_r($response, true) . "\n");
if(!empty($errors)) fwrite($fh, "errors: " . $errors . "\n");
fwrite($fh, date('Ymd H:i:s') . ': ' . $app->getURI() . "\n");
fwrite($fh, "{$requestType} url: " . $url . "\n");
if(!empty($data)) fwrite($fh, 'data: ' . print_r($data, true) . "\n");
fwrite($fh, 'results:' . print_r($response, true) . "\n");
if(!empty($errors))
{
fwrite($fh, 'errno: ' . $errno . "\n");
fwrite($fh, 'errors: ' . $errors . "\n");
fwrite($fh, 'info: ' . print_r($info, true) . "\n");
}
fclose($fh);
}
}
if($errors) commonModel::$requestErrors[] = $errors;
return $httpCode ? array($response, $httpCode) : $response;
return $httpCode ? array($response, $httpCode, 'body' => $body, 'header' => $newHeader, 'errno' => $errno, 'info' => $info, 'response' => $response) : $response;
}
/**
@@ -3647,6 +3731,96 @@ EOF;
return false;
}
/**
* Get method of API.
*
* @param string $url
* @param array|object $data
* @param array $headers example: array("key1:value1", "key2:value2")
* @access public
* @return object
*/
public static function apiGet($url, $data = array(), $headers = array())
{
$url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($data, '', '&', PHP_QUERY_RFC3986);
$result = json_decode(commonModel::http($url, $data, array(CURLOPT_CUSTOMREQUEST => 'GET'), $headers, 'json'));
if($result && $result->code == 200) return $result;
return static::apiError($result);
}
/**
* Post method of API.
*
* @param string $url
* @param array|object $data
* @param array $headers example: array("key1:value1", "key2:value2")
* @access public
* @return object
*/
public static function apiPost($url, $data, $headers = array())
{
$result = json_decode(commonModel::http($url, $data, array(CURLOPT_CUSTOMREQUEST => 'POST'), $headers, 'json'));
if($result && $result->code == 200) return $result;
return static::apiError($result);
}
/**
* Put method of API.
*
* @param string $url
* @param array|object $data
* @param array $headers example: array("key1:value1", "key2:value2")
* @access public
* @return object
*/
public static function apiPut($url, $data, $headers = array())
{
$result = json_decode(commonModel::http($url, $data, array(CURLOPT_CUSTOMREQUEST => 'PUT'), $headers, 'json'));
if($result && $result->code == 200) return $result;
return static::apiError($result);
}
/**
* Delete method of API.
*
* @param string $url
* @param array|object $data
* @param array $headers example: array("key1:value1", "key2:value2")
* @access public
* @return object
*/
public static function apiDelete($url, $data, $headers = array())
{
$result = json_decode(commonModel::http($url, $data, array(CURLOPT_CUSTOMREQUEST => 'DELETE'), $headers, 'json'));
if($result && $result->code == 200) return $result;
return static::apiError($result);
}
/**
* Return error object of api server.
*
* @param object|null $result
* @access protected
* @return object
*/
protected static function apiError($result)
{
global $lang;
if($result && $result->code) return $result;
$error = new stdclass;
$error->code = 600;
$error->message = $lang->error->httpServerError;
return $error;
}
/**
* Check object priv.
*
@@ -3663,6 +3837,123 @@ EOF;
return false;
}
/**
* 按照模块生成详情页的操作按钮。
* Build operate actions menu.
*
* @param object $data
* @access public
* @return array
*/
public function buildOperateMenu(object $data): array
{
global $app, $config;
/* build operate menu. */
$moduleName = $app->moduleName;
$methodName = $app->methodName;
$actionsMenu = array();
$this->loadModel($moduleName);
foreach($config->{$moduleName}->actions->{$methodName} as $menu => $actionList)
{
$actions = array();
foreach($actionList as $action)
{
$actionData = $config->{$moduleName}->actionList[$action];
if(!empty($actionData['url']) && is_array($actionData['url']))
{
$module = $actionData['url']['module'];
$method = $actionData['url']['method'];
$params = $actionData['url']['params'];
if(!common::hasPriv($module, $method)) continue;
$actionData['url'] = helper::createLink($module, $method, $params);
}
else if(!empty($actionData['data-url']) && is_array($actionData['data-url']))
{
$module = $actionData['data-url']['module'];
$method = $actionData['data-url']['method'];
$params = $actionData['data-url']['params'];
if(!common::hasPriv($module, $method)) continue;
$actionData['data-url'] = helper::createLink($module, $method, $params);
}
else
{
if(!common::hasPriv($moduleName, $action)) continue;
}
if(method_exists($this->{$moduleName}, 'isClickable') && false === $this->{$moduleName}->isClickable($data, $action)) continue;
if($menu == 'suffixActions' && !empty($actionData['text']) && empty($actionData['showText'])) $actionData['text'] = '';
$actions[] = $actionData;
}
$actionsMenu[$menu] = $actions;
}
return $actionsMenu;
}
/**
* 计算应用运行时间。
* Print duration of a instance.
*
* @param int $seconds
* @param string $format y-m-d-h-i-s, case insensitive
* @static
* @access public
* @return string
*/
public static function printDuration(int $seconds, string $format = 'y-m-d-h-i-s'): string
{
global $lang;
$duration = '';
$format = strtolower($format);
if(strpos($format, 'y') !== false)
{
$years = intval($seconds / (3600 * 24 * 365));
$leftSeconds = intval($seconds % (3600 * 24 * 365));
if($years) $duration .= $years . $lang->year;
}
if(strpos($format, 'm') !== false)
{
$months = intval($leftSeconds / (3600 * 24 * 30));
$leftSeconds = intval($leftSeconds % (3600 * 24 * 30));
if($months) $duration .= $months . $lang->month;
}
if(strpos($format, 'd') !== false)
{
$days = intval($leftSeconds / (3600 * 24));
$leftSeconds = intval($leftSeconds % (3600 * 24));
if($days) $duration .= $days . trim($lang->day);
}
if(strpos($format, 'h') !== false)
{
$hours = intval($leftSeconds / 3600);
$leftSeconds = intval($leftSeconds % 3600);
if($hours) $duration .= $hours . $lang->hour;
}
if(strpos($format, 'i') !== false)
{
$minutes = intval($leftSeconds / 60);
$leftSeconds = intval($leftSeconds % 60);
if($minutes) $duration .= $minutes . $lang->minute;
}
if(strpos($format, 's') !== false)
{
$seconds = intval($leftSeconds % 3600);
if($seconds) $duration .= $seconds . $lang->second;
}
return $duration;
}
}
class common extends commonModel