diff --git a/app/constants.php b/app/constants.php index d6da0487e..89c79e6cd 100644 --- a/app/constants.php +++ b/app/constants.php @@ -20,6 +20,9 @@ // 项目程序模型文件所在根目录(文件系统) define('MODEL_PATH', APP_PATH . 'model/'); +// 插件目录 +define('PLUGIN_PATH', APP_PATH . 'plugin/'); + // 项目程序服务文件所在根目录(文件系统) define('API_PATH', APP_PATH . 'api/'); diff --git a/app/ctrl/BaseCtrl.php b/app/ctrl/BaseCtrl.php index b68bad9da..066eff837 100644 --- a/app/ctrl/BaseCtrl.php +++ b/app/ctrl/BaseCtrl.php @@ -4,12 +4,12 @@ use main\app\classes\UserAuth; use main\app\classes\UserLogic; +use main\app\model\DbModel; use main\app\model\user\UserPostedFlagModel; use main\app\model\SettingModel; use main\app\model\system\AnnouncementModel; use main\app\model\user\UserModel; use main\app\protocol\Ajax; -use main\lib\MyPdo; /** * 控制器基类 @@ -223,7 +223,7 @@ public function render($tpl, $dataArr = [], $partial = false) if ($tplEngine == 'php') { require_once VIEW_PATH . $tpl; if (!$partial && XPHP_DEBUG) { - $sqlLogs = MyPdo::$sqlLogs; + $sqlLogs = DbModel::$sqlLogs; include_once VIEW_PATH . 'debug.php'; unset($sqlLogs); } diff --git a/app/ctrl/OrgRoute.php b/app/ctrl/OrgRoute.php index 290a3403b..343a0e9d4 100644 --- a/app/ctrl/OrgRoute.php +++ b/app/ctrl/OrgRoute.php @@ -51,6 +51,9 @@ public function pageIndex() case 'issues': $projectCtrlMain->pageIssues(); break; + case 'plugin': + $projectCtrlMain->pagePlugin(); + break; case 'summary': $projectCtrlMain->pageHome(); break; diff --git a/app/ctrl/Upgrade.php b/app/ctrl/Upgrade.php index 3e7749a6c..2d0793607 100644 --- a/app/ctrl/Upgrade.php +++ b/app/ctrl/Upgrade.php @@ -258,7 +258,7 @@ public function run() * 执行SQL * * @param $sql - * @param \main\lib\MyPdo $db + * @param \main\app\model\DbModel $db * @return bool */ private function runSql($sql, $db) diff --git a/app/ctrl/admin/DataBackup.php b/app/ctrl/admin/DataBackup.php index c3a5f53dd..9fd797d5c 100644 --- a/app/ctrl/admin/DataBackup.php +++ b/app/ctrl/admin/DataBackup.php @@ -31,7 +31,7 @@ public function pageIframeBackup() mkdir($backupPath,0777); } - $dbConfig = getCommonConfigVar('database'); + $dbConfig = getYamlConfigByModule('database'); $dbConfig = $dbConfig['default']; $time = -microtime(true); @@ -68,7 +68,7 @@ public function pageIframeRecover($dump_file_name) } - $dbConfig = getCommonConfigVar('database'); + $dbConfig = getYamlConfigByModule('database'); $dbConfig = $dbConfig['default']; $dumpFile = STORAGE_PATH .'dump_test.sql.gz'; diff --git a/app/ctrl/admin/Main.php b/app/ctrl/admin/Main.php index 5f368b28e..371cefab3 100644 --- a/app/ctrl/admin/Main.php +++ b/app/ctrl/admin/Main.php @@ -54,7 +54,7 @@ public function pageIndex() $settingModel = new SettingModel(); $mysqlVersionStr = $settingModel->getFieldBySql($versionSql); - $dbConf = getCommonConfigVar('database'); + $dbConf = getYamlConfigByModule('database'); $data['sys_domain'] = ROOT_URL;//ServerInfo::getDomain(); $data['sys_datetime'] = date('Y-m-d H:i:s', time()); diff --git a/app/ctrl/issue/Main.php b/app/ctrl/issue/Main.php index c7596f806..f0d2404b4 100644 --- a/app/ctrl/issue/Main.php +++ b/app/ctrl/issue/Main.php @@ -658,6 +658,7 @@ public function filter() $userLogic = new UserLogic(); $users = $userLogic->getAllUser(); + $emptyObj = new \stdClass(); foreach ($data['issues'] as &$issue) { $issueId = $issue['id']; IssueFilterLogic::formatIssue($issue); @@ -672,7 +673,6 @@ public function filter() $customValueArr = $customValuesIssueArr[$issueId]; $issue = array_merge($customValueArr, $issue); } - $emptyObj = new \stdClass(); $issue['creator_info'] = isset($users[$issue['creator']])?$users[$issue['creator']]:$emptyObj; $issue['modifier_info'] = isset($users[$issue['modifier']])?$users[$issue['modifier']]:$emptyObj; $issue['reporter_info'] = isset($users[$issue['reporter']])?$users[$issue['reporter']]:$emptyObj; diff --git a/app/ctrl/project/Main.php b/app/ctrl/project/Main.php index 9d8997c82..f3ea90040 100644 --- a/app/ctrl/project/Main.php +++ b/app/ctrl/project/Main.php @@ -210,6 +210,24 @@ public function pageIssues() $issueMainCtrl->pageIndex(); } + /** + * 跳转至事项页面 + * @throws \Exception + */ + public function pagePlugin() + { + // 1.取出插件名称 + $pluginName = $_GET['_target'][3]; + + // 2. 数据库查询插件配置信息 + + // 3. 实例化插件控制器和入口 + + // 渲染数据 + + + } + /** * backlog页面 * @throws \Exception diff --git a/app/event/Events.php b/app/event/Events.php new file mode 100644 index 000000000..50eaec3f8 --- /dev/null +++ b/app/event/Events.php @@ -0,0 +1,36 @@ +ctrl = $ctrl; + $this->issueModel = $issueModel; + } + + /** + * @return IssueModel + */ + public function getIssueModel() + { + return $this->issueModel; + } + + /** + * @return BaseUserCtrl|null + */ + public function getCtrl() + { + return $this->ctrl; + } +} diff --git a/app/globals.php b/app/globals.php index bd0d8826d..0851fdd22 100644 --- a/app/globals.php +++ b/app/globals.php @@ -1,24 +1,26 @@ '插件名称', + # 'directory'=>'插件安装目录' + #); + $this->dispatcher = new EventDispatcher(); + if ($plugins) { + foreach ($plugins as $plugin) { + $pluginName = $plugin['name']; + $pluginFile = PRE_APP_PATH . 'plugin/' . $plugin['directory'] . '/' . $pluginName . '.php'; + if (file_exists($pluginFile)) { + include_once($pluginFile); + if (class_exists($pluginName)) { + //初始化所有插件 + $this->_plugins[$pluginName] = new $pluginName($ctrlObj, $this); + } + } + } + } + #此处做些日志记录方面的东西 + } + + /** + * 注册需要监听的插件方法(钩子) + * + * @param string $hook + * @param object $reference + * @param string $method + */ + function register($hook, &$reference, $method) + { + //获取插件要实现的方法 + $key = get_class($reference) . '->' . $method; + //将插件的引用连同方法push进监听数组中 + $this->_listeners[$hook][$key] = array(&$reference, $method); + #此处做些日志记录方面的东西 + } + + /** + * 触发一个钩子 + * + * @param string $hook 钩子的名称 + * @param mixed $data 钩子的入参 + * @return mixed + */ + function trigger($hook, $data = null) + { + $result = ''; + //查看要实现的钩子,是否在监听数组之中 + if (isset($this->_listeners[$hook]) && is_array($this->_listeners[$hook]) && count($this->_listeners[$hook]) > 0) { + // 循环调用开始 + foreach ($this->_listeners[$hook] as $listener) { + // 取出插件对象的引用和方法 + $class =& $listener[0]; + $method = $listener[1]; + if (method_exists($class, $method)) { + // 动态调用插件的方法 + $result .= $class->$method($data); + } + } + } + #此处做些日志记录方面的东西 + return $result; + } +} \ No newline at end of file diff --git a/app/plugin/activity/ActivityPlugin.php b/app/plugin/activity/ActivityPlugin.php new file mode 100644 index 000000000..880610a8b --- /dev/null +++ b/app/plugin/activity/ActivityPlugin.php @@ -0,0 +1,96 @@ +getEventSubscriberFile(realpath(dirname(__FILE__))); + $this->loadEventSubscriber($pluginManager); + } + + /** + * 递归获取事件订阅类 + * @param $subscriberDir + */ + public function getEventSubscriberFile($subscriberDir) + { + $currentDir = dir($subscriberDir); + while ($file = $currentDir->read()) { + if ((is_dir($subscriberDir . $file)) and ($file != ".") and ($file != "..")) { + $this->getEventSubscriberFile($subscriberDir . $file . '/'); + } else { + $subClassPath = str_replace(PLUGIN_PATH, '', $subscriberDir); + $subClassPath = str_replace('/', "\\", $subClassPath); + $file = pathinfo($file); + if ($file['extension'] = 'php' + && strpos($file['basename'], 'Model') !== false + && !in_array($file['basename'], ['BaseModel', 'DbModel']) + ) { + $this->subscribersArr[] = $subClassPath . $file['basename']; + } + } + } + $currentDir->close(); + + } + + /** + * 添加事件订阅 + * @param $pluginManager + */ + public function loadEventSubscriber($pluginManager) + { + foreach ($this->subscribersArr as $subscriberName) { + $subscriberClass = str_replace('.php', '', $subscriberName); + // require_once MODEL_PATH.$modelName.'.php'; + $subscriberClass = sprintf("main\\%s\\plugin\\event\\%s", APP_NAME, $subscriberClass); + //var_dump($model_class); + if (!class_exists($subscriberClass)) { + // @todo 通用的使用日志写入 + echo sprintf("plugin %s event/%s no found ", __CLASS__, $subscriberClass)."\n"; + } + $subscriberObj = new $subscriberClass(); + + // @todo 通过反射是否实现 EventSubscriberInterface 接口 + if ($subscriberObj && method_exists($subscriberObj, 'getSubscribedEvents')) { + $pluginManager->dispatcher->addSubscriber($subscriberObj); + } + } + } + + /** + * 插件安装时执行动作 + * @param $pluginManager + */ + public function beforeInstallEvent($pluginManager) + { + + } + + /** + * 安装完毕后 + * @param $pluginManager + */ + public function afterInstallEvent($pluginManager) + { + + } + + + /** + * 卸载插件时的操作 + * @param $pluginManager + */ + public function unstallEvent($pluginManager) + { + + } +} \ No newline at end of file diff --git a/app/plugin/activity/classes/.gitignore b/app/plugin/activity/classes/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/app/plugin/activity/classes/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/app/plugin/activity/ctrl/index.php b/app/plugin/activity/ctrl/index.php new file mode 100644 index 000000000..8fcb251de --- /dev/null +++ b/app/plugin/activity/ctrl/index.php @@ -0,0 +1,351 @@ +initCSRF(); + // 向视图传入通用的变量 + $this->addGVar('_GET', $_GET); + $this->addGVar('site_url', ROOT_URL); + $this->addGVar('attachment_url', ATTACHMENT_URL); + $this->addGVar('_version', MASTERLAB_VERSION); + $this->addGVar('csrf_token', $this->csrfToken); + $user = []; + $curUid = UserAuth::getInstance()->getId(); + if ($curUid) { + $user = UserModel::getInstance($curUid)->getUser(); + $user = UserLogic::format($user); + } + $this->addGVar('user', $user); + + $dataArr = array_merge(self::$gTplVars, $dataArr); + ob_start(); + ob_implicit_flush(false); + extract($dataArr, EXTR_PREFIX_SAME, 'tpl_'); + + require_once PLUGIN_PATH .'activity/view/'. $tpl; + + echo ob_get_clean(); + } + + public function pageIndex() + { + $data = []; + $data['title'] = '项目活动日志'; + $data['nav_links_active'] = 'activity'; + $data = RewriteUrl::setProjectData($data); + // 权限判断 + if (!empty($data['project_id'])) { + if (!$this->isAdmin && !PermissionLogic::checkUserHaveProjectItem(UserAuth::getId(), $data['project_id'])) { + $this->warn('提 示', '您没有权限访问该项目,请联系管理员申请加入该项目'); + die; + } + } + $projectId = $data['project_id']; + $data['current_uid'] = UserAuth::getId(); + $userLogic = new UserLogic(); + $projectUsers = $userLogic->getUsersAndRoleByProjectId($projectId); + + foreach ($projectUsers as &$user) { + $user = UserLogic::format($user); + } + $data['project_users'] = $projectUsers; + + $projectRolemodel = new ProjectRoleModel(); + $data['roles'] = $projectRolemodel->getsByProject($projectId); + + $this->myRender('index.php', $data); + } + + + /** + * @param $id + * @throws \Exception + */ + public function fetch($id) + { + if (!isset($id)) { + $this->ajaxFailed('提示', '缺少参数'); + } + $model = new ProjectCatalogLabelModel(); + $arr = $model->getById($id); + $this->ajaxSuccess('success', $arr); + } + + public function fetchAll() + { + $projectId = null; + if (isset($_GET['_target'][3])) { + $projectId = (int)$_GET['_target'][3]; + } + if (isset($_GET['project_id'])) { + $projectId = (int)$_GET['project_id']; + } + if (empty($projectId)) { + $this->ajaxFailed('参数错误', '项目id不能为空'); + } + $model = new ProjectCatalogLabelModel(); + $data['catalogs'] = $model->getByProject($projectId); + $this->ajaxSuccess('ok', $data); + } + + /** + * @throws \Exception + */ + public function add() + { + $uid = $this->getCurrentUid(); + $projectId = null; + if (isset($_POST['project_id'])) { + $projectId = (int)$_POST['project_id']; + } + if (empty($projectId)) { + $this->ajaxFailed('参数错误', '项目id不能为空'); + } + + $errorMsg = []; + if (!isset($_POST['name']) || empty($_POST['name'])) { + $errorMsg['name'] = '名称不能为空'; + } + if (!isset($_POST['label_id_arr']) || empty($_POST['label_id_arr'])) { + $errorMsg['label_id_arr'] = '包含标签不能为空'; + } + + if (!isset($_POST['font_color']) || empty($_POST['font_color'])) { + $_POST['font_color'] = 'blueviolet'; + } + if (!empty($errorMsg)) { + $this->ajaxFailed('参数错误', $errorMsg, BaseCtrl::AJAX_FAILED_TYPE_FORM_ERROR); + } + $projectCatalogLabelModel = new ProjectCatalogLabelModel(); + + if ($projectCatalogLabelModel->checkNameExist($projectId, $_POST['name'])) { + $this->ajaxFailed('分类名称已存在.', array(), 500); + } + $insertArr = []; + $insertArr['project_id'] = $projectId; + $insertArr['name'] = $_POST['name']; + $insertArr['font_color'] = $_POST['font_color']; + $insertArr['label_id_json'] = json_encode($_POST['label_id_arr']); + if (isset($_POST['description'])) { + $insertArr['description'] = $_POST['description']; + } + if (isset($_POST['order_weight'])) { + $insertArr['order_weight'] = (int)$_POST['order_weight']; + } + + list($ret, $errMsg) = $projectCatalogLabelModel->insert($insertArr); + if ($ret) { + $activityModel = new ActivityModel(); + $activityInfo = []; + $activityInfo['action'] = '创建了分类'; + $activityInfo['type'] = ActivityModel::TYPE_PROJECT; + $activityInfo['obj_id'] = $errMsg; + $activityInfo['title'] = $insertArr['name']; + $activityModel->insertItem($uid, $projectId, $activityInfo); + + //写入操作日志 + $logData = []; + $logData['user_name'] = $this->auth->getUser()['username']; + $logData['real_name'] = $this->auth->getUser()['display_name']; + $logData['obj_id'] = $errMsg; + $logData['module'] = LogOperatingLogic::MODULE_NAME_PROJECT; + $logData['page'] = $_SERVER['REQUEST_URI']; + $logData['action'] = LogOperatingLogic::ACT_ADD; + $logData['remark'] = '添加分类'; + $logData['pre_data'] = []; + $logData['cur_data'] = $insertArr; + LogOperatingLogic::add($uid, $projectId, $logData); + + $this->ajaxSuccess('提示', '分类添加成功'); + } else { + $this->ajaxFailed('提示', '服务器执行失败:'.$errMsg); + } + } + + + /** + * @param $id + * @param $title + * @param $bg_color + * @param $description + * @throws \Exception + */ + public function update() + { + $currentUserId = $this->getCurrentUid(); + $id = null; + if (isset($_POST['id'])) { + $id = (int)$_POST['id']; + } + if (empty($id)) { + $this->ajaxFailed('提示', '参数错误,id不能为空'); + } + + $errorMsg = []; + if (isset($_POST['name']) && empty($_POST['name'])) { + $errorMsg['name'] = '名称不能为空'; + } + if (isset($_POST['label_id_arr']) && empty($_POST['label_id_arr'])) { + $errorMsg['label_id_arr'] = '包含标签不能为空'; + } + if (!empty($errorMsg)) { + $this->ajaxFailed('参数错误', $errorMsg, BaseCtrl::AJAX_FAILED_TYPE_FORM_ERROR); + } + + $updateArr = []; + if (isset($_POST['name'])) { + $updateArr['name'] = $_POST['name']; + } + if (isset($_POST['label_id_arr'])) { + $updateArr['label_id_json'] = json_encode($_POST['label_id_arr']); + } + if (isset($_POST['font_color'])) { + $updateArr['font_color'] = $_POST['font_color']; + } + if (isset($_POST['description'])) { + $updateArr['description'] = $_POST['description']; + } + if (isset($_POST['order_weight'])) { + $updateArr['order_weight'] = (int)$_POST['order_weight']; + } + + $model = new ProjectCatalogLabelModel(); + $catalog = $model->getById($id); + if (empty($catalog)) { + $this->ajaxFailed('提示', '参数错误, 数据为空'); + } + if (!isset($this->projectPermArr[PermissionLogic::ADMINISTER_PROJECTS])) { + $this->ajaxFailed('提示', '您没有权限访问该页面,需要项目管理权限'); + } + if ($catalog['project_id'] != $this->projectId) { + $this->ajaxFailed('提示', '参数错误, 非当前项目的数据'); + } + if ($catalog['name'] != $updateArr['name']) { + if ($model->checkNameExist($catalog['project_id'], $updateArr['name'])) { + $this->ajaxFailed('提示', '分类名已存在'); + } + } + $ret = $model->updateById($id, $updateArr); + if ($ret[0]) { + $activityModel = new ActivityModel(); + $activityInfo = []; + $activityInfo['action'] = '更新了分类'; + $activityInfo['type'] = ActivityModel::TYPE_PROJECT; + $activityInfo['obj_id'] = $id; + $activityInfo['title'] = $updateArr['name']; + $activityModel->insertItem($currentUserId, $catalog['project_id'], $activityInfo); + + //写入操作日志 + $logData = []; + $logData['user_name'] = $this->auth->getUser()['username']; + $logData['real_name'] = $this->auth->getUser()['display_name']; + $logData['obj_id'] = 0; + $logData['module'] = LogOperatingLogic::MODULE_NAME_PROJECT; + $logData['page'] = $_SERVER['REQUEST_URI']; + $logData['action'] = LogOperatingLogic::ACT_EDIT; + $logData['remark'] = '修改分类'; + $logData['pre_data'] = $catalog; + $logData['cur_data'] = $updateArr; + LogOperatingLogic::add($currentUserId, $catalog['project_id'], $logData); + + $this->ajaxSuccess('提示','修改成功'); + } else { + $this->ajaxFailed('提示','更新失败'); + } + } + + + /** + * @param $project_id + * @param $label_id + * @throws \Exception + */ + public function delete($project_id, $label_id) + { + $id = null; + if (isset($_POST['id'])) { + $id = (int)$_POST['id']; + } + if (!$id) { + $this->ajaxFailed('参数错误', 'id不能为空'); + } + $id = intval($id); + $model = new ProjectCatalogLabelModel(); + $info = $model->getById($id); + if ($info['project_id'] != $this->projectId) { + $this->ajaxFailed('提示', '参数错误,非当前项目的分类无法删除'); + } + $model->deleteItem($id); + $currentUid = $this->getCurrentUid(); + $activityModel = new ActivityModel(); + $activityInfo = []; + $activityInfo['action'] = '删除了分类'; + $activityInfo['type'] = ActivityModel::TYPE_PROJECT; + $activityInfo['obj_id'] = $label_id; + $activityInfo['title'] = $info['name']; + $activityModel->insertItem($currentUid, $project_id, $activityInfo); + + + $callFunc = function ($value) { + return '已删除'; + }; + $info2 = array_map($callFunc, $info); + //写入操作日志 + $logData = []; + $logData['user_name'] = $this->auth->getUser()['username']; + $logData['real_name'] = $this->auth->getUser()['display_name']; + $logData['obj_id'] = 0; + $logData['module'] = LogOperatingLogic::MODULE_NAME_PROJECT; + $logData['page'] = $_SERVER['REQUEST_URI']; + $logData['action'] = LogOperatingLogic::ACT_DELETE; + $logData['remark'] = '删除分类'; + $logData['pre_data'] = $info; + $logData['cur_data'] = $info2; + LogOperatingLogic::add($currentUid, $project_id, $logData); + + $this->ajaxSuccess('提示','操作成功'); + } +} diff --git a/app/plugin/activity/event/IssueSubscriber.php b/app/plugin/activity/event/IssueSubscriber.php new file mode 100644 index 000000000..418029b93 --- /dev/null +++ b/app/plugin/activity/event/IssueSubscriber.php @@ -0,0 +1,51 @@ + [ + ['onKernelResponsePre', 10], + ['onKernelResponsePost', -10], + ], + IssuePlacedEvent::NAME => [ + ['onIssueCreateBefore', 1], + ['onIssueCreateAfter', 2], + ] + ]; + } + + public function onKernelResponsePre(Event $event) + { + // ... + var_dump('onKernelResponsePre'); + } + + public function onKernelResponsePost(Event $event) + { + // ... + var_dump('onKernelResponsePost'); + } + + public function onIssueCreateBefore(IssuePlacedEvent $event) + { + // ... + var_dump($event); + var_dump('onIssueCreateBefore'); + } + + public function onIssueCreateAfter(IssuePlacedEvent $event) + { + // ... + var_dump($event); + var_dump('onIssueCreateAfter'); + } +} \ No newline at end of file diff --git a/app/plugin/activity/index.php b/app/plugin/activity/index.php new file mode 100644 index 000000000..a4abe2daf --- /dev/null +++ b/app/plugin/activity/index.php @@ -0,0 +1,2 @@ + + + + + 活动日志插件的首页 + + + + + + \ No newline at end of file diff --git a/app/plugin/notify/.gitignore b/app/plugin/notify/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/app/plugin/notify/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/app/plugin/operate/.gitignore b/app/plugin/operate/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/app/plugin/operate/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/app/plugin/webhook/.gitignore b/app/plugin/webhook/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/app/plugin/webhook/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/app/test/bootstrap.php b/app/test/bootstrap.php index 0a780cecc..fde4947af 100644 --- a/app/test/bootstrap.php +++ b/app/test/bootstrap.php @@ -11,7 +11,6 @@ require_once TEST_PATH . '../globals.php'; require_once TEST_PATH . 'BaseTestCase.php'; require_once TEST_PATH . 'BaseAppTestCase.php'; -require_once PRE_APP_PATH . 'lib/MyPdo.php'; //require_once TEST_PATH . '../../../hornet-framework/src/framework/bootstrap.php'; spl_autoload_register('testAutoload'); diff --git a/app/test/unit/lib/.gitignore b/app/test/unit/lib/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/app/test/unit/lib/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/app/test/unit/lib/TestMyPdo.php b/app/test/unit/lib/TestMyPdo.php deleted file mode 100644 index 2d030e198..000000000 --- a/app/test/unit/lib/TestMyPdo.php +++ /dev/null @@ -1,30 +0,0 @@ -close(); } diff --git a/app/view/exception.php b/app/view/exception.php index 424b581b3..e3520fcac 100644 --- a/app/view/exception.php +++ b/app/view/exception.php @@ -43,7 +43,7 @@
Sql: - ' . print_r(\main\lib\MyPdo::$sqlLogs, true) . ''; ?> + ' . print_r(\main\app\model\DbModel::$sqlLogs, true) . ''; ?>
TRACE: diff --git a/app/view/plugin/.gitignore b/app/view/plugin/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/app/view/plugin/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/config.development.yml b/config.development.yml index 536da3406..960331bce 100644 --- a/config.development.yml +++ b/config.development.yml @@ -14,7 +14,7 @@ xhprof: rate: 1 cache: - enable: false + enable: true expire: 864000 gc_rate: 10 server: diff --git a/env.ini-example b/env.ini-example index efe12c21e..830ed76f0 100644 --- a/env.ini-example +++ b/env.ini-example @@ -1,7 +1,11 @@ + ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; envionment variables list ; 线上部署、测试环境、开发环境的相关配置 ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; 项目状态:deploy | development -APP_STATUS = deploy \ No newline at end of file +; 项目状态:deploy | development | travis | test +APP_STATUS = deploy + +; 是否缓存 config.yml 文件的读取 +CACHE_YAML = 0 \ No newline at end of file diff --git a/lib/MyPdo.php b/lib/MyPdo.php deleted file mode 100644 index b6c13a115..000000000 --- a/lib/MyPdo.php +++ /dev/null @@ -1,745 +0,0 @@ -config = $dbConfig; - $this->enableSqlLog = $enableSqlLog; - - // 判断服务器环境是否支持PDO - if (!class_exists('PDO')) { - throw new \PDOException("当前服务器环境不支持PDO,访问数据库失败。", 3000); - } - - // 判断是传入了正确的数据库配置参数 - if (empty($this->config['host'])) { - throw new \PDOException("没有定义数据库配置,请在配置文件中配置。", 3001); - } - - $names = (isset($this->config['charset']) && !empty($this->config['charset'])) ? $this->config['charset'] : 'utf8'; - - // 生成数据库配置 - $driver = $this->config['driver']; - $host = $this->config['host']; - $port = $this->config['port']; - $dbName = $this->config['db_name']; - $this->config['dsn'] = sprintf("%s:host=%s;port=%s;dbname=%s", $driver, $host, $port, $dbName); - $this->config['params'] = [ - \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES {$names}", - \PDO::ATTR_CASE => \PDO::CASE_NATURAL, - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, - \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, - \PDO::ATTR_PERSISTENT => $persistent, - \PDO::ATTR_TIMEOUT => isset($this->config['timeout']) ? $this->config['timeout'] : 10 - ]; - } - - /** - * 数据库预连接,保存到$pdo变量中 - * - * @throws \PDOException - * @access public - */ - public function connect() - { - if (!isset($this->pdo)) { - try { - $dsn = $this->config['dsn']; - $user = $this->config['user']; - $password = $this->config['password']; - $params = $this->config['params']; - $this->pdo = new \PDO($dsn, $user, $password, $params); - $sqlMode = "SET SQL_MODE='IGNORE_SPACE,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'"; - $this->pdo->query($sqlMode); - } catch (\PDOException $e) { - $message = $e->getMessage(); - $message = mb_convert_encoding($message, 'UTF-8', 'GBK'); - //Mylog::error("数据库连接失败" . $message, 'db/error'); - throw new \PDOException($message, 3002); - } - - if (!$this->pdo) { - throw new \PDOException('PDO CONNECT ERROR', 3003); - } - } - return $this->pdo; - } - - - /** - * 执行查询性的SQL查询,准备pdoStatement - * @param string $sql - * @param array $params - * @return bool - */ - public function query($sql, $params = array()) - { - return $this->exec($sql, $params); - } - - - /** - * 执行查询性的SQL查询,准备pdoStatement - * @param string $sql 要执行的SQL指令。 - * @param array $params 参数化的数据 - * @return bool 返回true或者false - */ - public function exec($sql = '', $params = array()) - { - if (empty($sql)) { - throw new \PDOException('Sql is empty', 3002); - } - $this->connect(); - - if (empty($this->pdo)) { - throw new \PDOException('Server connect failed', 3005); - } - - $this->queryStr = $sql; - - // 释放前次的查询结果 - if (!empty($this->pdoStatement)) { - $this->pdoStatement = null; - } - try { - $this->pdoStatement = $this->pdo->prepare($sql); - if (empty($this->pdoStatement)) { - return false; - } - - if (is_array($params) && !empty($params)) { - foreach ($params as $k => &$v) { - $this->pdoStatement->bindParam($k, $v); - } - } - - $log_index = count(self::$sqlLogs); - if ($this->enableSqlLog) { - self::$sqlLogs[$log_index]['sql'] = $sql; - self::$sqlLogs[$log_index]['time'] = time(); - self::$sqlLogs[$log_index]['result'] = ''; - } - - $start_time = array_sum(explode(' ', microtime())); - $result = $this->pdoStatement->execute(); - $end_time = array_sum(explode(' ', microtime())); - $diff = $end_time - $start_time; - SlowLog::getInstance()->write($sql, $diff); - - if ($this->enableSqlLog) { - self::$sqlLogs[$log_index]['result'] = boolval($result); - } - } catch (\PDOException $e) { - // @todo 记录日志 - if (isset($_SERVER['argv'])) { - var_dump($sql, $params, $e->getMessage(), $e->getTrace()); - } - // print_r($e->getTrace()); - throw new \PDOException($e->getMessage() . "\n" . json_encode($e->getTrace(), true), (int)$e->getCode()); - } - return $result; - } - - /** - * 执行查询性的SQL查询,准备pdoStatement - * @param string $sql 要执行的SQL指令。 - * @param array $params 参数化的数据 - * @return bool 返回true或者false - */ - public function execPrepare($sql = '', $params = array()) - { - if (empty($sql)) { - throw new \PDOException('要执行的SQL语句为空。', 3002); - } - $this->connect(); - - if (empty($this->pdo)) { - throw new \PDOException('无法连接数据库。', 3005); - } - - $this->queryStr = $sql; - - // 释放前次的查询结果 - if (!empty($this->pdoStatement)) { - $this->pdoStatement = null; - } - - $this->pdoStatement = $this->pdo->prepare($sql); - if (empty($this->pdoStatement)) { - return false; - } - - $log_index = count(self::$sqlLogs); - if ($this->enableSqlLog) { - self::$sqlLogs[$log_index]['sql'] = $sql; - self::$sqlLogs[$log_index]['time'] = time(); - self::$sqlLogs[$log_index]['result'] = ''; - } - - $start_time = array_sum(explode(' ', microtime())); - $result = $this->pdoStatement->execute($params); - $end_time = array_sum(explode(' ', microtime())); - $diff = $end_time - $start_time; - SlowLog::getInstance()->write($sql, $diff); - - - if ($this->enableSqlLog) { - self::$sqlLogs[$log_index]['result'] = boolval($result); - } - return $result; - } - - /** - * 获得所有的查询数据 - * @param string $sql 要执行的SQL指令,查询得到的数据集,失败返回false - * @param array $params 是否以主键为下标。使用主键下标,可以返回以数据库主键的值为下标的二维数组 - * @param bool $primaryKey - * @return array - */ - public function getRows($sql, $params = array(), $primaryKey = false) - { - $this->query($sql, $params); - if ($primaryKey) { - $result = $this->pdoStatement->fetchAll(\PDO::FETCH_GROUP | \PDO::FETCH_ASSOC); - $result = array_map('reset', $result); - } else { - $result = $this->pdoStatement->fetchAll(\PDO::FETCH_ASSOC); - } - $this->pdoStatement = null; - return $result; - } - - /** - * 获得所有的查询数据 - * @param string $sql 要执行的SQL指令,查询得到的数据集,失败返回false - * @param array $params 是否以主键为下标。使用主键下标,可以返回以数据库主键的值为下标的二维数组 - * @param bool $primaryKey - * @return array - */ - public function getLists($sql, $params = array(), $primaryKey = false) - { - $this->execPrepare($sql, $params); - if ($primaryKey) { - $result = $this->pdoStatement->fetchAll(\PDO::FETCH_GROUP | \PDO::FETCH_ASSOC); - $result = array_map('reset', $result); - } else { - $result = $this->pdoStatement->fetchAll(\PDO::FETCH_ASSOC); - } - $this->pdoStatement = null; - return $result; - } - - - /** - * 获得一条查询数据 - * @param string $sql 要执行的SQL指令 - * @param array $params - * @return array 一条查询数据,失败返回空数组。 - */ - public function getRow($sql, $params = array()) - { - $this->query($sql, $params); - $result = $this->pdoStatement->fetch(\PDO::FETCH_ASSOC, \PDO::FETCH_ORI_NEXT); - $this->pdoStatement = null; - if ($result === false) { - return []; - } - return $result; - } - - /** - * 获得一条查询结果一列的一个值,没有数据则返回false - * @param string $sql 要执行的SQL指令 - * @param array $params 参数化数组 - * @return mixed 获得一条查询结果一列的一个值,没有数据则返回false - */ - public function getOne($sql, $params = array()) - { - $this->query($sql, $params); - $result = $this->pdoStatement->fetchColumn(); - $this->pdoStatement = null; - return $result; - } - - - /** - * @param $table - * @param $primaryKey - * @param $conditions - * @return int - */ - public function getCount($table, $primaryKey, $conditions) - { - $conditions = $this->buildWhereSqlByParam($conditions); - $field = "count({$primaryKey}) as cc"; - $sql = 'SELECT ' . $field . ' FROM ' . $table . $conditions["_where"]; - return intval($this->getOne($sql, $conditions["_bindParams"])); - } - - - /** - * 获取最近一次查询的sql语句 - * @return string - * @access public - */ - public function getLastSql() - { - return $this->queryStr; - } - - /** - * 获取最后插入的ID - * - * @return integer 最后插入时的数据ID - */ - public function getLastInsId() - { - $this->connect(); - return $this->pdo->lastInsertId(); - } - - - /** - * 构造插入的SQL语句目的利于缓存,能够同步缓存的数据 - * 该函数用于缓存中数据是一维数组的情况 - * @param $table - * @param $info - * @return array - */ - public function replace($table, $info) - { - if (empty($table) || empty($info)) { - return [false, 'replace data is null']; - } - $sql = " Replace into {$table} Set "; - $sql .= $this->parsePrepareSql($info); - //echo $sql; - return $this->execInsertPrepareSql($sql, $info); - } - - /** - * 参数化插入数据 - * @param string $table - * @param array $row - * @return array [ boolean,number|string] - */ - public function insert($table, $row) - { - if (empty($table) || empty($row)) { - return [false, 'insert data is null']; - } - $sql = "Insert into {$table} Set "; - $sql .= $this->parsePrepareSql($row); - - return $this->execInsertPrepareSql($sql, $row); - } - - /** - * 插入一行数据(重复则忽略) - * @param string $table 数据表名 - * @param array $row 插入数据的键值对数组 - * @return array [ boolean,number|string] - */ - public function insertIgnore($table, $row) - { - if (empty($table) || empty($row)) { - return [false, 'insert data is null']; - } - $sql = "INSERT IGNORE INTO {$table} SET "; - $sql .= $this->parsePrepareSql($row); - //echo $sql; - return $this->execInsertPrepareSql($sql, $row); - } - - /** - * 通过条件删除 - * @param string $table - * @param array $conditions - * @return int - */ - public function delete($table, $conditions) - { - $conditions = $this->buildWhereSqlByParam($conditions); - $sql = " Delete from $table " . $conditions["_where"]; - $ret = $this->exec($sql, $conditions["_bindParams"]); - // var_dump($ret); - return intval($ret); - } - - public function truncate($table) - { - $sql = " Truncate $table"; - $ret = $this->exec($sql); - // var_dump($ret); - return intval($ret); - } - - - /** - * 执行更新操作 - * @param $table - * @param $row - * @param $conditions - * @return array [bool, int|string] - */ - public function update($table, $row, $conditions) - { - if (!is_array($conditions)) { - return [false, 0]; - } - $conditions = $this->buildWhereAndSqlByQuestion($conditions); - $sql = " UPDATE {$table} SET "; - $sql .= $this->parsePrepareSql($row, true); - $sql .= ' ' . $conditions["_where"]; - return $this->execUpdatePrepareSql($sql, $row, $conditions['_bindParams']); - } - - - /** - * 参数化的方式执行update语句 - * @param string $sql - * @param array $row - * @param array $bind_params - * @return array [boolean ,number|string] 返回包含两个元素的数组,两个数组元素分别为执行是否成功和影响行数 - */ - private function execUpdatePrepareSql($sql, $row, $bind_params) - { - $this->connect(); - $sth = $this->pdo->prepare($sql); - $i = 0; - if (!empty($row)) { - foreach ($row as &$v) { - $i++; - $sth->bindValue($i, $v); - } - } - if (!empty($bind_params)) { - foreach ($bind_params as &$vw) { - $i++; - $sth->bindValue($i, $vw); - } - } - try { - $start_time = array_sum(explode(' ', microtime())); - $ret = $sth->execute(); - $end_time = array_sum(explode(' ', microtime())); - $diff = $end_time - $start_time; - SlowLog::getInstance()->write($sql, $diff); - } catch (\PDOException $e) { - return [false, $e->getMessage()]; - } - - return [$ret, $sth->rowCount()]; - } - - - /** - * 构建预编译的UPDATE SQL语句 - * @param array $arr - * @param bool $is_index - * @return string - */ - public function parsePrepareSql($arr, $is_index = false) - { - $setsStr = ''; - if (is_array($arr) && !empty($arr)) { - foreach ($arr as $key => $val) { - $key = trimStr(str_replace('`', '', $key)); - - $bind_key = ":{$key}"; - if ($is_index) { - $bind_key = '?'; - } - - $_key = "`{$key}`"; - $setsStr .= "$_key={$bind_key},"; - } - $setsStr = substr($setsStr, 0, -1); - } elseif (is_string($arr)) { - $setsStr = $arr; - } - return $setsStr; - } - - - /** - * 返回插入成功后的自增id - * @return int - */ - public function lastInsertId() - { - return $this->getLastInsId(); - } - - /** - * 开始一个事务 - * - * @access public - */ - public function beginTransaction() - { - $this->connect(); - return $this->pdo->beginTransaction(); - } - - /** - * 回滚一个事务 - * - * @access public - */ - public function rollBack() - { - $this->connect(); - return $this->pdo->rollBack(); - } - - /** - * 提交一个事务 - * - * @access public - */ - public function commit() - { - $this->connect(); - return $this->pdo->commit(); - } - - /** - * 关闭数据库PDO连接 - * - * @access public - */ - public function close() - { - $this->pdo = null; - } - - - /** - * 参数化的方式执行insert语句 - * @param string $sql - * @param array $row - * @return array [ boolean,number|string] - */ - private function execInsertPrepareSql($sql, $row) - { - - $this->connect(); - $sth = $this->pdo->prepare($sql); - if (!empty($row) && is_array($row)) { - foreach ($row as $k => &$v) { - $sth->bindValue($k, $v); - } - } - try { - $start_time = array_sum(explode(' ', microtime())); - $ret = $sth->execute(); - $end_time = array_sum(explode(' ', microtime())); - $diff = $end_time - $start_time; - SlowLog::getInstance()->write($sql, $diff); - } catch (\PDOException $e) { - //echo $e->getMessage(); - return [false, $e->getMessage()]; - } - - return [$ret, $this->lastInsertId()]; - } - - - /** - * 参数化条件语句 - * @param $conditions - * @return array - */ - public function buildWhereSqlByParam($conditions = array()) - { - $result = array("_where" => " ", "_bindParams" => array()); - if (is_array($conditions) && !empty($conditions)) { - $sql = null; - $join = array(); - if (isset($conditions[0]) && $sql = $conditions[0]) { - unset($conditions[0]); - } - foreach ($conditions as $key => $condition) { - $bindKey = $this->processBindKey($key); - if (substr($key, 0, 1) != ":") { - unset($conditions[$key]); - $conditions[$bindKey] = $condition; - } - $join[] = $this->backquoteColumn($key) . ' = ' . $bindKey; - } - if (!$sql) { - $sql = join(" AND ", $join); - } - - $result["_where"] = " WHERE " . $sql; - $result["_bindParams"] = $conditions; - } - return $result; - } - - /** - * 参数化条件语句 - * @param array $conditions - * @return array - */ - private function buildWhereAndSqlByQuestion($conditions = array()) - { - $result = array("_where" => " ", "_bindParams" => array()); - if (is_array($conditions) && !empty($conditions)) { - $sql = null; - $join = array(); - if (isset($conditions[0]) && $sql = $conditions[0]) { - unset($conditions[0]); - } - foreach ($conditions as $key => $value) { - $join[] = $this->backquoteColumn($key) . ' = ?'; - } - if (!$sql) { - $sql = join(" AND ", $join); - } - - $result["_where"] = " WHERE " . $sql; - $result["_bindParams"] = $conditions; - } - return $result; - } - - - /** - * 处理参数绑定的健,避免别名引用字段时错误. - * @param $key - * @return string 如果是别名引用字段,例如main_user.uid将被替换成:main_user_uid,其中的点号被替换成下换线 ,因为PDO在执行绑定参数后SQL时报无效参数错误. - */ - private function processBindKey($key) - { - if (strpos($key, '.') !== false) { - $key = str_replace('.', '_', $key); - } - return ':' . $key; - } - - - /** - * 参数绑定时反引查询字段,避免安全字符事项. - * @param $column - * @return string 如果是别名引用字段,将把别名和字段名分别用反引号括起来,否则在执行SQL时将报字段名无效错误. - */ - private function backquoteColumn($column) - { - // 考虑到点号只会在别名引用时出现一次,所以直接用字符串替换中间的点号,而不用判断是否存在点号再拆分拼凑方式处理 - return '`' . str_replace('.', '`.`', $column) . '`'; - } - - - /** - * 读取一个表的字段数据 - * @param string $table 表名,默认本实例的table属性 - * @param array $excepts 要剔除的字段名列表 - * @return array 字段名数组 - */ - public function getFields($table = '', $excepts = array()) - { - $table = $table ?: $this->getTable(); - $sql = "describe `$table`"; - $fields = $this->getRows($sql, 'Field'); - $fields = array_keys($fields); - - if ($excepts) { - foreach ($excepts as $value) { - $key = array_search($value, $fields); - if ($key) { - array_splice($fields, $key, 1); - } - } - } - - return $fields; - } - - /** - * 获取一个表的全部字段信息 - * @param $table - * @return array - */ - public function getFullFields($table) - { - $sql = "show full fields from {$table} "; - $fields = $this->getRows($sql, [], true); - return $fields; - } - -} diff --git a/upgrade/database/up-v2.1-to-v2.2.sql b/upgrade/database/up-v2.1-to-v2.2.sql new file mode 100644 index 000000000..2d11d7c4b --- /dev/null +++ b/upgrade/database/up-v2.1-to-v2.2.sql @@ -0,0 +1,7 @@ + +ALTER TABLE `issue_main` ADD INDEX(`project_id`); + +ALTER TABLE `issue_label_data` ADD INDEX(`issue_id`); +ALTER TABLE `issue_label_data` ADD INDEX(`label_id`); +ALTER TABLE `field_main` ADD INDEX(`is_system`); +ALTER TABLE `field_custom_value` ADD INDEX( `issue_id`, `custom_field_id`); \ No newline at end of file