name = plugin_lang_get('title');
$this->description = plugin_lang_get('description');
$this->page = 'config';
$this->version = '1.0';
$this->requires = array(
'MantisCore' => '1.3.0',
);
$this->author = 'Robin van Nunen & Wizardry and Steamworks';
$this->contact = 'robin@vnunen.nl / office@grimore.org';
$this->url = 'https://github.com/TechGuard/MantisBT-Discord';
}
function install()
{
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
plugin_error(ERROR_PHP_VERSION, ERROR);
return false;
}
if (!extension_loaded('curl')) {
plugin_error(ERROR_NO_CURL, ERROR);
return false;
}
return true;
}
function config()
{
return array(
'url_webhooks' => array(),
'url_webhook' => '',
'skip_bulk' => true,
'link_names' => true,
'language' => 'english',
'usernames' => array(),
'hook_bug_report' => true,
'hook_bug_update' => true,
'hook_bug_deleted' => true,
'hook_bugnote_add' => true,
'hook_bugnote_edit' => true,
'hook_bugnote_deleted' => true,
'columns' => array(
'status',
'handler_id',
'priority',
'severity',
'description',
),
);
}
function hooks()
{
return array(
'EVENT_REPORT_BUG' => 'bug_report',
'EVENT_UPDATE_BUG' => 'bug_update',
'EVENT_BUG_DELETED' => 'bug_deleted',
'EVENT_BUG_ACTION' => 'bug_action',
'EVENT_BUGNOTE_ADD' => 'bugnote_add_edit',
'EVENT_BUGNOTE_EDIT' => 'bugnote_add_edit',
'EVENT_BUGNOTE_DELETED' => 'bugnote_deleted',
'EVENT_BUGNOTE_ADD_FORM' => 'bugnote_add_form',
);
}
function bugnote_add_form($event, $bug_id)
{
if ($_SERVER['PHP_SELF'] !== '/bug_update_page.php') {
return;
}
echo '
';
echo '' . plugin_lang_get('skip') . ' | ';
echo '';
echo '';
echo ' |
';
}
function bug_report_update($event, $bug, $bug_id)
{
lang_push(plugin_config_get('language'));
$this->skip = $this->skip || gpc_get_bool('slack_skip') || $bug->view_state == VS_PRIVATE;
$project = project_get_name($bug->project_id);
$url = string_get_bug_view_url_with_fqdn($bug_id);
$summary = $this->format_summary($bug);
$reporter = $this->get_user_name(auth_get_current_user_id());
$handler = $this->format_value($bug, 'handler_id');
// TODO: The page anchors do not work.
$msg = sprintf(
plugin_lang_get($event === 'EVENT_REPORT_BUG' ? 'bug_created' : 'bug_updated'),
$project,
$reporter,
$url,
$summary,
$handler
);
$attachments = array('color' => hexdec('ef2929'));
$t_columns = (array) plugin_config_get('columns');
foreach ($t_columns as $t_column) {
$title = column_get_title($t_column);
$value = $this->format_value($bug, $t_column);
if ($title && $value) {
$attachments['fallback'] .= $title . ': ' . $value . "\n";
$attachments['fields'][] = array(
'name' => $title,
'value' => $value,
'inline' => !column_is_extended($t_column),
);
}
}
$this->notify($msg, $this->get_webhook($project), $attachments);
lang_pop();
}
function bug_report($event, $bug, $bug_id)
{
if (plugin_config_get('hook_bug_report', false)) {
$this->bug_report_update($event, $bug, $bug_id);
}
}
function bug_update($event, $bug_existing, $bug_updated)
{
if (plugin_config_get('hook_bug_update', false)) {
$this->bug_report_update($event, $bug_updated, $bug_updated->id);
}
}
function bug_action($event, $action, $bug_id)
{
$this->skip = $this->skip || gpc_get_bool('slack_skip') || plugin_config_get('skip_bulk');
if ($action !== 'DELETE') {
$bug = bug_get($bug_id);
$this->bug_update('EVENT_UPDATE_BUG', null, $bug);
}
}
function bug_deleted($event, $bug_id)
{
if (!plugin_config_get('hook_bug_deleted', false)) {
return;
}
lang_push(plugin_config_get('language'));
$bug = bug_get($bug_id);
$this->skip = $this->skip || gpc_get_bool('slack_skip') || $bug->view_state == VS_PRIVATE;
$project = project_get_name($bug->project_id);
$reporter = $this->get_user_name(auth_get_current_user_id());
$summary = $this->format_summary($bug);
// TODO: The page anchors do not work.
$msg = sprintf(plugin_lang_get('bug_deleted'), $project, $reporter, $summary);
$attachments = array('color' => hexdec('ef2929'));
$attachments['title'] = $msg;
$this->notify(null, $this->get_webhook($project), $attachments);
lang_pop();
}
function bugnote_add_edit($event, $bug_id, $bugnote_id)
{
$type = ($event === 'EVENT_BUGNOTE_ADD') ? 'add' : 'edit';
if (!plugin_config_get('hook_bugnote_' . $type, false)) {
return;
}
lang_push(plugin_config_get('language'));
$bug = bug_get($bug_id);
$bugnote = bugnote_get($bugnote_id);
$this->skip = $this->skip || gpc_get_bool('slack_skip') || $bug->view_state == VS_PRIVATE || $bugnote->view_state == VS_PRIVATE;
$url = string_get_bugnote_view_url_with_fqdn($bug_id, $bugnote_id);
$project = project_get_name($bug->project_id);
$summary = $this->format_summary($bug);
$reporter = $this->get_user_name(auth_get_current_user_id());
$note = bugnote_get_text($bugnote_id);
// TODO: The page anchors do not work.
$msg = sprintf(
plugin_lang_get($event === 'EVENT_BUGNOTE_ADD' ? 'bugnote_created' : 'bugnote_updated'),
$project,
$reporter,
$url,
$summary
);
$attachments = array('color' => hexdec("3366ff"));
$attachments['title'] = $msg . ' : ' . $this->bbcode_to_discord($note);
$this->notify(null, $this->get_webhook($project), $attachments);
lang_pop();
}
function bugnote_deleted($event, $bug_id, $bugnote_id)
{
if (!plugin_config_get('hook_bugnote_deleted', false)) {
return;
}
lang_push(plugin_config_get('language'));
$bug = bug_get($bug_id);
$bugnote = bugnote_get($bugnote_id);
$this->skip = $this->skip || gpc_get_bool('slack_skip') || $bug->view_state == VS_PRIVATE || $bugnote->view_state == VS_PRIVATE;
$project = project_get_name($bug->project_id);
$url = string_get_bug_view_url_with_fqdn($bug_id);
$summary = $this->format_summary($bug);
$reporter = $this->get_user_name(auth_get_current_user_id());
// TODO: The page anchors do not work.
$msg = sprintf(plugin_lang_get('bugnote_deleted'), $project, $reporter, $url, $summary);
$attachments = array('color' => hexdec("3366ff"));
$attachments['title'] = $msg;
$this->notify(null, $this->get_webhook($project), $attachments);
lang_pop();
}
function format_summary($bug)
{
$summary = bug_format_id($bug->id) . ': ' . string_display_line_links($bug->summary);
return strip_tags(html_entity_decode($summary));
}
function format_text($bug, $text)
{
$t = string_display_line_links($this->bbcode_to_discord($text));
return strip_tags(html_entity_decode($t));
}
function format_value($bug, $field_name)
{
$self = $this;
$values = array(
'id' => function ($bug) {
return sprintf('<%s|%s>', string_get_bug_view_url_with_fqdn($bug->id), $bug->id);
},
'project_id' => function ($bug) {
return project_get_name($bug->project_id);
},
'reporter_id' => function ($bug) {
return $this->get_user_name($bug->reporter_id, true);
},
'handler_id' => function ($bug) {
return empty($bug->handler_id) ? plugin_lang_get('no_user') : $this->get_user_name($bug->handler_id, true);
},
'duplicate_id' => function ($bug) {
return sprintf('<%s|%s>', string_get_bug_view_url_with_fqdn($bug->duplicate_id), $bug->duplicate_id);
},
'priority' => function ($bug) {
return get_enum_element('priority', $bug->priority);
},
'severity' => function ($bug) {
return get_enum_element('severity', $bug->severity);
},
'reproducibility' => function ($bug) {
return get_enum_element('reproducibility', $bug->reproducibility);
},
'status' => function ($bug) {
return get_enum_element('status', $bug->status);
},
'resolution' => function ($bug) {
return get_enum_element('resolution', $bug->resolution);
},
'projection' => function ($bug) {
return get_enum_element('projection', $bug->projection);
},
'category_id' => function ($bug) {
return category_full_name($bug->category_id, false);
},
'eta' => function ($bug) {
return get_enum_element('eta', $bug->eta);
},
'view_state' => function ($bug) {
return $bug->view_state == VS_PRIVATE ? lang_get('private') : lang_get('public');
},
'sponsorship_total' => function ($bug) {
return sponsorship_format_amount($bug->sponsorship_total);
},
'os' => function ($bug) {
return $bug->os;
},
'os_build' => function ($bug) {
return $bug->os_build;
},
'platform' => function ($bug) {
return $bug->platform;
},
'version' => function ($bug) {
return $bug->version;
},
'fixed_in_version' => function ($bug) {
return $bug->fixed_in_version;
},
'target_version' => function ($bug) {
return $bug->target_version;
},
'build' => function ($bug) {
return $bug->build;
},
'summary' => function ($bug) use ($self) {
return $self->format_summary($bug);
},
'last_updated' => function ($bug) {
return date(config_get('short_date_format'), $bug->last_updated);
},
'date_submitted' => function ($bug) {
return date(config_get('short_date_format'), $bug->date_submitted);
},
'due_date' => function ($bug) {
return date(config_get('short_date_format'), $bug->due_date);
},
'description' => function ($bug) use ($self) {
return $self->format_text($bug, $bug->description);
},
'steps_to_reproduce' => function ($bug) use ($self) {
return $self->format_text($bug, $bug->steps_to_reproduce);
},
'additional_information' => function ($bug) use ($self) {
return $self->format_text($bug, $bug->additional_information);
},
);
// Discover custom fields.
$t_related_custom_field_ids = custom_field_get_linked_ids($bug->project_id);
foreach ($t_related_custom_field_ids as $t_id) {
$t_def = custom_field_get_definition($t_id);
$values['custom_' . $t_def['name']] = function ($bug) use ($t_id) {
return custom_field_get_value($t_id, $bug->id);
};
}
if (isset($values[$field_name])) {
$func = $values[$field_name];
return $func($bug);
} else {
return false;
}
}
function get_webhook($project)
{
$webhooks = plugin_config_get('url_webhooks');
return array_key_exists($project, $webhooks) ? $webhooks[$project] : plugin_config_get('url_webhook');
}
function notify($msg, $webhook, $attachments)
{
if ($this->skip) {
return;
}
if (empty($webhook)) {
return;
}
$payload['content'] = $msg;
if ($attachments != null) {
$payload['embeds'] = array($attachments);
}
$ch = curl_init($webhook);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=utf-8']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
$result = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) !== 204 && $result !== 'ok') {
trigger_error(curl_errno($ch) . ': ' . curl_error($ch) . ": ", E_USER_WARNING);
echo '' . json_encode($payload, JSON_PRETTY_PRINT) . '
';
echo '' . $result . '
';
plugin_error('ERROR_CURL', E_USER_ERROR);
}
curl_close($ch);
}
function bbcode_to_discord($bbtext)
{
$bbextended = array(
"/\[code(.*?)\](.*?)\[\/code\]/is" => "`$2`",
"/\[list(.*?)\](.*?)\[\/list\]/is" => "$2",
"/\[color(.*?)\](.*?)\[\/color\]/is" => "$2",
"/\[size=(.*?)\](.*?)\[\/size\]/is" => "$2",
"/\[highlight(.*?)\](.*?)\[\/highlight\]/is" => "$2",
"/\[url](.*?)\[\/url]/i" => "$1",
"/\[url=(.*?)\](.*?)\[\/url\]/i" => "$1",
"/\[email=(.*?)\](.*?)\[\/email\]/i" => "$1",
"/\[img\]([^[]*)\[\/img\]/i" => "$1",
);
foreach ($bbextended as $match => $replacement) {
$bbtext = preg_replace($match, $replacement, $bbtext);
}
$bbtags = array(
'[b]' => '*', '[/b]' => '** ',
'[i]' => '*', '[/i]' => '* ',
'[u]' => '__', '[/u]' => '__ ',
'[s]' => '~~', '[/s]' => '~~ ',
'[sup]' => '', '[/sup]' => '',
'[sub]' => '', '[/sub]' => '',
'[list]' => '', '[/list]' => "\n",
'[*]' => 'ā¢ ',
'[hr]' => "\nāāā\n",
'[left]' => '', '[/left]' => '',
'[right]' => '', '[/right]' => '',
'[center]' => '', '[/center]' => '',
'[justify]' => '', '[/justify]' => '',
);
$bbtext = str_ireplace(array_keys($bbtags), array_values($bbtags), $bbtext);
$bbtext = preg_replace_callback(
"/\[quote(=)?(.*?)\](.*?)\[\/quote\]/is",
function ($matches) {
if (!empty($matches[2])) {
$result = "\n> _*" . $matches[2] . "* wrote:_\n> \n";
}
$lines = explode("\n", $matches[3]);
foreach ($lines as $line) {
$result .= "> " . $line . "\n";
}
return $result;
},
$bbtext
);
return $bbtext;
}
function get_user_name($user_id, $discord = false)
{
$user = user_get_row($user_id);
$username = $user['username'];
if (!$discord || !plugin_config_get('link_names')) {
return $username;
}
$usernames = plugin_config_get('usernames');
if (array_key_exists($username, $usernames)) {
return '<@' . $usernames[$username] . '>';
}
return $username;
}
}