| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- <?php declare(strict_types = 0);
- /*
- ** WeCom Center - business logic helpers.
- ** Works through the official Zabbix API so every setting is stored in the
- ** Zabbix database itself -> 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;
- }
- /* ------------------------------------------------------------------ */
- /* Triggers (per-trigger alerting switch) */
- /* ------------------------------------------------------------------ */
- /**
- * Search triggers by description. An empty search returns nothing on
- * purpose - the instance has tens of thousands of triggers, listing all
- * is pointless. Results are capped.
- */
- public static function triggers(string $search, int $limit = 200): array {
- $search = trim($search);
- if ($search === '') {
- return [];
- }
- try {
- $res = API::Trigger()->get([
- 'output' => ['triggerid', 'description', 'status', 'priority'],
- 'selectHosts' => ['host'],
- 'search' => ['description' => $search],
- 'limit' => $limit
- ]);
- return is_array($res) ? $res : [];
- }
- catch (\Throwable $e) {
- return [];
- }
- }
- public static function triggerCount(): int {
- try {
- $n = API::Trigger()->get(['countOutput' => true]);
- return (int) $n;
- }
- catch (\Throwable $e) {
- return 0;
- }
- }
- /**
- * Set trigger status. $map = [triggerid => status(0 enabled,1 disabled)].
- * NB: this Zabbix version's trigger.update does NOT accept a "triggers"
- * array, so we update one trigger at a time.
- */
- public static function setTriggersStatus(array $map): int {
- $changed = 0;
- foreach ($map as $tid => $status) {
- $res = API::Trigger()->update([
- 'triggerid' => (string) $tid,
- 'status' => (string) ((int) $status)
- ]);
- if ($res === false) {
- throw new Exception('trigger.update failed for '.$tid);
- }
- $changed++;
- }
- return $changed;
- }
- }
|