* @package api * @version $Id$ * @link http://www.zentao.net */ class apiModel extends model { /* Status. */ const STATUS_DOING = 'doing'; const STATUS_DONE = 'done'; const STATUS_HIDDEN = 'hidden'; /* Scope. */ const SCOPE_QUERY = 'query'; const SCOPE_FORM_DATA = 'formData'; const SCOPE_PATH = 'path'; const SCOPE_BODY = 'body'; const SCOPE_HEADER = 'header'; const SCOPE_COOKIE = 'cookie'; /* Params. */ const PARAMS_TYPE_CUSTOM = 'custom'; /** * Create release. * * @param object $data * @access public * @return int */ public function publishLib($data) { /* Get lib modules list. */ $modules = $this->dao->select('*')->from(TABLE_MODULE) ->where('root')->eq((int)$data->lib) ->andWhere('type')->eq('api') ->andWhere('deleted')->eq(0) ->orderBy('grade desc, `order`') ->fetchAll(); /* Get all api list. */ $apis = $this->dao->select('id,version')->from(TABLE_API) ->where('lib')->eq($data->lib) ->andWhere('deleted')->eq(0) ->fetchAll(); /* Get all struct list. */ $structs = $this->dao->select('id,version')->from(TABLE_APISTRUCT) ->where('lib')->eq($data->lib) ->andWhere('deleted')->eq(0) ->fetchAll(); $snap = array('modules' => $modules, 'apis' => $apis, 'structs' => $structs); $data->snap = json_encode($snap); $this->dao->insert(TABLE_API_LIB_RELEASE)->data($data) ->autoCheck() ->batchCheck($this->config->api->createrelease->requiredFields, 'notempty') ->exec(); return dao::isError() ? false : $this->dao->lastInsertID(); } /** * Delete a lib publish. * * @param int $id * @access public * @return void */ public function deleteRelease($id) { $this->dao->delete()->from(TABLE_API_LIB_RELEASE) ->where('id')->eq($id) ->exec(); } /** * Create an api doc. * * @access public * @return object | bool */ public function create() { $now = helper::now(); $data = fixer::input('post') ->trim('title,path') ->remove('type') ->skipSpecial('params,response') ->add('addedBy', $this->app->user->account) ->add('addedDate', $now) ->add('editedBy', $this->app->user->account) ->add('editedDate', $now) ->add('version', 1) ->setDefault('product,module', 0) ->get(); $this->dao->insert(TABLE_API)->data($data) ->autoCheck() ->batchCheck($this->config->api->create->requiredFields, 'notempty') ->check('title', 'unique', "lib = $data->lib AND module = $data->module") ->check('path', 'unique', "lib = $data->lib AND module = $data->module AND method = '$data->method'") ->exec(); if(dao::isError()) return false; $data->id = $this->dao->lastInsertID(); $apiSpec = $this->getApiSpecByData($data); $this->dao->replace(TABLE_API_SPEC)->data($apiSpec)->exec(); return $data; } /** * Create a global struct. * * @param object $data * @access public * @return int */ public function createStruct($data) { $data->version = 1; $this->dao->insert(TABLE_APISTRUCT)->data($data) ->autoCheck() ->batchCheck($this->config->api->struct->requiredFields, 'notempty') ->exec(); if(dao::isError()) return false; $id = $this->dao->lastInsertID(); /* Create a struct version. */ $version = array( 'name' => $data->name, 'type' => $data->type, 'desc' => $data->desc, 'version' => $data->version, 'attribute' => $data->attribute, 'addedBy' => $data->addedBy, 'addedDate' => $data->addedDate ); $this->dao->insert(TABLE_APISTRUCT_SPEC)->data($version)->exec(); if(dao::isError()) return false; return $id; } /** * Update a struct. * * @param int $id * @access public * @return array */ public function updateStruct($id) { $old = $this->dao->findByID($id)->from(TABLE_APISTRUCT)->fetch(); $now = helper::now(); $data = fixer::input('post') ->skipSpecial('attribute') ->add('lib', $old->lib) ->add('editedBy', $this->app->user->account) ->add('editedDate', $now) ->get(); unset($data->addedBy); unset($data->addedDate); $data->version = $old->version + 1; $this->dao->update(TABLE_APISTRUCT) ->data($data)->autoCheck() ->batchCheck($this->config->api->struct->requiredFields, 'notempty') ->where('id')->eq($id) ->exec(); if(dao::isError()) return false; /* Create a struct version */ $version = array( 'name' => $data->name, 'type' => $data->type, 'desc' => $data->desc, 'version' => $data->version, 'attribute' => $data->attribute, 'addedBy' => $data->editedBy, 'addedDate' => $data->editedDate ); $this->dao->insert(TABLE_APISTRUCT_SPEC)->data($version)->exec(); return common::createChanges($old, $data); } /** * Update an api doc. * * @param int $apiID * @access public * @return bool|array */ public function update($apiID) { $oldApi = $this->dao->findByID($apiID)->from(TABLE_API)->fetch(); if(!empty($_POST['editedDate']) and $oldApi->editedDate != $this->post->editedDate) { dao::$errors[] = $this->lang->error->editedByOther; return false; } $now = helper::now(); $data = fixer::input('post') ->skipSpecial('params,response') ->add('editedBy', $this->app->user->account) ->add('editedDate', $now) ->add('version', $oldApi->version) ->setDefault('product,module', 0) ->remove('type') ->get(); $changes = common::createChanges($oldApi, $data); if(!empty($changes)) $data->version = $oldApi->version + 1; $this->dao->update(TABLE_API) ->data($data) ->autoCheck() ->batchCheck($this->config->api->edit->requiredFields, 'notempty') ->where('id')->eq($apiID) ->exec(); $data->id = $apiID; $apiSpec = $this->getApiSpecByData($data); $this->dao->replace(TABLE_API_SPEC)->data($apiSpec)->exec(); return $changes; } /** * Get struct list by api doc id. * * @param int $id * @access public * @return array */ public function getStructListByLibID($id) { $res = $this->dao->select('*') ->from(TABLE_APISTRUCT) ->where('lib')->eq($id) ->fetchAll(); array_map(function ($item) { $item->attribute = json_decode($item->attribute, true); return $item; }, $res); return $res; } /** * Get a struct info. * * @param int $id * @access public * @return object */ public function getStructByID($id) { $model = $this->dao->select('*') ->from(TABLE_APISTRUCT) ->where('id')->eq($id) ->fetch(); if($model) $model->attribute = json_decode($model->attribute, true); return $model; } /** * Get release. * * @param int $libID * @param string $type * @param int $param * @access public * @return object */ public function getRelease($libID = 0, $type = '', $param = 0) { $model = $this->dao->select('*')->from(TABLE_API_LIB_RELEASE) ->where('1 = 1') ->beginIF($libID)->andWhere('lib')->eq($libID)->fi() ->beginIF($type == 'byVersion')->andWhere('version')->eq($param)->fi() ->beginIF($type == 'byId')->andWhere('id')->eq($param)->fi() ->fetch(); if($model) $model->snap = json_decode($model->snap, true); return $model; } /** * Get Versions by api id * * @param int $libID * @access public * @return array */ public function getReleaseListByApi($libID) { return $this->dao->select('*')->from(TABLE_API_LIB_RELEASE)->where('lib')->eq($libID)->fetchAll('id'); } /** * Get api doc by id. * * @param int $id * @param int $version * @param int $release * @access public * @return object */ public function getLibById($id, $version = 0, $release = 0) { if($release) { $rel = $this->getRelease(0, 'byId', $release); foreach($rel->snap['apis'] as $api) { if($api['id'] == $id) $version = $api['version']; } } if($version) { $fields = 'spec.*,api.id,api.product,api.lib,api.version,doc.name as libName,module.name as moduleName,api.editedBy,api.editedDate'; } else { $fields = 'api.*,doc.name as libName,module.name as moduleName'; } $model = $this->dao->select($fields)->from(TABLE_API)->alias('api') ->beginIF($version)->leftJoin(TABLE_API_SPEC)->alias('spec')->on('api.id = spec.doc')->fi() ->leftJoin(TABLE_DOCLIB)->alias('doc')->on('api.lib = doc.id') ->leftJoin(TABLE_MODULE)->alias('module')->on('api.module = module.id') ->where('api.id')->eq($id) ->beginIF($version)->andWhere('spec.version')->eq($version)->fi() ->fetch(); if($model) { $model->params = json_decode($model->params, true); $model->response = json_decode($model->response, true); } return $model; } /** * Get api list by release. * * @param object $release * @param string $where * @access public * @return array */ public function getApiListByRelease($release, $where = '1 = 1 ') { $strJoin = array(); if(isset($release->snap['apis'])) { foreach($release->snap['apis'] as $api) { $strJoin[] = "(spec.doc = {$api['id']} and spec.version = {$api['version']} )"; } } if($strJoin) $where .= 'and (' . implode(' or ', $strJoin) . ')'; $list = $this->dao->select('api.lib,spec.*,api.id')->from(TABLE_API)->alias('api') ->leftJoin(TABLE_API_SPEC)->alias('spec')->on('api.id = spec.doc') ->where($where) ->fetchAll(); return $list; } /** * Get api doc list by module id. * * @param int $libID * @param int $moduleID * @param int $release * @return array $list */ public function getListByModuleId($libID = 0, $moduleID = 0, $release = 0) { /* Get release info. */ if($release > 0) { $rel = $this->getRelease(0, 'byId', $release); $where = "1=1 and lib = $libID "; if($moduleID > 0 and isset($rel->snap['modules'])) { $sub = array(); foreach($rel->snap['modules'] as $module) { $tmp = explode(',', $module['path']); if(in_array($moduleID, $tmp)) { $sub[] = $module['id']; } } if($sub) $where .= 'and module in (' . implode(',', $sub) . ')'; } $list = $this->getApiListByRelease($rel, $where); } else { if($moduleID > 0) { $sub = $this->dao->select('id')->from(TABLE_MODULE)->where('FIND_IN_SET(' . $moduleID . ', path)')->processSQL(); $where = 'module in (' . $sub . ')'; } else { $where = 'lib = ' . $libID; } $list = $this->dao->select('*') ->from(TABLE_API) ->where($where) ->andWhere('deleted')->eq(0) ->fetchAll(); } array_map(function ($item) { $item->params = json_decode($item->params, true); $item->response = json_decode($item->response, true); return $item; }, $list); return $list; } /** * Get status text by status. * * @param string $status * @access public * @return string */ public static function getApiStatusText($status) { global $lang; switch($status) { case static::STATUS_DOING: { return $lang->api->doing; } case static::STATUS_DONE: { return $lang->api->done; } } } /** * @param int $libID * @param string $pager * @param string $orderBy * @access public * @return array */ public function getStructByQuery($libID, $pager = '', $orderBy = '') { return $this->dao->select('s.*,user.realname as addedName')->from(TABLE_APISTRUCT)->alias('s') ->leftJoin(TABLE_USER)->alias('user')->on('user.account = s.addedBy') ->where('s.deleted')->eq(0) ->andWhere('s.lib')->eq($libID) ->orderBy($orderBy) ->page($pager) ->fetchAll(); } /** * Get struct list by release. * * @param object $release * @param string $where * @param string $orderBy * @access public * @return array */ public function getStructListByRelease($release, $where = '1 = 1 ', $orderBy = 'id') { $strJoin = array(); if(isset($release->snap['structs'])) { foreach($release->snap['structs'] as $struct) { $strJoin[] = "(object.id = {$struct['id']} and spec.version = {$struct['version']} )"; } } if($strJoin) $where .= 'and (' . implode(' or ', $strJoin) . ')'; $list = $this->dao->select('object.lib,spec.name,spec.type,spec.desc,spec.attribute,spec.version,spec.addedBy,spec.addedDate,object.id,user.realname as addedName')->from(TABLE_APISTRUCT)->alias('object') ->leftJoin(TABLE_APISTRUCT_SPEC)->alias('spec')->on('object.name = spec.name') ->leftJoin(TABLE_USER)->alias('user')->on('user.account = spec.addedBy') ->where($where) ->orderBy($orderBy) ->fetchAll(); return $list; } /** * @param int $libID * @param string $pager * @param string $orderBy * @access public * @return array */ public function getReleaseByQuery($libID, $pager = '', $orderBy = '') { return $this->dao->select('*')->from(TABLE_API_LIB_RELEASE) ->where('lib')->eq($libID) ->orderBy($orderBy) ->page($pager) ->fetchAll(); } /** * Get struct tree by lib id * * @param int $libID * @param int $structID * @access public * @return string */ public function getStructTreeByLib($libID = 0, $structID = 0) { $list = $this->getStructListByLibID($libID); $html = ""; return $html; } /** * Get the details of the method by file path. * * @param string $filePath * @param string $ext * @access public * @return object */ public function getMethod($filePath, $ext = '') { $fileName = dirname($filePath); $className = basename(dirname(dirname($filePath))); if(!class_exists($className)) helper::import($fileName); $methodName = basename($filePath); $method = new ReflectionMethod($className . $ext, $methodName); $data = new stdClass(); $data->startLine = $method->getStartLine(); $data->endLine = $method->getEndLine(); $data->comment = $method->getDocComment(); $data->parameters = $method->getParameters(); $data->className = $className; $data->methodName = $methodName; $data->fileName = $fileName; $data->post = false; $file = file($fileName); for($i = $data->startLine - 1; $i <= $data->endLine; $i++) { if(strpos($file[$i], '$this->post') or strpos($file[$i], 'fixer::input') or strpos($file[$i], '$_POST')) { $data->post = true; } } return $data; } /** * Request the api. * * @param string $moduleName * @param string $methodName * @param string $action * @access public * @return array */ public function request($moduleName, $methodName, $action) { $host = common::getSysURL(); $param = ''; if($action == 'extendModel') { if(!isset($_POST['noparam'])) { foreach($_POST as $key => $value) $param .= ',' . $key . '=' . $value; $param = ltrim($param, ','); } $url = rtrim($host, '/') . inlink('getModel', "moduleName=$moduleName&methodName=$methodName¶ms=$param", 'json'); $url .= strpos($url, '?') === false ? '?' : '&'; $url .= $this->config->sessionVar . '=' . session_id(); } else { if(!isset($_POST['noparam'])) { foreach($_POST as $key => $value) $param .= '&' . $key . '=' . $value; $param = ltrim($param, '&'); } $url = rtrim($host, '/') . helper::createLink($moduleName, $methodName, $param, 'json'); $url .= strpos($url, '?') === false ? '?' : '&'; $url .= $this->config->sessionVar . '=' . session_id(); } /* Unlock session. After new request, restart session. */ session_write_close(); $content = file_get_contents($url); session_start(); return array('url' => $url, 'content' => $content); } /** * Query sql. * * @param string $sql * @param string $keyField * @access public * @return array */ public function sql($sql, $keyField = '') { if(!$this->config->features->apiSQL) return sprintf($this->lang->api->error->disabled, '$config->features->apiSQL'); $sql = trim($sql); if(strpos($sql, ';') !== false) $sql = substr($sql, 0, strpos($sql, ';')); $result = array(); $result['status'] = 'fail'; $result['message'] = ''; if(empty($sql)) return $result; if(stripos($sql, 'select ') !== 0) { $result['message'] = $this->lang->api->error->onlySelect; return $result; } try { $stmt = $this->dbh->query($sql); $rows = array(); if(empty($keyField)) { $rows = $stmt->fetchAll(); } else { while($row = $stmt->fetch()) $rows[$row->$keyField] = $row; } $result['status'] = 'success'; $result['data'] = $rows; } catch(PDOException $e) { $result['status'] = 'fail'; $result['message'] = $e->getMessage(); } return $result; } /** * Get spec of api. * * @param object $data * @access private * @return array */ private function getApiSpecByData($data) { return array( 'doc' => $data->id, 'module' => $data->module, 'title' => $data->title, 'path' => $data->path, 'protocol' => $data->protocol, 'method' => $data->method, 'requestType' => $data->requestType, 'responseType' => isset($data->responseType) ? $data->responseType : '', 'status' => $data->status, 'owner' => $data->owner, 'desc' => $data->desc, 'version' => $data->version, 'params' => $data->params, 'paramsExample' => $data->paramsExample, 'responseExample' => $data->responseExample, 'response' => $data->response, 'addedBy' => $this->app->user->account, 'addedDate' => helper::now(), ); } /** * Get Type list. * * @param int $libID * @access public * @return void */ public function getTypeList($libID) { $typeList = array(); foreach($this->lang->api->paramsTypeOptions as $key => $item) { $typeList[$key] = $item; } /* Get all struct by libID. */ $structs = $this->getStructListByLibID($libID); foreach($structs as $struct) { $typeList[$struct->id] = $struct->name; } return $typeList; } /** * Create demo data. * * @param string $name * @param string $baseUrl * @param string $version * @access public * @return int */ public function createDemoData($name, $baseUrl, $version = '16.0') { /* Replace the doc lib name to api lib name. */ $this->app->loadLang('doc'); $this->lang->doclib->name = $this->lang->doclib->apiLibName; $firstAccount = $this->dao->select('account')->from(TABLE_USER)->orderBy('id_asc')->limit(1)->fetch('account'); $currentAccount = isset($this->app->user->account) ? $this->app->user->account : $firstAccount; /* Insert doclib. */ $lib = new stdclass(); $lib->type = 'api'; $lib->name = $name; $lib->baseUrl = $baseUrl; $lib->acl = 'open'; $lib->users = ',' . $currentAccount . ','; $this->dao->insert(TABLE_DOCLIB)->data($lib)->autoCheck() ->batchCheck($this->config->api->createlib->requiredFields, 'notempty') ->check('name', 'unique', "`type` = 'api'") ->exec(); $libID = $this->dao->lastInsertID(); /* Insert struct. */ $structMap = array(); $structs = $this->getDemoData('apistruct', $version); foreach($structs as $struct) { $oldID = $struct->id; unset($struct->id); $struct->lib = $libID; $struct->addedBy = $currentAccount; $struct->addedDate = helper::now(); $struct->editedBy = $currentAccount; $struct->editedDate = helper::now(); $this->dao->insert(TABLE_APISTRUCT)->data($struct)->exec(); $newID = $this->dao->lastInsertID(); $structMap[$oldID] = $newID; } /* Insert struct spec. */ $specs = $this->getDemoData('apistruct_spec', $version); foreach($specs as $spec) { unset($spec->id); $spec->addedBy = $currentAccount; $spec->addedDate = helper::now(); $this->dao->insert(TABLE_APISTRUCT_SPEC)->data($spec)->exec(); } /* Insert module. */ $modules = $this->getDemoData('module', $version); foreach($modules as $module) { if($module->type != 'api') continue; $oldID = $module->id; unset($module->id); $module->root = $libID; $this->dao->insert(TABLE_MODULE)->data($module)->exec(); $newID = $this->dao->lastInsertID(); $this->dao->update(TABLE_MODULE)->set('path')->eq(",$newID,")->where('id')->eq($newID)->exec(); $moduleMap[$oldID] = $newID; } /* Insert api. */ $this->loadModel('action'); $apiMap = array(); $apis = $this->getDemoData('api', $version); foreach($apis as $api) { $oldID = $api->id; unset($api->id); $api->lib = $libID; $api->module = $moduleMap[$api->module]; $api->addedBy = $currentAccount; $api->addedDate = helper::now(); $api->editedBy = $currentAccount; $api->editedDate = helper::now(); $this->dao->insert(TABLE_API)->data($api)->exec(); $newID = $this->dao->lastInsertID(); $this->action->create('api', $newID, 'Created', '', '', $currentAccount); $apiMap[$oldID] = $newID; } /* Insert api spec. */ $specs = $this->getDemoData('apispec', $version); foreach($specs as $spec) { unset($spec->id); $spec->doc = $apiMap[$spec->doc]; $spec->module = zget($moduleMap, $spec->module, 0); $spec->owner = $currentAccount; $spec->addedBy = $currentAccount; $spec->addedDate = helper::now(); $this->dao->insert(TABLE_API_SPEC)->data($spec)->exec(); } return $libID; } /** * Get demo data. * * @param string $table * @param stirng $version * @access private * @return array */ private function getDemoData($table, $version) { $file = $this->app->getAppRoot() . 'db' . DS . 'api' . DS . $version . DS . $table; return unserialize(preg_replace_callback('#s:(\d+):"(.*?)";#s', function($match){return 's:'.strlen($match[2]).':"'.$match[2].'";';}, file_get_contents($file))); } /** * Build search form. * * @param object $lib * @param string $queryID * @param string $actionURL * @access public * @return void */ public function buildSearchForm($lib, $queryID, $actionURL) { $this->config->api->search['module'] = 'api'; $this->config->api->search['queryID'] = $queryID; $this->config->api->search['actionURL'] = $actionURL; $this->config->api->search['params']['lib']['values'] = (!empty($lib)) ? array($lib->id => $lib->name) + array('all' => $this->lang->api->allLibs) : array('all' => $this->lang->api->allLibs); $this->loadModel('search')->setSearchParams($this->config->api->search); } /** * Get api by search. * * @param string $libID * @param int $queryID * @access public * @return array */ public function getApiListBySearch($libID, $queryID) { if($queryID) { $query = $this->loadModel('search')->getQuery($queryID); if($query) { $this->session->set('apiQuery', $query->sql); $this->session->set('apiForm', $query->form); } else { $this->session->set('apiQuery', ' 1 = 1'); } } else { if($this->session->apiQuery == false) $this->session->set('apiQuery', ' 1 = 1'); } $apiQuery = $this->session->apiQuery; $apiQuery = strpos($apiQuery, "`lib` = 'all'") === false ? "$apiQuery and lib = $libID" : str_replace("`lib` = 'all'", '1', $apiQuery); $list = $this->dao->select('*') ->from(TABLE_API) ->where('deleted')->eq(0) ->andWhere($apiQuery) ->fetchAll(); return $list; } }