<?php
require_once(__DIR__ . '/../util2.inc.php');
require_once(__DIR__ . '/../log.inc.php');
require_once(__DIR__ . '/../sql.inc.php');
require_once(__DIR__ . '/../prof.inc.php');
$rg_wh_error = "";
function rg_wh_set_error($str)
{
global $rg_wh_error;
$rg_wh_error = $str;
rg_log($str);
}
function rg_wh_error()
{
global $rg_wh_error;
return $rg_wh_error;
}
// Here plugins will store the functions
$rg_wh_plugins = array();
$rg_wh_flags = array(
'D' => 'Hook disabled'
);
$rg_wh_events = array(
'C' => 'Create repository',
'P' => 'Push',
'B' => 'Create branch'
);
/*
* Generates event list as html
*/
function rg_wh_check_events($events)
{
global $rg_wh_events;
if (empty($rg_wh_events))
return '';
$ret = '<fieldset>';
$ret .= '<legend>Select trigger events</legend>';
$br = '';
foreach ($rg_wh_events as $id => $name) {
$add = '';
if (strchr($events, $id))
$add = ' checked="checked"';
$ret .= $br
. '<input type="checkbox" name="wh::idata::events[' . $id . ']"'
. ' id="events-' . $id . '"'
. $add . ' />'
. "\n"
. '<label for="events-' . $id . '">' . $name . '</label>';
$br = '<br />' . "\n";
}
$ret .= '</fieldset>' . "\n";
return $ret;
}
/*
* Generates an events list as text
*/
function rg_wh_events($events)
{
global $rg_wh_events;
$a = array();
foreach ($rg_wh_events as $id => $name) {
if (strchr($events, $id))
$a[] = $name;
}
return implode(', ', $a);
}
/*
* Generates flags list
*/
function rg_wh_check_flags($all_flags, $flags)
{
global $rg_wh_flags;
$ret = '';
$br = '';
$list = array_merge($rg_wh_flags, $all_flags);
foreach ($list as $id => $name) {
$add = '';
if (strchr($flags, $id))
$add = ' checked="checked"';
$ret .= $br
. '<input type="checkbox" name="wh::flags[' . $id . ']"'
. ' id="flags-' . $id . '"'
. $add . ' />'
. "\n"
. '<label for="flags-' . $id . '">' . $name . '</label>';
$br = '<br />' . "\n";
}
return $ret;
}
/*
* Generates a flags list as text
*/
function rg_wh_flags($all_flags, $flags)
{
global $rg_wh_flags;
$a = array();
$list = array_merge($rg_wh_flags, $all_flags);
foreach ($list as $id => $name) {
if (strchr($flags, $id))
$a[] = $name;
}
return implode(', ', $a);
}
/*
* Some cosmetics applied to a webhook
*/
function rg_wh_cosmetic(&$list)
{
global $rg_wh_plugins;
rg_log_debug('rg_wh_cosmetic');
foreach ($list as $id => &$row) {
$type = $row['htype'];
if (isset($row['itime']))
$row['itime_nice'] = gmdate('Y-m-d H:i', $row['itime']);
$row['HTML:flags_text'] =
rg_wh_flags($rg_wh_plugins[$type]['flags'],
$row['flags']);
if (isset($row['description'])) {
if (empty($row['description']))
$row['HTML:description_nice'] = 'n/a';
else
$row['HTML:description_nice'] =
nl2br(rg_xss_safe($row['description']));
}
if (isset($row['last_output'])) {
if (empty($row['last_output']))
$row['HTML:last_output_nice'] = 'n/a';
else
$row['HTML:last_output_nice'] =
nl2br(rg_xss_safe($row['last_output']));
}
if ($rg_wh_plugins[$type]['have_events'] == 1) {
if (isset($row['idata']['events']))
$row['idata']['events_text'] =
rg_wh_events($row['idata']['events']);
}
$row['idata']['HTML:secrets_list'] = rg_template_table('user/settings/wh/secrets_show',
$row['idata']['secrets'], array());
rg_wh_call_callback($type, $row['hsubtype'],
'cosmetic_pre', $xret_junk, $row, $junk);
rg_wh_call_callback($type, $row['hsubtype'],
'cosmetic_post', $xret_junk, $row, $junk);
}
unset($row);
}
/*
* Set last_output field of a webhook
*/
function rg_wh_set_last_output($db, $uid, $id, $output)
{
rg_prof_start('wh_set_last_output');
rg_log_enter('wh_set_last_output id=' . $id);
$ret = FALSE;
while (1) {
$params = array('id' => $id, 'last_output' => $output);
$sql = 'UPDATE webhooks'
. ' SET last_output = @@last_output@@'
. ' WHERE id = @@id@@';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_wh_set_error('cannot insert/update data');
break;
}
rg_sql_free_result($res);
$key = 'wh' . '::' . $uid . '::' . 'list'
. '::' . $id . '::' . 'last_output';
rg_cache_set($key, $output, RG_SOCKET_NO_WAIT);
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end('wh_set_last_output');
return $ret;
}
/*
* Sorting the webhooks list by itime DESC
*/
function rg_wh_sort_helper($a, $b)
{
if ($a['itime'] > $b['itime'])
return -1;
if ($a['itime'] == $b['itime'])
return 0;
return 1;
}
/*
* Returns a list of webhooks associated with a user
*/
function rg_wh_list($db, $uid)
{
rg_prof_start('wh_list');
rg_log_enter('wh_list');
$ret = array('ok' => 0, 'list' => array());
while (1) {
$key = 'wh' . '::' . $uid;
$r = rg_cache_get($key);
if (($r !== FALSE) && isset($r['LIST_LOADED'])) {
$ret['list'] = $r['list'];
//rg_log_ml('list: ' . rg_array2string($ret['list']));
$ret['ok'] = 1;
break;
}
$params = array('uid' => $uid);
$sql = 'SELECT * FROM webhooks'
. ' WHERE uid = @@uid@@'
. ' ORDER BY itime DESC';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_wh_set_error('cannot load data');
break;
}
while (($row = rg_sql_fetch_array($res))) {
//rg_log_debug('wh_list: row: ' . rg_array2string($row));
$id = $row['id'];
if (!empty($row['idata'])) {
$row['idata'] = rg_unserialize($row['idata']);
if ($row['idata'] === FALSE) {
// TODO: we try to continue
$row['idata'] = array();
}
} else {
$row['idata'] = array();
}
// We used to not populate hsubtype in the past
if (empty($row['hsubtype'])) {
if (isset($row['idata']['url'])
&& stristr($row['idata']['url'], 'slack.com'))
$row['hsubtype'] = 'slack';
else
$row['hsubtype'] = 'generic';
}
$ret['list'][$id] = $row;
}
rg_sql_free_result($res);
$a = array('LIST_LOADED' => 1, 'list' => $ret['list']);
rg_cache_merge($key, $a, RG_SOCKET_NO_WAIT);
$ret['ok'] = 1;
break;
}
uasort($ret['list'], 'rg_wh_sort_helper');
rg_log_exit();
rg_prof_end('wh_list');
return $ret;
}
/*
* Adds/edits a webhook
*/
function rg_wh_add($db, $uid, $data)
{
rg_prof_start('wh_add');
rg_log_enter('wh_add');
$ret = array('ok' => 0);
while (1) {
$data['uid'] = $uid;
$data['itime'] = time();
$params = $data;
$params['idata'] = rg_serialize($params['idata']);
if ($data['id'] == 0) {
$data['last_output'] = '';
$sql = 'INSERT INTO webhooks (uid, itime'
. ', htype, hsubtype, flags, repo, refname'
. ', add_ip, description, idata)'
. ' VALUES (@@uid@@, @@itime@@'
. ', @@htype@@, @@hsubtype@@, @@flags@@, @@repo@@'
. ', @@refname@@'
. ', @@add_ip@@'
. ', @@description@@, @@idata@@)'
. ' RETURNING id';
} else {
$sql = 'UPDATE webhooks'
. ' SET description = @@description@@'
. ', flags = @@flags@@'
. ', repo = @@repo@@'
. ', refname = @@refname@@'
. ', idata = @@idata@@'
. ' WHERE uid = @@uid@@'
. ' AND id = @@id@@';
}
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_wh_set_error('cannot insert/update data');
break;
}
if ($data['id'] == 0)
$row = rg_sql_fetch_array($res);
rg_sql_free_result($res);
if ($data['id'] == 0)
$data['id'] = $row['id'];
$key = 'wh' . '::' . $uid . '::' . 'list'
. '::' . $data['id'];
rg_cache_merge($key, $data, RG_SOCKET_NO_WAIT);
rg_debug_html_set('wh', $data);
$ret['ok'] = 1;
break;
}
rg_log_exit();
rg_prof_end('wh_add');
return $ret;
}
/*
* Removes a list of webhooks
*/
function rg_wh_remove($db, $uid, $list)
{
rg_prof_start('wh_remove');
rg_log_enter('wh_remove');
$ret = array('ok' => 0);
while (1) {
if (empty($list)) {
rg_wh_set_error('you did not select anything');
break;
}
$my_list = array();
foreach ($list as $id => $junk)
$my_list[] = sprintf("%u", $id);
$params = array('uid' => $uid);
$sql_list = implode(', ', $my_list);
$sql = 'DELETE FROM webhooks'
. ' WHERE uid = @@uid@@'
. ' AND id IN (' . $sql_list . ')';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_wh_set_error('cannot remove webhooks');
break;
}
rg_sql_free_result($res);
foreach ($my_list as $junk => $id) {
$key = 'wh' . '::' . $uid
. '::' . 'list' . '::' . $id;
rg_cache_unset($key, RG_SOCKET_NO_WAIT);
}
$ret['ok'] = 1;
break;
}
rg_log_exit();
rg_prof_end('wh_remove');
return $ret;
}
/*
* Extract vars from request
*/
function rg_wh_fill_vars(&$rg)
{
global $rg_wh_plugins;
$ret = FALSE;
while (1) {
$type = $rg['wh']['htype'];
$rg['wh']['repo'] = rg_var_str('wh::repo');
$rg['wh']['flags'] = rg_var_a2s('wh::flags');
$rg['wh']['refname'] = rg_var_str('wh::refname');
$rg['wh']['itime'] = time();
$rg['wh']['add_ip'] = rg_ip();
$rg['wh']['description'] = trim(rg_var_str('wh::description'));
$rg['wh']['idata'] = array();
// secrets
$rg['wh']['idata']['secrets'] = array();
for ($i = 0; $i < 5; $i++) {
$rg['wh']['idata']['secrets'][$i]['name'] = rg_var_str('wh::idata::secrets::' . $i . '::name');
$rg['wh']['idata']['secrets'][$i]['value'] = rg_var_str('wh::idata::secrets::' . $i . '::value');
}
$rg['wh']['idata']['events'] = rg_var_a2s('wh::idata::events'); // TODO
// TODO: move this!
if (!isset($rg_wh_plugins[$type])) {
$errmsg[] = rg_template('user/settings/wh/invalid_htype',
$rg, TRUE /*xss*/);
break;
}
rg_wh_call_callback($type, $rg['wh']['hsubtype'],
'fill_vars', $xret_junk, $rg, $junk);
$ret = TRUE;
break;
}
return $ret;
}
/*
* Validate parameters
*/
function rg_wh_validate_vars($rg, &$errmsg)
{
global $rg_wh_flags;
global $rg_wh_plugins;
rg_log_ml('wh_validate_vars: wh: ' . rg_array2string($rg['wh']));
$ret = FALSE;
while (1) {
$type = $rg['wh']['htype'];
// Move this?
if (!isset($rg_wh_plugins[$type])) {
$errmsg[] = rg_template('user/settings/wh/invalid_htype.txt',
$rg, TRUE /*xss*/);
break;
}
$all_ok = TRUE;
$list = array_merge($rg_wh_flags, $rg_wh_plugins[$type]['flags']);
$len = strlen($rg['wh']['flags']);
for ($i = 0; $i < $len; $i++) {
$f = $rg['wh']['flags'][$i];
if (!isset($list[$f])) {
$all_ok = FALSE;
$errmsg[] = rg_template('user/settings/wh/inv_flag.txt',
$rg, TRUE /*xss*/);
break;
}
}
if (!$all_ok)
break;
// Some hooks do not need events, for example CodeDeploy
if ($rg_wh_plugins[$type]['have_events'] == 1) {
if (empty($rg['wh']['idata']['events'])) {
$errmsg[] = rg_template('user/settings/wh/inv_events.txt',
$rg, TRUE /*xss*/);
break;
}
}
$r = rg_wh_call_callback($type, $rg['wh']['hsubtype'],
'validate_vars', $ret, $rg, $errmsg);
if (($r === TRUE) && ($ret === FALSE))
break;
$ret = TRUE;
break;
}
return $ret;
}
/*
* Generic add_form function
*/
function rg_wh_add_form($db, &$rg)
{
global $rg_wh_plugins;
rg_log_debug('wh_add_form');
while (1) {
$type = $rg['wh']['htype'];
if (!isset($rg_wh_plugins[$type]))
break;
$rg['HTML:check_flags'] =
rg_wh_check_flags($rg_wh_plugins[$type]['flags'],
$rg['wh']['flags']);
if ($rg_wh_plugins[$type]['have_events'] == 1)
$rg['HTML:check_events'] =
rg_wh_check_events($rg['wh']['idata']['events']);
$rg['HTML:secrets'] = rg_template_table('user/settings/wh/secrets',
$rg['wh']['idata']['secrets'], $rg);
rg_wh_call_callback($type, $rg['wh']['hsubtype'],
'add_form_pre', $xret_junk, $db, $rg);
rg_wh_call_callback($type, $rg['wh']['hsubtype'],
'add_form_post', $xret_junk, $db, $rg);
break;
}
rg_log_debug('AFTER wh_add_form: wh[idata]: '
. rg_array2string($rg['wh']['idata']));
}
/*
* Generic default_paras function
*/
function rg_wh_default_paras(&$rg)
{
if (!isset($rg['wh']['id']))
$rg['wh']['id'] = 0;
if (!isset($rg['wh']['description']))
$rg['wh']['description'] = '';
if (!isset($rg['wh']['flags']))
$rg['wh']['flags'] = '';
if (!isset($rg['wh']['repo']))
$rg['wh']['repo'] = '';
if (!isset($rg['wh']['refname']))
$rg['wh']['refname'] = '';
if (!isset($rg['wh']['idata']))
$rg['wh']['idata'] = array();
if (!isset($rg['wh']['idata']['secrets']))
$rg['wh']['idata']['secrets'] = array();
for ($i = 0; $i < 5; $i++)
if (!isset($rg['wh']['idata']['secrets'][$i]))
$rg['wh']['idata']['secrets'][$i] =
array('name' => '', 'value' => '');
if (!isset($rg['wh']['idata']['events']))
$rg['wh']['idata']['events'] = '';
if (!isset($rg['wh']['idata']['url_example']))
$rg['wh']['idata']['url_example'] = '';
}
/*
* Generic hints add function
*/
function rg_wh_fill_hints(&$rg, &$hints)
{
while (1) {
$hints[]['HTML:hint'] =
rg_template('user/settings/wh/hints_tags.html',
$rg, TRUE /*xss*/);
rg_wh_call_callback($rg['wh']['htype'], $rg['wh']['hsubtype'],
'fill_hints', $xret_junk, $rg, $hints);
break;
}
}
/*
* Returns a HTML list with possible htypes
*/
function rg_wh_htypes($rg)
{
global $rg_wh_plugins;
foreach ($rg_wh_plugins as $htype => &$pi) {
$pi['HTML:hsubtype_list'] = '';
$add = '';
foreach ($pi['subtypes'] as $info) {
$a = array(
'htype' => $htype,
'hsubtype' => $info['subtype'],
'name' => $info['name']
);
$pi['HTML:hsubtype_list'] .= $add
. rg_template('user/settings/wh/subtype.html', $a, TRUE /*xss*/);
$add = ' | ';
}
}
unset($pi);
asort($rg_wh_plugins);
return rg_template_table('user/settings/wh/plugins_list',
$rg_wh_plugins, $rg);
}
/*
* Returns TRUE if htype is valid
*/
function rg_wh_valid($htype)
{
global $rg_wh_plugins;
return isset($rg_wh_plugins[$htype]);
}
/*
* Function used to replace ##tag##
*/
function rg_wh_replace_tags(&$ev)
{
//rg_log_ml('wh_replace_tags: ev=' . rg_array2string($ev));
$branch = isset($ev['refname']) ? rg_repo_ref_nice($ev['refname']) : '';
//rg_log_debug('branch=' . $branch);
$repo_name = isset($ev['ri']['name']) ? $ev['ri']['name'] : '';
$new_rev = isset($ev['new_rev']) ? $ev['new_rev'] : '';
if (isset($ev['wh']['idata']['content_type'])
&& (strcasecmp($ev['wh']['idata']['content_type'], 'application/json') == 0))
$ev['wh']['idata']['custom_body'] = @json_decode($ev['wh']['idata']['custom_body'], TRUE);
$keys = array('##repo_url##',
'##commit_url##',
'##branch##', '##repo##',
'##hook_id##', '##commit##',
'##date##', '##time##',
'##ip##', '##timestamp##');
$values = array($ev['ri']['url'],
$ev['ri']['url'] . '/source/log/commit/' . $new_rev,
$branch, $repo_name,
$ev['wh']['id'], $new_rev,
gmdate('Y-m-d', $ev['itime']),
gmdate('H:i:s', $ev['itime']),
$ev['ip'], $ev['itime']);
foreach ($ev['wh']['idata'] as $var => $value)
$ev['wh']['idata'][$var] = rg_str_replace($keys, $values, $value, 0);
if (isset($ev['wh']['idata']['content_type'])
&& (strcasecmp($ev['wh']['idata']['content_type'], 'application/json') == 0))
$ev['wh']['idata']['custom_body'] = @json_encode($ev['wh']['idata']['custom_body']);
//rg_log_ml('after: ' . rg_array2string($ev['wh']['idata']));
}
/*
* Used to filter hooks by repo name
*/
function rg_wh_repo_match($pattern, $repo_name)
{
rg_log('wh_repo_match pattern=[' . $pattern . ']'
. ' repo_name=[' . $repo_name . ']');
$r = preg_match('`' . $pattern . '`uD', $repo_name);
if ($r === FALSE) {
rg_internal_error('preg_match failed: pattern=['
. $pattern . '] repo_name=' . $repo_name);
return FALSE;
}
return $r === 1;
}
/*
* Register a new plugin
*/
function rg_wh_register_type($type, $info)
{
global $rg_wh_plugins;
if (!isset($cb['subtypes']))
$cb['subtypes'] = array();
$rg_wh_plugins[$type] = $info;
}
/*
* Allow subtype to register with a main htype
*/
function rg_wh_register_subtype($type, $subtype, $name, $callbacks)
{
global $rg_wh_plugins;
$rg_wh_plugins[$type]['subtypes'][] = array(
'subtype' => $subtype,
'name' => $name,
'cb' => $callbacks
);
}
/*
* Returns the information for a subtype by name
*/
function rg_wh_find_subtype($type, $subtype)
{
global $rg_wh_plugins;
foreach ($rg_wh_plugins[$type]['subtypes'] as $st) {
if (strcmp($st['subtype'], $subtype) == 0)
return $st;
}
return FALSE;
}
/*
* Call plugin and subtype callbacks
* Returns TRUE if callback wes called, else TRUE
*/
function rg_wh_call_callback($type, $subtype, $cb, &$xret, &$para1, &$para2)
{
global $rg_wh_plugins;
rg_log_enter('wh_call_callback: type=' . $type . ' subtype=' . $subtype
. ' cb=' . $cb);
$ret = FALSE;
while (1) {
if (!isset($rg_wh_plugins[$type]))
break;
$pt = $rg_wh_plugins[$type];
//rg_log_debug('pt: ' . rg_array2string($pt));
if (isset($pt['cb'][$cb])) {
rg_log_debug('calling type callback ' . $cb);
$xret = $pt['cb'][$cb]($para1, $para2);
$ret = TRUE;
} else {
rg_log_debug('type cb [' . $cb . '] not defined!');
}
$si = rg_wh_find_subtype($type, $subtype);
//rg_log_debug('si: ' . rg_array2string($si));
if ($si === FALSE)
break;
if (!isset($si['cb'][$cb])) {
rg_log_debug('subtype cb ' . $cb . ' is not defined!');
break;
}
rg_log_debug('calling subtype callback ' . $cb);
$xret = $si['cb'][$cb]($para1, $para2);
$ret = TRUE;
break;
}
rg_log_exit();
return $ret;
}
/*
* Validate a custom_body of type JSON
*/
function rg_wh_validate_json($rg, &$errmsg)
{
$ret = FALSE;
while (1) {
$r = @json_decode($rg['wh']['idata']['custom_body'], TRUE);
if ($r === NULL) {
$errmsg[] = 'cannot validate json: ' . json_last_error_msg();
break;
}
$ret = TRUE;
break;
}
return $ret;
}