286 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			286 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
#!/usr/bin/php
 | 
						|
<?php
 | 
						|
declare(strict_types = 1);
 | 
						|
require_once 'vendor/autoload.php';
 | 
						|
 | 
						|
use Yasumi\Holiday;
 | 
						|
 | 
						|
/* On Call Ticket Notification Script */
 | 
						|
$oncall = new oncall();
 | 
						|
$oncall->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('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);
 | 
						|
 | 
						|
  }
 | 
						|
}
 |