diff --git a/config/zentaopms.php b/config/zentaopms.php index a5f8f0a6aa..78af3313ee 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -120,6 +120,7 @@ define('TABLE_LOG', '`' . $config->db->prefix . 'log`'); define('TABLE_SCORE', '`' . $config->db->prefix . 'score`'); define('TABLE_NOTIFY', '`' . $config->db->prefix . 'notify`'); define('TABLE_TRANSLATION', '`' . $config->db->prefix . 'translation`'); +define('TABLE_DINGUSERID', '`' . $config->db->prefix . 'dinguserid`'); if(!defined('TABLE_LANG')) define('TABLE_LANG', '`' . $config->db->prefix . 'lang`'); $config->objectTables['product'] = TABLE_PRODUCT; diff --git a/db/update11.6.5.sql b/db/update11.6.5.sql index d9b0c17808..e0c5f82d45 100644 --- a/db/update11.6.5.sql +++ b/db/update11.6.5.sql @@ -1 +1,7 @@ ALTER TABLE `zt_dept` CHANGE `order` `order` smallint(4) unsigned NOT NULL DEFAULT '0' AFTER `grade`; +CREATE TABLE `zt_dinguserid` ( + `webhook` mediumint(8) unsigned NOT NULL, + `account` varchar(30) NOT NULL, + `userid` varchar(255) NOT NULL, + UNIQUE KEY `webhook_account_userid` (`webhook`,`account`,`userid`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/lib/dingapi/dingapi.class.php b/lib/dingapi/dingapi.class.php new file mode 100644 index 0000000000..7c548a16e3 --- /dev/null +++ b/lib/dingapi/dingapi.class.php @@ -0,0 +1,114 @@ +appKey = $appKey; + $this->appSecret = $appSecret; + $this->agentId = $agentId; + if($apiUrl) $this->apiUrl = rtrim($apiUrl, '/') . '/'; + + if(!$this->getToken()) return array('result' => 'fail', 'message' => $this->errors); + } + + public function getToken() + { + if($this->token and (time() - $this->expires) >= 0) return $this->token; + + $response = $this->queryAPI($this->apiUrl . "gettoken?appkey={$this->appKey}&appsecret={$this->appSecret}"); + if($this->isError()) return false; + + $this->token = $response->access_token; + $this->expires = time() + $response->expires_in; + return $this->token; + } + + public function getAllUsers() + { + $depts = $this->getAllDepts(); + if($this->isError()) return array('result' => 'fail', 'message' => $this->errors); + + $users = array(); + foreach($depts as $deptID => $deptName) + { + $response = $this->queryAPI($this->apiUrl . "user/simplelist?access_token={$this->token}&department_id={$deptID}"); + if($this->isError()) return array('result' => 'fail', 'message' => $this->errors); + + foreach($response->userlist as $user) $users[$user->name] = $user->userid; + } + + return array('result' => 'success', 'data' => $users); + } + + public function getAllDepts() + { + $response = $this->queryAPI($this->apiUrl . "department/list?access_token={$this->token}"); + if($this->isError()) return false; + + $deptPairs = array(); + foreach($response->department as $dept) $deptPairs[$dept->id] = $dept->name; + return $deptPairs; + } + + public function send($userList, $message) + { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + curl_setopt($curl, CURLOPT_USERAGENT, 'Sae T OAuth2 v0.1'); + curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30); + curl_setopt($curl, CURLOPT_TIMEOUT, 30); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); + 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_URL, $this->apiUrl . 'topapi/message/corpconversation/asyncsend_v2?access_token=' . $this->token); + curl_setopt($curl, CURLINFO_HEADER_OUT, TRUE); + + $postData = array(); + $postData['agent_id'] = $this->agentId; + $postData['userid_list'] = $userList; + $postData['msg'] = $message; + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postData); + + $response = curl_exec($curl); + curl_close($curl); + + $response = json_encode($response); + if(isset($response->errcode) and $response->errcode == 0) return array('result' => 'success'); + + $this->errors[$response->errcode] = "Errcode:{$response->errcode}, Errmsg:{$response->errmsg}"; + return array('result' => 'fail', 'message' => $this->errors); + } + + public function queryAPI($url) + { + $response = json_decode(file_get_contents($url)); + if(isset($response->errcode) and $response->errcode == 0) return $response; + + $this->errors[$response->errcode] = "Errcode:{$response->errcode}, Errmsg:{$response->errmsg}"; + return false; + } + + public function isError() + { + return !empty($this->errors); + } + + public function getErrors() + { + $errors = $this->errors; + $this->errors = array(); + + return $errors; + } +} diff --git a/module/common/model.php b/module/common/model.php index 90dc66d246..9d94fb82b9 100644 --- a/module/common/model.php +++ b/module/common/model.php @@ -28,9 +28,9 @@ class commonModel extends model $this->setUser(); $this->loadConfigFromDB(); $this->app->setTimezone(); - if((strpos($this->config->global->version, 'pro') !== false && version_compare($this->config->global->version, 'pro2.3.beta', '>')) - || (strpos($this->config->global->version, 'biz') !== false) - || version_compare($this->config->global->version, '4.3.beta', '>')) + if((strpos($this->config->version, 'pro') !== false && version_compare($this->config->version, 'pro2.3.beta', '>')) + || (strpos($this->config->version, 'biz') !== false) + || version_compare($this->config->version, '4.3.beta', '>')) { $this->loadCustomFromDB(); } diff --git a/module/upgrade/control.php b/module/upgrade/control.php index e8302b1ae7..75a5c4974d 100644 --- a/module/upgrade/control.php +++ b/module/upgrade/control.php @@ -201,6 +201,8 @@ class upgrade extends control if(empty($extensions)) $this->locate(inlink('selectVersion')); /* Check network. */ + if(!extension_loaded('curl')) $this->locate(inlink('selectVersion')); + $curl = curl_init(); curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); diff --git a/module/webhook/control.php b/module/webhook/control.php index bbcbf75066..22d66491f0 100644 --- a/module/webhook/control.php +++ b/module/webhook/control.php @@ -137,6 +137,73 @@ class webhook extends control $this->display(); } + /** + * Bind dingtalk userid. + * + * @param int $id + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function bind($id, $recTotal = 0, $recPerPage = 50, $pageID = 1) + { + if($_POST) + { + $this->webhook->bind($id); + if(dao::isError()) die(js::error(dao::getError())); + + die(js::reload('parent')); + } + + $webhook = $this->webhook->getById($id); + if($webhook->type != 'dingapi') + { + echo js::alert($this->lang->webhook->note->bind); + die(js::locate($this->createLink('webhook', 'browse'))); + } + $webhook->secret = json_decode($webhook->secret); + + $this->app->loadClass('dingapi', true); + $dingapi = new dingapi($webhook->secret->appKey, $webhook->secret->appSecret, $webhook->secret->agentId); + $response = $dingapi->getAllUsers(); + if($response['result'] == 'fail') + { + echo js::error($response->message); + die(js::locate($this->createLink('webhook', 'browse'))); + } + + $dingUsers = $response['data']; + $bindedPairs = $this->webhook->getBindUsers($id); + $useridPairs = array('' => ''); + foreach($dingUsers as $name => $userid) $useridPairs[$userid] = $name; + + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + $users = $this->loadModel('user')->getByQuery($query = '', $pager); + + $unbindUsers = array(); + $bindedUsers = array(); + foreach($users as $user) + { + if(isset($bindedPairs[$user->account])) $bindedUsers[$user->account] = $user; + if(!isset($bindedPairs[$user->account])) $unbindUsers[$user->account] = $user; + } + $users = $unbindUsers + $bindedUsers; + + $this->view->title = $this->lang->webhook->bind; + $this->view->position[] = html::a($this->createLink('webhook', 'browse'), $this->lang->webhook->common); + $this->view->position[] = $this->lang->webhook->bind; + + $this->view->dingUsers = $dingUsers; + $this->view->useridPairs = $useridPairs; + $this->view->users = $users; + $this->view->pager = $pager; + $this->view->bindedUsers = $bindedPairs; + $this->display(); + } + /** * Send data by async. * diff --git a/module/webhook/lang/zh-cn.php b/module/webhook/lang/zh-cn.php index 8ccc9a71c1..e4035b76ac 100644 --- a/module/webhook/lang/zh-cn.php +++ b/module/webhook/lang/zh-cn.php @@ -4,6 +4,7 @@ $lang->webhook->list = 'Webhook列表'; $lang->webhook->api = '接口'; $lang->webhook->entry = '应用'; $lang->webhook->log = '日志'; +$lang->webhook->bind = '绑定用户'; $lang->webhook->assigned = '指派给'; $lang->webhook->setting = '设置'; @@ -41,8 +42,14 @@ $lang->webhook->typeList['default'] = '其他'; $lang->webhook->sendTypeList['sync'] = '同步'; $lang->webhook->sendTypeList['async'] = '异步'; -$lang->webhook->dingAppKey = '钉钉AppKey'; -$lang->webhook->dingAppSecret = '钉钉AppSecret'; +$lang->webhook->dingAgentId = '钉钉AgentId'; +$lang->webhook->dingAppKey = '钉钉AppKey'; +$lang->webhook->dingAppSecret = '钉钉AppSecret'; +$lang->webhook->dingUserid = '钉钉Userid'; +$lang->webhook->dingBindStatus = '钉钉绑定状态'; + +$lang->webhook->dingBindStatusList['0'] = '未绑定'; +$lang->webhook->dingBindStatusList['1'] = '已绑定'; $lang->webhook->paramsList['objectType'] = '对象类型'; $lang->webhook->paramsList['objectID'] = '对象ID'; @@ -60,6 +67,7 @@ $lang->webhook->trimWords = '了'; $lang->webhook->note = new stdClass(); $lang->webhook->note->async = '异步需要打开计划任务'; +$lang->webhook->note->bind = '只有钉钉工作通知类型才需要绑定用户。'; $lang->webhook->note->product = "此项为空时所有{$lang->productCommon}的动作都会触发钩子,否则只有关联{$lang->productCommon}的动作才会触发。"; $lang->webhook->note->project = "此项为空时所有{$lang->projectCommon}的动作都会触发钩子,否则只有关联{$lang->projectCommon}的动作才会触发。"; diff --git a/module/webhook/model.php b/module/webhook/model.php index f2bc8b9942..dfb686c4af 100644 --- a/module/webhook/model.php +++ b/module/webhook/model.php @@ -113,6 +113,19 @@ class webhookModel extends model return $this->dao->select('*')->from(TABLE_NOTIFY)->where('status')->eq('wait')->andWhere('objectType')->eq('webhook')->orderBy('id')->fetchAll('id'); } + /** + * Get bind users. + * + * @param int $webhookID + * @access public + * @return array + */ + public function getBindUsers($webhookID) + { + return $this->dao->select('*')->from(TABLE_DINGUSERID)->where('webhook')->eq($webhookID) + ->fetchPairs('account', 'userid'); + } + /** * Create a webhook. * @@ -133,14 +146,20 @@ class webhookModel extends model if($webhook->type == 'dingapi') { $webhook->secret = array(); + $webhook->secret['agentId'] = $webhook->agentId; $webhook->secret['appKey'] = $webhook->appKey; $webhook->secret['appSecret'] = $webhook->appSecret; + if(empty($webhook->agentId)) dao::$errors['agentId'] = sprintf($this->lang->error->notempty, $this->lang->webhook->dingAgentId); + if(empty($webhook->appKey)) dao::$errors['appKey'] = sprintf($this->lang->error->notempty, $this->lang->webhook->dingAppKey); + if(empty($webhook->appSecret)) dao::$errors['appSecret'] = sprintf($this->lang->error->notempty, $this->lang->webhook->dingAppSecret); + if(dao::isError()) return false; + $webhook->secret = json_encode($webhook->secret); $webhook->url = $this->config->webhook->dingapiUrl; } - $this->dao->insert(TABLE_WEBHOOK)->data($webhook, 'appKey,appSecret') + $this->dao->insert(TABLE_WEBHOOK)->data($webhook, 'agentId,appKey,appSecret') ->batchCheck($this->config->webhook->create->requiredFields, 'notempty') ->autoCheck() ->exec(); @@ -170,13 +189,19 @@ class webhookModel extends model if($webhook->type == 'dingapi') { $webhook->secret = array(); + $webhook->secret['agentId'] = $webhook->agentId; $webhook->secret['appKey'] = $webhook->appKey; $webhook->secret['appSecret'] = $webhook->appSecret; + if(empty($webhook->agentId)) dao::$errors['agentId'] = sprintf($this->lang->error->notempty, $this->lang->webhook->dingAgentId); + if(empty($webhook->appKey)) dao::$errors['appKey'] = sprintf($this->lang->error->notempty, $this->lang->webhook->dingAppKey); + if(empty($webhook->appSecret)) dao::$errors['appSecret'] = sprintf($this->lang->error->notempty, $this->lang->webhook->dingAppSecret); + if(dao::isError()) return false; + $webhook->secret = json_encode($webhook->secret); } - $this->dao->update(TABLE_WEBHOOK)->data($webhook, 'appKey,appSecret') + $this->dao->update(TABLE_WEBHOOK)->data($webhook, 'agentId,appKey,appSecret') ->batchCheck($this->config->webhook->edit->requiredFields, 'notempty') ->autoCheck() ->where('id')->eq($id) @@ -184,6 +209,29 @@ class webhookModel extends model return !dao::isError(); } + /** + * Bind ding userid. + * + * @param int $webhookID + * @access public + * @return bool + */ + public function bind($webhookID) + { + $data = fixer::input('post')->get(); + foreach($data->userid as $account => $userid) + { + if(empty($userid)) continue; + + $dingUser = new stdclass(); + $dingUser->webhook = $webhookID; + $dingUser->account = $account; + $dingUser->userid = $userid; + $this->dao->replace(TABLE_DINGUSERID)->data($dingUser)->exec(); + } + return !dao::isError(); + } + /** * Send data. * @@ -288,7 +336,7 @@ class webhookModel extends model foreach(explode(',', $webhook->params) as $param) $data->$param = $action->$param; } - return helper::jsonEncode($data); + return json_encode($data); } /** diff --git a/module/webhook/view/bind.html.php b/module/webhook/view/bind.html.php new file mode 100644 index 0000000000..49f6fb59c8 --- /dev/null +++ b/module/webhook/view/bind.html.php @@ -0,0 +1,63 @@ + +