#!/usr/bin/php dateAndTimeCheck(); $oncall->retrieveItems(); if (!$oncall->notify) { exit(0); } $oncall->setEmail(); $table = $oncall->buildMattermostTable(); if ($table) { $oncall->sendToMattermost($table); } /** * The class that does all the magic. */ class oncall { /** * Are we in debug mode? * @var bool */ private $debugMode = FALSE; /** * A flag to indicate that everyone should be notified. * @var bool */ private $everyone = FALSE; /** * The URL of the Redmine install. * @var string */ private $baseUrl; /** * The Redmine API key. * @var string */ private $apiKey; /** * An array containing queries we want to run, with a label as a key. * @var array */ private $queries; /** * An array of the query results from Redmine. Key is the query label. * @var array */ private $queryResults = []; /** * The email of the person on call. * @var string; */ private $email; /** * A flag of whether Mattermost should be contacted. * @var bool */ public $notify; public function __construct() { // Load the .env file $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->load(); // Debug Mode will show more information. $this->debugMode = (bool) getenv('DEBUG_MODE'); $this->baseUrl = getenv('REDMINE_URL'); $this->apiKey = getenv('REDMINE_API_KEY'); // FIXME: Not using $this->apiKey. $this->queries = [ 'New Support Tickets' => 'https://hq.megaphonetech.com/issues.json?query_id=17&key=7ebe204bef5804f4effb9b4160a295487dde15f1', 'New Maintenance Tickets' => 'https://hq.megaphonetech.com/issues.json?query_id=18&key=7ebe204bef5804f4effb9b4160a295487dde15f1', ]; } /** * Add the number of minutes since a ticket was created. * Also set the everyone and notify flags if necessary. * @return int */ private function calculateMinutes(array $item) { // find the time since the ticket was created $created = $item['created_on']; $time = strtotime("$created"); $time_difference = strtotime('now') - $time; // put it into minutes $td_min = (int) ($time_difference / 60); // Flag if any items are overdue. if ($td_min >= 60) { $this->everyone = TRUE; } // Notify every 30 minutes. if (($td_min % 30) < 3) { $this->notify = TRUE; } return $td_min; } /** * Get items from Redmine. */ public function retrieveItems() { foreach ($this->queries as $title => $url) { // get the contents of the query in a usable format $json = json_decode(file_get_contents($url), TRUE); // if the query has any results if (!empty($json)) { // go through this loop for each ticket found foreach ($json['issues'] as $k => $item) { $item['minutes'] = $this->calculateMinutes($item); $this->queryResults[$title][] = $item; } } } } /** * Render the $items property in Markdown. * @return string */ public function buildMattermostTable() { $table = ''; foreach ($this->queryResults as $label => $queryResult) { //Add header row $table .= "\n### $label\n"; $table .= "| Issue | Project | Author | Subject | Minutes Elapsed |\n| :----- | :------- | :----- | :-------| ----: |\n"; foreach ($queryResult as $item) { $table .= "| [{$item['id']}](https://hq.megaphonetech.com/issues/{$item['id']}) "; $table .= "| {$item['project']['name']} "; $table .= "| {$item['author']['name']} "; $table .= "| {$item['subject']} "; $table .= "| {$item['minutes']} "; $table .= "|\n"; } } return $table; } public function sendToMattermost($table) { $mattermostName = $this->findMattermostName(); $url = 'https://chat.civicrm.org/hooks/4d7wtzzmj3rjmqpjg9z9nfqwjo'; $data['channel'] = 'Megaphone'; $data['username'] = 'megaphone-bot'; $data['icon_url'] = 'http://oncall.megaphonetech.com/oncallbot.jpg'; $data['text'] = "@$mattermostName\n\n$table"; $json = json_encode($data); // use key 'http' even if you send the request to https://... $options = [ 'http' => [ 'header' => "Content-type: application/json\r\n", 'method' => 'POST', 'content' => $json, ], ]; $context = stream_context_create($options); $result = file_get_contents($url, FALSE, $context); if ($result === FALSE) { print_r($result); } } /** * Return a Mattermost name from email, or "@channel" if there's an everyone ticket. * @return string */ private function findMattermostName() { if ($this->everyone) { return 'channel'; } // Check users.csv column 1 for email; return column 2 if it matches. if (($handle = fopen(__DIR__ . "/users.csv", "r")) !== FALSE) { while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { if ($data[0] == $this->email) { fclose($handle); return $data[1]; } } } fclose($handle); return ''; } public function setEmail() { // path to file containing email of person on call $contactfile = __DIR__ . "/contact.txt"; // set the email of the person on call $this->email = trim(file_get_contents("$contactfile")); } /** * Checks whether we're responsible for on-call on this date and time. */ public function dateAndTimeCheck() { $today = new DateTime("now", new DateTimeZone('America/New_York')); $thisYear = (int) $today->format('Y'); $thisHour = (int) $today->format('H'); // Time check. if ($thisHour < 10 || $thisHour > 20) { // After hours. exit(0); } $holidays = Yasumi\Yasumi::create('USA', $thisYear); $holidays->removeHoliday('columbusDay'); $holidays->addHoliday(new Holiday('indigenousPeoplesDay', [ 'en' => 'Indigenous People\'s Day', ], new DateTime("second monday of october $thisYear", new DateTimeZone('America/New_York')))); $holidays->addHoliday(new Holiday('dayAfterThanksgiving', [ 'en' => 'Day After Thanksgiving', ], new DateTime("fourth friday of november $thisYear", new DateTimeZone('America/New_York')))); // Is today a holiday? if ($holidays->isHoliday($today)) { $this->everyone = TRUE; $todaysHoliday = $holidays->on($today); $holidayName = $todaysHoliday->getName(); $this->sendToMattermost("Happy $holidayName Megaphone Tech! There is no on-call today."); } // Don't do anything else if it's a holiday or weekend. if (!$holidays->isWorkingDay($today)) { exit(0); } } } /** * Send an email for this item. */ function email($item, $contact) { // if it's been an hour, email everyone instead if ($item['minutes'] > 60) { $contact = 'team@megaphonetech.com'; } // if it's been 25 minutes, make an email if ($item['minutes'] > 0) { // email body $email = "New issue to attend to:" . "\n"; $email .= "\n"; $email .= "Project: " . $item['project']['name'] . "\n"; $email .= "Author: " . $item['author']['name'] . "\n"; $email .= "Subject: " . $item['subject'] . "\n"; $email .= "\n"; $email .= $item['description'] . "\n"; $email .= "\n"; $email .= "Ticket created {$item['minutes']} minutes ago"; $email .= "\n"; $email .= 'https://hq.megaphonetech.com/issues/' . $item['id'] . "\n"; $email .= "\n"; // email headers $to = $contact; $subject = "[#{$item['id']}]"; $message = $email; $headers = 'From: support@megaphonetech.com' . "\r\n" . 'Reply-To: support@megaphonetech.com' . "\r\n" . 'X-Mailer: PHP/' . phpversion(); // send email mail($to, $subject, $message, $headers); } }