diff --git a/config/zentaopms.php b/config/zentaopms.php index 8933ef5f0f..3e192df98c 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -104,6 +104,8 @@ define('TABLE_TESTREPORT', '`' . $config->db->prefix . 'testreport`'); define('TABLE_ENTRY', '`' . $config->db->prefix . 'entry`'); define('TABLE_WEBHOOK', '`' . $config->db->prefix . 'webhook`'); +define('TABLE_WEBHOOKDATAS', '`' . $config->db->prefix . 'webhookdatas`'); +define('TABLE_LOG', '`' . $config->db->prefix . 'log`'); if(!defined('TABLE_LANG')) define('TABLE_LANG', '`' . $config->db->prefix . 'lang`'); $config->objectTables['product'] = TABLE_PRODUCT; diff --git a/db/update9.5.1.sql b/db/update9.5.1.sql index 88c7cd1fdb..6368a5b6fe 100644 --- a/db/update9.5.1.sql +++ b/db/update9.5.1.sql @@ -18,12 +18,43 @@ CREATE TABLE IF NOT EXISTS `zt_webhook` ( `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `url` varchar(255) NOT NULL, - `requestType` enum('post', 'get') NOT NULL DEFAULT 'get', - `params` text NOT NULL, + `contentType` enum('json','form') NOT NULL DEFAULT 'json', + `sendType` enum('sync','async') NOT NULL DEFAULT 'sync', + `params` varchar(100) NOT NULL, + `actions` text NOT NULL, `desc` text NOT NULL, `createdBy` varchar(30) NOT NULL, `createdDate` datetime NOT NULL, `editedBy` varchar(30) NOT NULL, `editedDate` datetime NOT NULL, - PRIMARY KEY `id` (`id`) + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `zt_webhookdatas`; +CREATE TABLE IF NOT EXISTS `zt_webhookdatas` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `webhook` mediumint(8) unsigned NOT NULL, + `action` mediumint(8) unsigned NOT NULL, + `data` text NOT NULL, + `status` enum('wait', 'sended') NOT NULL DEFAULT 'wait', + `createdBy` varchar(30) NOT NULL, + `createdDate` datetime NOT NULL, + PRIMARY KEY `id` (`id`), + UNIQUE KEY `uniq` (`webhook`, `action`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `zt_log`; +CREATE TABLE IF NOT EXISTS `zt_log` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `objectType` varchar(30) NOT NULL, + `objectID` mediumint(8) unsigned NOT NULL, + `action` mediumint(8) unsigned NOT NULL, + `date` datetime NOT NULL, + `url` varchar(255) NOT NULL, + `contentType` varchar(40) NOT NULL, + `data` text NOT NULL, + `status` smallint(5) NOT NULL, + PRIMARY KEY `id` (`id`), + KEY `objectType` (`objectType`), + KEY `obejctID` (`objectID`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/module/action/lang/zh-cn.php b/module/action/lang/zh-cn.php index 78f3a12eb0..8e0bce22e6 100755 --- a/module/action/lang/zh-cn.php +++ b/module/action/lang/zh-cn.php @@ -139,7 +139,7 @@ $lang->action->label->blocked = '阻塞了'; $lang->action->label->resolved = '解决了'; $lang->action->label->reviewed = '评审了'; $lang->action->label->moved = '移动了'; -$lang->action->label->confirmed = '确认了需求,'; +$lang->action->label->confirmed = '确认了需求'; $lang->action->label->bugconfirmed = '确认了'; $lang->action->label->tostory = '转需求'; $lang->action->label->frombug = '转需求'; diff --git a/module/action/model.php b/module/action/model.php index 5f1a54b01d..5784e55b4a 100755 --- a/module/action/model.php +++ b/module/action/model.php @@ -60,6 +60,8 @@ class actionModel extends model $this->file->updateObjectID($this->post->uid, $objectID, $objectType); + $this->loadModel('webhook')->send($objectType, $objectID, $actionType, $actionID); + return $actionID; } diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index b960c80214..b55ab98d6b 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -418,6 +418,7 @@ $lang->error->int = array("『%s』应当是数字。", "『%s』应 $lang->error->float = "『%s』应当是数字,可以是小数。"; $lang->error->email = "『%s』应当为合法的EMAIL。"; $lang->error->date = "『%s』应当为合法的日期。"; +$lang->error->code = "『%s』应当为字母或数字的组合。"; $lang->error->account = "『%s』应当为合法的用户名。"; $lang->error->passwordsame = "两次密码应当相等。"; $lang->error->passwordrule = "密码应该符合规则,长度至少为六位。"; diff --git a/module/entry/js/create.js b/module/entry/js/create.js index ccb04adea0..be33903126 100644 --- a/module/entry/js/create.js +++ b/module/entry/js/create.js @@ -16,16 +16,19 @@ function createKey() return false; } -$('#allIP').change(function() +$(function() { - if($(this).prop('checked')) + $('#allIP').change(function() { - $('#ip').attr('disabled', 'disabled'); - } - else - { - $('#ip').removeAttr('disabled'); - } -}) - -$('#name').focus(); + if($(this).prop('checked')) + { + $('#ip').attr('disabled', 'disabled'); + } + else + { + $('#ip').removeAttr('disabled'); + } + }) + + $('#name').focus(); +}); diff --git a/module/entry/js/edit.js b/module/entry/js/edit.js index ccb04adea0..be33903126 100644 --- a/module/entry/js/edit.js +++ b/module/entry/js/edit.js @@ -16,16 +16,19 @@ function createKey() return false; } -$('#allIP').change(function() +$(function() { - if($(this).prop('checked')) + $('#allIP').change(function() { - $('#ip').attr('disabled', 'disabled'); - } - else - { - $('#ip').removeAttr('disabled'); - } -}) - -$('#name').focus(); + if($(this).prop('checked')) + { + $('#ip').attr('disabled', 'disabled'); + } + else + { + $('#ip').removeAttr('disabled'); + } + }) + + $('#name').focus(); +}); diff --git a/module/entry/lang/zh-cn.php b/module/entry/lang/zh-cn.php index 8d1b8b6460..0cdf3ee54d 100644 --- a/module/entry/lang/zh-cn.php +++ b/module/entry/lang/zh-cn.php @@ -22,11 +22,13 @@ $lang->entry->createdDate = '创建时间'; $lang->entry->editedby = '最后编辑'; $lang->entry->editedDate = '编辑时间'; +$lang->entry->apiIndex = '应用入口'; $lang->entry->saveSuccess = '保存成功'; $lang->entry->confirmDelete = '您确认要删除该应用吗?'; $lang->entry->note = new stdClass(); $lang->entry->note->name = '授权应用名称'; -$lang->entry->note->code = '授权应用代号,必须为英文、数字或下划线的组合'; +$lang->entry->note->code = '授权应用代号,必须为字母或数字的组合'; $lang->entry->note->ip = "允许访问应用的ip,多个ip用逗号隔开。支持IP段,如192.168.1.*"; $lang->entry->note->allIP = '无限制'; +$lang->entry->note->api = 'moduleName、methodName以及参数列表替换成实际的值'; diff --git a/module/entry/model.php b/module/entry/model.php index a495dfbd04..35693b499b 100644 --- a/module/entry/model.php +++ b/module/entry/model.php @@ -27,7 +27,7 @@ class entryModel extends model * Get entry list. * * @param string $orderBy - * @param obejct $pager + * @param object $pager * @access public * @return array */ @@ -54,6 +54,7 @@ class entryModel extends model $this->dao->insert(TABLE_ENTRY)->data($entry) ->batchCheck($this->config->entry->create->requiredFields, 'notempty') + ->check('code', 'code') ->check('code', 'unique') ->autoCheck() ->exec(); @@ -83,6 +84,7 @@ class entryModel extends model $this->dao->update(TABLE_ENTRY)->data($entry) ->batchCheck($this->config->entry->edit->requiredFields, 'notempty') + ->check('code', 'code') ->check('code', 'unique', "id!=$entryID") ->autoCheck() ->where('id')->eq($entryID) diff --git a/module/entry/view/create.html.php b/module/entry/view/create.html.php index 020593d8d1..097f9bdf07 100644 --- a/module/entry/view/create.html.php +++ b/module/entry/view/create.html.php @@ -53,6 +53,11 @@ + + entry->apiIndex;?> + moduleVar}=moduleName&{$config->methodVar}=methodName&params=params";?> + + diff --git a/module/entry/view/edit.html.php b/module/entry/view/edit.html.php index 8282446994..7a997621b0 100644 --- a/module/entry/view/edit.html.php +++ b/module/entry/view/edit.html.php @@ -53,6 +53,11 @@ desc, "rows='3' class='form-control'");?> + + entry->apiIndex;?> + moduleVar}=moduleName&{$config->methodVar}=methodName&params=params";?> + + diff --git a/module/entry/view/log.html.php b/module/entry/view/log.html.php index 82d5fb3f7a..d005085aa2 100644 --- a/module/entry/view/log.html.php +++ b/module/entry/view/log.html.php @@ -15,8 +15,18 @@ action->date;?> - + action->actor;?> + entry->desc;?> + + + + date;?> + actor);?> + extra;?> + + + diff --git a/module/webhook/config.php b/module/webhook/config.php index 09291c3ead..a237812206 100644 --- a/module/webhook/config.php +++ b/module/webhook/config.php @@ -5,5 +5,23 @@ $config->webhook->create->requiredFields = 'name, url'; $config->webhook->edit = new stdclass(); $config->webhook->edit->requiredFields = 'name, url'; -$config->webhook->requestType['post'] = 'post'; -$config->webhook->requestType['get'] = 'get'; +$config->webhook->contentTypes['json'] = 'application/json'; +$config->webhook->contentTypes['form'] = 'application/x-www-form-urlencoded'; + +$config->webhook->objectTypes['product'] = array('opened', 'edited', 'closed', 'undeleted'); +$config->webhook->objectTypes['story'] = array('opened', 'edited', 'commented', 'frombug', 'changed', 'reviewed', 'closed', 'activated'); +$config->webhook->objectTypes['productplan'] = array('opened', 'edited'); +//$config->webhook->objectTypes['release'] = array('opened', 'edited', 'changestatus'); +$config->webhook->objectTypes['project'] = array('opened', 'edited', 'started', 'delayed', 'suspended', 'closed', 'activated', 'undeleted'); +$config->webhook->objectTypes['task'] = array('opened', 'edited', 'commented', 'assigned', 'confirmed', 'started', 'finished', 'editestimate', 'deleteestimate', 'paused', 'canceled', 'restarted', 'closed', 'activated'); +//$config->webhook->objectTypes['build'] = array('opened', 'edited'); +$config->webhook->objectTypes['bug'] = array('opened', 'edited', 'commented', 'assigned', 'confirmed', 'bugconfirmed', 'resolved', 'closed', 'activated'); +$config->webhook->objectTypes['case'] = array('opened', 'edited', 'commented', 'reviewed', 'confirmed'); +$config->webhook->objectTypes['testtask'] = array('opened', 'edited', 'started', 'blocked', 'closed', 'activated'); +$config->webhook->objectTypes['todo'] = array('opened', 'edited', 'erased'); +//$config->webhook->objectTypes['testsuite'] = array('opened', 'edited'); +//$config->webhook->objectTypes['caselib'] = array('opened', 'edited'); +//$config->webhook->objectTypes['testreport'] = array('opened', 'edited'); +//$config->webhook->objectTypes['doc'] = array('created', 'edited', 'commented'); +//$config->webhook->objectTypes['doclib'] = array('created', 'edited'); +//$config->webhook->objectTypes['user'] = array('login', 'logout'); diff --git a/module/webhook/control.php b/module/webhook/control.php index 1a955316ca..14a9e00149 100644 --- a/module/webhook/control.php +++ b/module/webhook/control.php @@ -12,29 +12,25 @@ class webhook extends control { /** - * Browse entries. + * Browse webhooks. * - * @param string $orderBy - * @param int $recTotal - * @param int $recPerPage - * @param int $pageID * @access public * @return void */ - public function browse($orderBy = 'id_desc', $recTotal = 0, $recPerPage = 10, $pageID = 1) + public function browse($orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) { - $pager = $this->app->loadClass('pager', $static = true); + $this->app->loadClass('pager', $static = true); $pager = new pager($recTotal, $recPerPage, $pageID); - $this->view->title = $this->lang->webhook->api . $this->lang->colon . $this->lang->webhook->list; - $this->view->entries = $this->webhook->getList($orderBy, $pager); - $this->view->orderBy = $orderBy; - $this->view->pager = $pager; + $this->view->title = $this->lang->webhook->api . $this->lang->colon . $this->lang->webhook->list; + $this->view->webhooks = $this->webhook->getList($orderBy, $pager); + $this->view->orderBy = $orderBy; + $this->view->pager = $pager; $this->display(); } /** - * Create an webhook. + * Create a webhook. * * @access public * @return void @@ -43,85 +39,112 @@ class webhook extends control { if($_POST) { - $webhookID = $this->webhook->create(); + $this->webhook->create(); if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); - - $this->loadModel('action')->create('webhook', $webhookID, 'created'); $this->send(array('result' => 'success', 'message' => $this->lang->webhook->saveSuccess, 'locate' => inlink('browse'))); } - $this->view->title = $this->lang->webhook->api . $this->lang->colon . $this->lang->webhook->create; + $this->app->loadLang('action'); + $this->view->title = $this->lang->webhook->api . $this->lang->colon . $this->lang->webhook->create; + $this->view->objectTypes = $this->webhook->getObjectTypes(); + $this->view->objectActions = $this->webhook->getObjectActions(); $this->display(); } /** - * Edit an webhook. + * Edit a webhook. * - * @param int $webhookID + * @param int $id * @access public * @return void */ - public function edit($webhookID) + public function edit($id) { if($_POST) { - $changes = $this->webhook->update($webhookID); + $this->webhook->update($id); if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); - - if($changes) - { - $actionID = $this->loadModel('action')->create('webhook', $webhookID, 'edited'); - $this->action->logHistory($actionID, $changes); - } $this->send(array('result' => 'success', 'message' => $this->lang->webhook->saveSuccess, 'locate' => inlink('browse'))); } - $webhook = $this->webhook->getById($webhookID); - $this->view->title = $this->lang->webhook->edit . $this->lang->colon . $webhook->name; - $this->view->webhook = $webhook; + $this->app->loadLang('action'); + $webhook = $this->webhook->getByID($id); + $this->view->title = $this->lang->webhook->edit . $this->lang->colon . $webhook->name; + $this->view->objectTypes = $this->webhook->getObjectTypes(); + $this->view->objectActions = $this->webhook->getObjectActions(); + $this->view->webhook = $webhook; $this->display(); } /** - * Delete an webhook. + * Delete a webhook. * - * @param int $webhookID + * @param int $id * @access public * @return void */ - public function delete($webhookID) + public function delete($id) { - $this->webhook->delete($webhookID); + $this->webhook->delete($id); if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); $this->send(array('result' => 'success')); } - public function log($webhookID) + /** + * Browse logs of a webhook. + * + * @param int $id + * @param string $orderBy + * @param int $recTotal + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function log($id, $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1) { - $webhook = $this->webhook->getById($webhookID); + $this->app->loadClass('pager', $static = true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $webhook = $this->webhook->getByID($id); $this->view->title = $this->lang->webhook->log . $this->lang->colon . $webhook->name; - $this->view->actions = $this->loadModel('action')->getList('webhook', $webhookID); + $this->view->logs = $this->webhook->getLogList($id, $orderBy, $pager); + $this->view->webhook = $webhook; + $this->view->orderBy = $orderBy; + $this->view->pager = $pager; $this->display(); } - public function actions($webhookID) + /** + * Send data by async. + * + * @access public + * @return void + */ + public function asyncSend() { - if($_POST) - { - $this->webhook->saveActions($webhookID); - if(dao::isError()) $this->send(array('result' => 'fail', 'message' => dao::getError())); + $webhooks = $this->webhook->getList(); + if(empty($hooks)) return false; + $dataList = $this->webhook->getDataList(); + if(empty($dataList)) return true; - $this->send(array('result' => 'success', 'message' => $this->lang->webhook->saveSuccess, 'locate' => inlink('browse'))); + $snoopy = $this->app->loadClass('snoopy'); + foreach($dataList as $data) + { + $webhook = zget($webhooks, $data->webhook, ''); + if($webhook) + { + $snoopy->_submit_type = zget($this->config->webhook->contentTypes, $webhook->contentType, 'application/json'); + $snoopy->submit($webhook->url, $data->data); + + $this->saveLog($data->webhook, $data->action, $webhook->url, $snoopy->_submit_type, $postData, $snoopy->status, $snoopy->error); + } + + if($snoopy->status == 200) $this->dao->update(TABLE_WEBHOOKDATA)->set('status')->eq('sended')->where('id')->eq($data->id)->exec(); } - $webhook = $this->webhook->getById($webhookID); - $this->view->title = $this->lang->webhook->actions . $this->lang->colon . $webhook->name; - $this->view->actions = $this->webhook->getActions($webhookID); - $this->display(); - } - - public function ajaxGetActions($module) - { + $this->dao->delete()->from(TABLE_WEBHOOKDATA)->where('status')->eq('sended')->exec(); + return true; } } diff --git a/module/webhook/css/browse.css b/module/webhook/css/browse.css new file mode 100644 index 0000000000..1d88a4811d --- /dev/null +++ b/module/webhook/css/browse.css @@ -0,0 +1 @@ +.text {overflow: hidden; text-overflow: ellipsis;} diff --git a/module/webhook/css/create.css b/module/webhook/css/create.css new file mode 100644 index 0000000000..19916caa95 --- /dev/null +++ b/module/webhook/css/create.css @@ -0,0 +1,3 @@ +.labelWidth label.checkbox-inline {width: 20%;} +.checkbox-inline + .checkbox-inline {margin-left: 0px;} +.objectType {margin-right: 5px !important;} diff --git a/module/webhook/css/edit.css b/module/webhook/css/edit.css new file mode 100644 index 0000000000..19916caa95 --- /dev/null +++ b/module/webhook/css/edit.css @@ -0,0 +1,3 @@ +.labelWidth label.checkbox-inline {width: 20%;} +.checkbox-inline + .checkbox-inline {margin-left: 0px;} +.objectType {margin-right: 5px !important;} diff --git a/module/webhook/js/create.js b/module/webhook/js/create.js index ccb04adea0..3acb71ac46 100644 --- a/module/webhook/js/create.js +++ b/module/webhook/js/create.js @@ -1,31 +1,17 @@ -/** - * create key for an entry. - * - * @access public - * @return void - */ -function createKey() +$(function() { - var chars = '0123456789abcdefghiklmnopqrstuvwxyz'.split(''); - var key = ''; - for(var i = 0; i < 32; i ++) - { - key += chars[Math.floor(Math.random() * chars.length)]; - } - $('#key').val(key); - return false; -} - -$('#allIP').change(function() -{ - if($(this).prop('checked')) + $('.objectType').click(function() { - $('#ip').attr('disabled', 'disabled'); - } - else - { - $('#ip').removeAttr('disabled'); - } -}) + if($(this).prop('checked')) + { + $(this).parent().parent().next().find('input[type=checkbox]').attr('checked', 'checked'); + } + else + { + $(this).parent().parent().next().find('input[type=checkbox]').removeAttr('checked'); + } + }); -$('#name').focus(); + $('#name').focus(); + $('#paramsmessage').attr('disabled', 'disabled'); +}); diff --git a/module/webhook/js/edit.js b/module/webhook/js/edit.js index ccb04adea0..3acb71ac46 100644 --- a/module/webhook/js/edit.js +++ b/module/webhook/js/edit.js @@ -1,31 +1,17 @@ -/** - * create key for an entry. - * - * @access public - * @return void - */ -function createKey() +$(function() { - var chars = '0123456789abcdefghiklmnopqrstuvwxyz'.split(''); - var key = ''; - for(var i = 0; i < 32; i ++) - { - key += chars[Math.floor(Math.random() * chars.length)]; - } - $('#key').val(key); - return false; -} - -$('#allIP').change(function() -{ - if($(this).prop('checked')) + $('.objectType').click(function() { - $('#ip').attr('disabled', 'disabled'); - } - else - { - $('#ip').removeAttr('disabled'); - } -}) + if($(this).prop('checked')) + { + $(this).parent().parent().next().find('input[type=checkbox]').attr('checked', 'checked'); + } + else + { + $(this).parent().parent().next().find('input[type=checkbox]').removeAttr('checked'); + } + }); -$('#name').focus(); + $('#name').focus(); + $('#paramsmessage').attr('disabled', 'disabled'); +}); diff --git a/module/webhook/lang/zh-cn.php b/module/webhook/lang/zh-cn.php index eec399e63c..aa35893694 100644 --- a/module/webhook/lang/zh-cn.php +++ b/module/webhook/lang/zh-cn.php @@ -1,31 +1,43 @@ webhook->common = 'webhook'; -$lang->webhook->list = 'webhook列表'; +$lang->webhook->list = '钩子列表'; $lang->webhook->api = '接口'; $lang->webhook->entry = '应用'; $lang->webhook->log = '日志'; -$lang->webhook->action = '触发操作'; -$lang->webhook->browse = '浏览webhook'; -$lang->webhook->create = '添加webhook'; -$lang->webhook->edit = '编辑webhook'; -$lang->webhook->delete = '删除webhook'; +$lang->webhook->browse = '浏览钩子'; +$lang->webhook->create = '添加钩子'; +$lang->webhook->edit = '编辑钩子'; +$lang->webhook->delete = '删除钩子'; $lang->webhook->id = 'ID'; $lang->webhook->name = '名称'; -$lang->webhook->url = '请求地址'; -$lang->webhook->requestType = '请求类型'; +$lang->webhook->url = 'Hook地址'; +$lang->webhook->contentType = '内容类型'; +$lang->webhook->sendType = '发送方式'; $lang->webhook->params = '参数'; +$lang->webhook->action = '触发动作'; $lang->webhook->desc = '描述'; -$lang->webhook->createdBy = '由谁创建'; -$lang->webhook->createdDate = '创建时间'; -$lang->webhook->editedby = '最后编辑'; -$lang->webhook->editedDate = '编辑时间'; +$lang->webhook->data = '数据'; +$lang->webhook->status = '状态'; + +$lang->webhook->sendTypeList['sync'] = '同步'; +$lang->webhook->sendTypeList['async'] = '异步'; + +$lang->webhook->paramsList['objectType'] = '对象类型'; +$lang->webhook->paramsList['objectID'] = '对象ID'; +$lang->webhook->paramsList['product'] = '所属产品'; +$lang->webhook->paramsList['project'] = '所属项目'; +$lang->webhook->paramsList['action'] = '动作'; +$lang->webhook->paramsList['actor'] = '操作者'; +$lang->webhook->paramsList['date'] = '操作日期'; +$lang->webhook->paramsList['comment'] = '备注'; +$lang->webhook->paramsList['message'] = '操作内容'; $lang->webhook->saveSuccess = '保存成功'; $lang->webhook->confirmDelete = '您确认要删除该webhook吗?'; +$lang->webhook->trimWords = '了'; + $lang->webhook->note = new stdClass(); -$lang->webhook->note->name = '授权webhook名称'; -$lang->webhook->note->url = '授权webhook地址'; -$lang->webhook->note->params = '参数列表,以 & 隔开'; +$lang->webhook->note->async = '异步需要打开计划任务'; diff --git a/module/webhook/model.php b/module/webhook/model.php index a2887d9d9e..8171198d2e 100644 --- a/module/webhook/model.php +++ b/module/webhook/model.php @@ -12,89 +12,342 @@ class webhookModel extends model { /** - * Get an webhook by id. + * Get a webhook by id. * - * @param int $webhookID + * @param int $id * @access public * @return array */ - public function getById($webhookID) + public function getByID($id) { - return $this->dao->select('*')->from(TABLE_WEBHOOK)->where('id')->eq($webhookID)->fetch(); + $webhook = $this->dao->select('*')->from(TABLE_WEBHOOK)->where('id')->eq($id)->fetch(); + $webhook->actions = json_decode($webhook->actions); + return $webhook; } /** * Get webhook list. * * @param string $orderBy - * @param obejct $pager + * @param object $pager * @access public * @return array */ public function getList($orderBy = 'id_desc', $pager = null) { - return $this->dao->select('*')->from(TABLE_WEBHOOK)->orderBy($orderBy)->page($pager)->fetchAll('id'); + $webhooks = $this->dao->select('*')->from(TABLE_WEBHOOK)->orderBy($orderBy)->page($pager)->fetchAll('id'); + foreach($webhooks as $webhook) $webhook->actions = json_decode($webhook->actions); + return $webhooks; } /** - * Create an webhook. + * Get log list of a webhook. + * + * @param int $id + * @param string $orderBy + * @param object $pager + * @access public + * @return array + */ + public function getLogList($id, $orderBy = 'date_desc', $pager = null) + { + $logs = $this->dao->select('*')->from(TABLE_LOG) + ->where('objectType')->eq('webhook') + ->andWhere('objectID')->eq($id) + ->orderBy($orderBy) + ->page($pager) + ->fetchAll('id'); + foreach($logs as $log) $log->data = json_decode($log->data); + return $logs; + } + + /** + * Get saved data list. * * @access public - * @return bool | int + * @return array + */ + public function getDataList() + { + return $this->dao->select('*')->from(TABLE_WEBHOOKDATA)->where('status')->eq('wait')->orderBy('id')->fetchAll('id'); + } + + /** + * Get object types. + * + * @access public + * @return array + */ + public function getObjectTypes() + { + $objectTypes = array(); + foreach($this->config->webhook->objectTypes as $objectType => $actions) + { + $objectTypes[$objectType] = $this->lang->action->objectTypes[$objectType]; + } + return $objectTypes; + } + + /** + * Get object actions. + * + * @access public + * @return array + */ + public function getObjectActions() + { + $objectActions = array(); + foreach($this->config->webhook->objectTypes as $objectType => $actions) + { + foreach($actions as $action) + { + $objectActions[$objectType][$action] = str_replace($this->lang->webhook->trimWords, '', $this->lang->action->label->$action); + } + } + return $objectActions; + } + + /** + * Create a webhook. + * + * @access public + * @return bool */ public function create() { $webhook = fixer::input('post') ->add('createdBy', $this->app->user->account) ->add('createdDate', helper::now()) + ->setForce('params', implode(',', $this->post->params) . ',message') + ->setForce('actions', helper::jsonEncode($this->post->actions)) + ->skipSpecial('url,actions') ->get(); - + $this->dao->insert(TABLE_WEBHOOK)->data($webhook) ->batchCheck($this->config->webhook->create->requiredFields, 'notempty') ->autoCheck() ->exec(); - if(dao::isError()) return false; - - return $this->dao->lastInsertId(); + return !dao::isError(); } /** - * Update an webhook. + * Update a webhook. * - * @param int $webhookID + * @param int $id * @access public - * @return bool | array + * @return bool */ - public function update($webhookID) + public function update($id) { - $oldEntry = $this->getById($webhookID); - $webhook = fixer::input('post') ->add('editedBy', $this->app->user->account) ->add('editedDate', helper::now()) + ->setForce('params', implode(',', $this->post->params) . ',message') + ->setForce('actions', helper::jsonEncode($this->post->actions)) + ->skipSpecial('url,actions') ->get(); $this->dao->update(TABLE_WEBHOOK)->data($webhook) ->batchCheck($this->config->webhook->edit->requiredFields, 'notempty') ->autoCheck() - ->where('id')->eq($webhookID) + ->where('id')->eq($id) ->exec(); - if(dao::isError()) return false; - - return common::createChanges($oldEntry, $webhook); + return !dao::isError(); } /** - * Delete an webhook. + * Delete a webhook. * - * @param int $webhookID - * @param int $null + * @param int $id + * @param object $null * @access public * @return bool */ - public function delete($webhookID, $null = null) + public function delete($id, $null = null) { - $this->dao->delete()->from(TABLE_WEBHOOK)->where('id')->eq($webhookID)->exec(); + $this->dao->delete()->from(TABLE_WEBHOOK)->where('id')->eq($id)->exec(); + $this->dao->delete()->from(TABLE_LOG)->where('objectType')->eq('webhook')->andWhere('objectID')->eq($id)->exec(); + return !dao::isError(); + } + + /** + * Send data. + * + * @param string $objectType + * @param int $objectID + * @param string $actionType + * @param int $actionID + * @access public + * @return bool + */ + public function send($objectType, $objectID, $actionType, $actionID) + { + static $webhooks = array(); + if(!$webhooks) $webhooks = $this->getList(); + if(!$webhooks) return true; + + $snoopy = $this->app->loadClass('snoopy'); + foreach($webhooks as $id => $webhook) + { + if(!in_array($actionType, $webhook->actions->$objectType)) continue; + $postData = $this->buildData($objectType, $objectID, $actionType, $actionID, explode(',', $webhook->params)); + if($webhook->sendType == 'async') + { + $this->saveData($id, $actionID, $postData); + continue; + } + + $contentType = zget($this->config->webhook->contentTypes, $webhook->contentType, 'application/json'); + $result = $this->fetchHook($contentType, $webhook->url, $postData); + + $this->saveLog($id, $actionID, $webhook->url, $contentType, $postData, $result); + } + return !dao::isError(); + } + + /** + * Build data. + * + * @param string $objectType + * @param int $objectID + * @param string $actionType + * @param int $actionID + * @param array $params + * @access public + * @return string + */ + public function buildData($objectType, $objectID, $actionType, $actionID, $params) + { + static $users = array(); + if(empty($users)) $users = $this->loadModel('user')->getPairs('noletter'); + if(!isset($this->lang->action->label)) $this->loadModel('action'); + if(!isset($this->lang->action->label->$actionType)) return false; + if(empty($this->config->objectTables[$objectType])) return false; + $action = $this->dao->select('*')->from(TABLE_ACTION)->where('id')->eq($actionID)->fetch(); + $object = $this->dao->select('*')->from($this->config->objectTables[$objectType])->where('id')->eq($objectID)->fetch(); + $field = $this->config->action->objectNameFields[$objectType]; + $title = $object->$field; + + $oldOnlyBody = ''; + if(isset($_GET['onlybody']) and $_GET['onlybody'] == 'yes') + { + $oldOnlyBody = 'yes'; + unset($_GET['onlybody']); + } + $viewLink = helper::createLink($objectType, 'view', "id=$objectID"); + if($oldOnlyBody) $_GET['onlybody'] = $oldOnlyBody; + + $host = common::getSysURL(); + $data = new stdclass(); + foreach($params as $param) + { + if($param == 'message') + { + $data->text = $this->app->user->realname . $this->lang->action->label->$actionType . $this->lang->action->objectTypes[$objectType] . ' ' . "[#{$objectID}::{$title}](" . $host . $viewLink . ")"; + if($actionType == 'assigned') $data->text .= ' ' . $this->lang->webhooks->assigned . ' ' . zget($users, $object->assignedTo); + } + else + { + $data->$param = $action->$param; + } + } + + if(!empty($_FILES['files']['name'][0])) + { + $this->loadModel('file'); + $files = $this->dao->select('*')->from(TABLE_FILE)->where('objectType')->eq($objectType)->andWhere('objectID')->eq($objectID)->orderBy('addedDate_desc,id_desc')->limit(count($_FILES['files']['name']))->fetchAll(); + if($files) + { + foreach($files as $file) + { + $attachment = array(); + $attachment['title'] = $file->title; + $attachment['images'][]['url'] = $data->host . $this->file->webPath . $file->pathname; + $data->attachments[] = $attachment; + } + } + } + + return helper::jsonEncode($data); + } + + /** + * Post hook data. + * + * @param string $contentType + * @param string $url + * @param string $sendData + * @access public + * @return int + */ + public function fetchHook($contentType, $url, $sendData) + { + $header[] = "Content-Type: $contentType"; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $sendData); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + return $httpCode; + } + + /** + * Save datas. + * + * @param int $webhookID + * @param int $actionID + * @param string $data + * @access public + * @return bool + */ + public function saveData($webhookID, $actionID, $data) + { + $webhookData = new stdclass(); + $webhookData->webhook = $webhookID; + $webhookData->action = $actionID; + $webhookData->data = $data; + $webhookData->createdBy = $this->app->user->account; + $webhookData->createdDate = helper::now(); + + $this->dao->insert(TABLE_WEBHOOKDATA)->data($webhookData)->exec(); + return !dao::isError(); + } + + /** + * Save log. + * + * @param int $webhookID + * @param int $actionID + * @param string $url + * @param string $contentType + * @param string $data + * @param int $status + * @param string $error + * @access public + * @return bool + */ + public function saveLog($webhookID, $actionID, $url, $contentType, $data, $status, $error = '') + { + $log = new stdclass(); + $log->objectType = 'webhook'; + $log->objectID = $webhookID; + $log->action = $actionID; + $log->date = helper::now(); + $log->url = $url; + $log->contentType = $contentType; + $log->data = $data; + $log->status = $status; + $log->error = $error; + + $this->dao->insert(TABLE_LOG)->data($log)->exec(); return !dao::isError(); } } diff --git a/module/webhook/view/browse.html.php b/module/webhook/view/browse.html.php index 1f6a7cfafb..2ec836e1f4 100644 --- a/module/webhook/view/browse.html.php +++ b/module/webhook/view/browse.html.php @@ -13,36 +13,29 @@ webhook->confirmDelete);?>
- +
- recTotal&recPerPage=$pager->recPerPage&pageID=$pager->pageID";?> - - - - - - - + + + + - + + $webhook):?> - - - - - - + + + - +
webhook->id);?>webhook->name);?>webhook->url);?>webhook->requestType);?>webhook->params);?>webhook->desc);?>actions);?>webhook->id;?>webhook->name;?>webhook->url;?>actions;?>
id;?>name;?>url;?>requestType;?>params;?>desc;?>name;?>url;?> id", '', 'list', 'file-text-o'); - common::printIcon('webhook', 'action', "webhookID=$webhook->id", '', 'list', 'cog'); - common::printIcon('webhook', 'edit', "webhookID=$webhook->id", '', 'list'); + common::printIcon('webhook', 'log', "webhookID=$id", '', 'list', 'file-text-o'); + common::printIcon('webhook', 'edit', "webhookID=$id", '', 'list'); if(common::hasPriv('webhook', 'delete')) { - $deleteURL = $this->createLink('webhook', 'delete', "webhookID=$webhook->id&confirm=yes"); + $deleteURL = $this->createLink('webhook', 'delete', "webhookID=$id&confirm=yes"); echo html::a("javascript:ajaxDelete(\"$deleteURL\",\"webhookList\",confirmDelete)", '', '', "title='{$lang->webhook->delete}' class='btn-icon'"); } ?> @@ -52,7 +45,7 @@
show();?>show();?>
diff --git a/module/webhook/view/create.html.php b/module/webhook/view/create.html.php index deaa13fa77..5dd4f813c8 100644 --- a/module/webhook/view/create.html.php +++ b/module/webhook/view/create.html.php @@ -12,7 +12,7 @@ ?> -
+
webhook->api;?> @@ -23,26 +23,48 @@ - - + + - + - - + + + + + + + + - + + + + + - - + diff --git a/module/webhook/view/edit.html.php b/module/webhook/view/edit.html.php index 3db5bcf184..52ae0a819c 100644 --- a/module/webhook/view/edit.html.php +++ b/module/webhook/view/edit.html.php @@ -12,7 +12,7 @@ ?> -
+
webhook->api;?> @@ -23,26 +23,49 @@
webhook->name;?>webhook->note->name}'");?>
webhook->url;?>webhook->note->url}'");?>
webhook->requestType;?>webhook->requestType, '', "class='form-control'");?>webhook->contentType;?>webhook->contentTypes, '', "class='form-control'");?>
webhook->sendType;?>webhook->sendTypeList, '', "class='form-control'");?>webhook->note->async;?>
webhook->params;?>webhook->note->params}' placeholder='{$lang->webhook->note->params}'");?>webhook->paramsList, 'message');?>
webhook->action;?> + + webhook->objectTypes as $objectType => $actions):?> + + + + + +
+ +
+
webhook->desc;?>
- - + + - + - - + + + + + + + + - + + + + + - - + diff --git a/module/webhook/view/log.html.php b/module/webhook/view/log.html.php new file mode 100644 index 0000000000..e2fe9ddd9f --- /dev/null +++ b/module/webhook/view/log.html.php @@ -0,0 +1,49 @@ + + * @package log + * @version $Id$ + * @link http://www.zentao.net + */ +?> + +
+
+ webhook->common);?> + name;?> + webhook->log;?> +
+
+
webhook->name;?>name, "class='form-control' placeholder='{$lang->webhook->note->name}'");?>name, "class='form-control'");?>
webhook->url;?>url, "class='form-control' placeholder='{$lang->webhook->note->url}'");?>url, "class='form-control'");?>
webhook->requestType;?>webhook->requestType, $webhook->requestType, "class='form-control'");?>webhook->contentType;?>webhook->contentTypes, $webhook->contentType, "class='form-control'");?>
webhook->sendType;?>webhook->sendTypeList, $webhook->sendType, "class='form-control'");?>webhook->note->async;?>
webhook->params;?>params, "class='form-control' title='{$lang->webhook->note->params}' placeholder='{$lang->webhook->note->params}'");?>webhook->paramsList, $webhook->params);?>
webhook->action;?> + + webhook->objectTypes as $objectType => $actions):?> + + + + + +
+ + actions->$objectType) ? $webhook->actions->$objectType : '');?>
+
webhook->desc;?>desc, "rows='3' class='form-control'");?>desc, "rows='3' class='form-control'");?>
+ + + id}&orderBy=%s&recTotal={$pager->recTotal}&recPerPage={$pager->recPerPage}&pageID={$pager->pageID}";?> + + + + + + + + + $log):?> + + + + + + + + + + + + + + +
webhook->id);?>webhook->url);?>webhook->action);?>webhook->contentType);?>webhook->status);?>
url;?>data->text;?>contentType;?>status;?>
show();?>
+