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 @@ + + +
+ +