survives image upgrades and is portable. */ namespace Modules\WecomCenter\Lib; use API; use Exception; class Helper { // Zabbix trigger severity index => media severity bit (1 << index). public const SEVERITY_BITS = [1, 2, 4, 8, 16, 32]; // Built-in alert schemes: name => [severity_mask, active(0=enabled,1=disabled)] public const SCHEMES = [ 'standard' => [48, 0], // High + Disaster (recommended) 'warnplus' => [56, 0], // Average + High + Disaster 'all' => [63, 0], // everything 'quiet' => [32, 0], // Disaster only 'off' => [63, 1] // alerting disabled ]; /* ------------------------------------------------------------------ */ /* Media types / robot */ /* ------------------------------------------------------------------ */ /** * All webhook media types (type == 4). */ public static function webhookMediatypes(): array { try { $res = API::Mediatype()->get([ 'output' => ['mediatypeid', 'name', 'type', 'status'], 'selectParameters' => 'extend', 'filter' => ['type' => 4] ]); return is_array($res) ? $res : []; } catch (\Throwable $e) { return []; } } public static function getMediatype(string $mediatypeid): ?array { try { $res = API::Mediatype()->get([ 'output' => ['mediatypeid', 'name', 'type', 'status'], 'selectParameters' => 'extend', 'mediatypeids' => [$mediatypeid] ]); return (is_array($res) && $res) ? $res[0] : null; } catch (\Throwable $e) { return null; } } /** * Read the webhook URL / key from a media type parameters list. */ public static function webhookUrl(array $mediatype): string { foreach (($mediatype['parameters'] ?? []) as $p) { $name = strtolower($p['name']); if (in_array($name, ['webhook_url', 'url', 'webhook'], true)) { return (string) $p['value']; } } return ''; } /** * Update (or add) the webhook_url parameter of a media type, preserving others. */ public static function setWebhookUrl(string $mediatypeid, string $url): void { $mt = self::getMediatype($mediatypeid); if (!$mt) { throw new Exception('Media type not found: '.$mediatypeid); } $params = []; $found = false; foreach (($mt['parameters'] ?? []) as $p) { if (in_array(strtolower($p['name']), ['webhook_url', 'url', 'webhook'], true)) { $params[] = ['name' => $p['name'], 'value' => $url]; $found = true; } else { $params[] = ['name' => $p['name'], 'value' => $p['value']]; } } if (!$found) { $params[] = ['name' => 'webhook_url', 'value' => $url]; } API::Mediatype()->update(['mediatypeid' => $mediatypeid, 'parameters' => $params]); } /* ------------------------------------------------------------------ */ /* Robot user + media (severity / active) */ /* ------------------------------------------------------------------ */ /** * Find the dedicated robot user with its media. */ public static function robotUser(string $username = 'wecomrobot'): ?array { $res = API::User()->get([ 'output' => ['userid', 'username'], 'selectMedias' => 'extend', 'filter' => ['username' => $username] ]); return $res ? $res[0] : null; } /** * Current severity mask + active flag of a user's media for a media type. * Returns ['active'=>int, 'severity'=>int] or null when media absent. */ public static function currentMedia(?array $user, string $mediatypeid): ?array { if (!$user || empty($user['medias'])) { return null; } foreach ($user['medias'] as $m) { if ((string) $m['mediatypeid'] === (string) $mediatypeid) { return [ 'active' => (int) $m['active'], 'severity' => (int) $m['severity'], 'period' => $m['period'] ?? '1-7,00:00-24:00', 'sendto' => $m['sendto'] ?? '' ]; } } return null; } /** * Ensure a robot user exists; create one (no frontend access) if missing. */ public static function ensureRobotUser(string $username): array { $user = self::robotUser($username); if ($user) { return $user; } // best-effort group / role for a non-login notification account $grp = API::UserGroup()->get([ 'output' => ['usrgrpid', 'name'], 'filter' => ['name' => 'No access to the frontend'], 'limit' => 1 ]); $usrgrpid = $grp ? $grp[0]['usrgrpid'] : null; if ($usrgrpid === null) { $all = API::UserGroup()->get(['output' => ['usrgrpid'], 'limit' => 1]); $usrgrpid = $all ? $all[0]['usrgrpid'] : null; } $role = API::Role()->get([ 'output' => ['roleid', 'name'], 'filter' => ['name' => 'User role'], 'limit' => 1 ]); $roleid = $role ? $role[0]['roleid'] : null; $create = [ 'username' => $username, 'passwd' => bin2hex(random_bytes(16)).'Aa1!', 'usrgrps' => [['usrgrpid' => $usrgrpid]] ]; if ($roleid !== null) { $create['roleid'] = $roleid; } $res = API::User()->create($create); return self::robotUser($username); } /** * Apply severity mask + active flag to a user's media for a media type. * Rebuilds the whole media list (API user.update replaces medias). */ public static function applyMedia(string $userid, string $mediatypeid, int $severity, int $active, string $sendto = 'WeCom') { $res = API::User()->get([ 'output' => ['userid'], 'selectMedias' => 'extend', 'userids' => [$userid] ]); $medias = []; if ($res) { foreach (($res[0]['medias'] ?? []) as $m) { $medias[] = [ 'mediatypeid' => $m['mediatypeid'], 'sendto' => $m['sendto'], 'severity' => (string) $m['severity'], 'active' => (string) $m['active'], 'period' => $m['period'] ?: '1-7,00:00-24:00' ]; } } $hit = false; foreach ($medias as &$m) { if ((string) $m['mediatypeid'] === (string) $mediatypeid) { $m['severity'] = (string) $severity; $m['active'] = (string) $active; if ($sendto !== '') { $m['sendto'] = $sendto; } $hit = true; } } unset($m); if (!$hit) { $medias[] = [ 'mediatypeid' => $mediatypeid, 'sendto' => $sendto !== '' ? $sendto : 'WeCom', 'severity' => (string) $severity, 'active' => (string) $active, 'period' => '1-7,00:00-24:00' ]; } API::User()->update(['userid' => $userid, 'medias' => $medias]); } /* ------------------------------------------------------------------ */ /* Send test message */ /* ------------------------------------------------------------------ */ public static function testWebhook(string $url, string $text): array { $payload = json_encode(['msgtype' => 'text', 'text' => ['content' => $text]], JSON_UNESCAPED_UNICODE); $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $payload, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_TIMEOUT => 15, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => 0 ]); $body = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $err = curl_error($ch); curl_close($ch); return ['status' => $status, 'body' => $body !== false ? $body : $err]; } /* ------------------------------------------------------------------ */ /* Templates */ /* ------------------------------------------------------------------ */ /** * Bundled templates shipped inside modules/WecomCenter/templates/. */ public static function bundledTemplates(string $module_dir): array { $dir = rtrim($module_dir, '/').'/templates'; $out = []; if (!is_dir($dir)) { return $out; } foreach (scandir($dir) as $f) { if ($f === '.' || $f === '..') { continue; } $ext = strtolower(pathinfo($f, PATHINFO_EXTENSION)); if (in_array($ext, ['xml', 'yaml', 'yml', 'json'], true)) { $out[] = ['file' => $f, 'name' => pathinfo($f, PATHINFO_FILENAME)]; } } return $out; } public static function importTemplate(string $module_dir, string $file): array { $dir = rtrim($module_dir, '/').'/templates'; $file = basename($file); $path = $dir.'/'.$file; if (!is_file($path)) { throw new Exception('Template file not found: '.$file); } $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); $format = ['xml' => 'xml', 'json' => 'json', 'yaml' => 'yaml', 'yml' => 'yaml'][$ext] ?? 'yaml'; $source = file_get_contents($path); $result = API::Configuration()->import([ 'format' => $format, 'source' => $source, 'rules' => [ 'template_groups' => ['createMissing' => true, 'updateExisting' => true], 'templates' => ['createMissing' => true, 'updateExisting' => true], 'items' => ['createMissing' => true, 'updateExisting' => true], 'triggers' => ['createMissing' => true, 'updateExisting' => true] ] ]); return is_array($result) ? $result : []; } /* ------------------------------------------------------------------ */ /* Map coordinates */ /* ------------------------------------------------------------------ */ public static function hosts(): array { return API::Host()->get([ 'output' => ['hostid', 'name'], 'sortfield' => 'name', 'limit' => 1000 ]); } public static function setHostLocation(string $hostid, string $lat, string $lon): void { API::Host()->update([ 'hostid' => $hostid, 'inventory_mode' => 0, // manual 'inventory' => [ 'location_lat' => $lat, 'location_lon' => $lon ] ]); } public static function hostLocation(string $hostid): array { $res = API::Host()->get([ 'output' => ['hostid', 'name'], 'selectInventory' => ['location_lat', 'location_lon'], 'hostids' => [$hostid] ]); return $res ? $res[0] : []; } /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ public static function severityMask(array $indices): int { $mask = 0; foreach ($indices as $i) { $i = (int) $i; if ($i >= 1 && $i <= 6) { $mask |= self::SEVERITY_BITS[$i - 1]; } } return $mask; } }