/
mikopbx
/
ModuleBeelinePbx
Обзор
Документация
Войти
/
mikopbx
/
ModuleBeelinePbx
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
master
bin/manageSubscription.php
188 строк
8 KB
boffart
Инициализация модуля ModuleBeelinePbx
08 июл 2026, 17:11
08 июл 2026, 17:11
1cc4e1a
Код
Авторство
О чём код?
<?php /* * MikoPBX - free phone system for small business * Copyright © 2017-2024 Alexey Portnov and Nikolay Beketov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. * If not, see <https://www.gnu.org/licenses/>. */ use MikoPBX\Core\System\Util; use Modules\ModuleBeelinePbx\Lib\BeelineApi; use Modules\ModuleBeelinePbx\Lib\Logger; use Modules\ModuleBeelinePbx\Models\ModuleBeelinePbx; require_once 'Globals.php'; // Желаемая длительность запрашиваемой подписки (сек). Билайн может выдать меньше — // тогда используем фактический expires из ответа. const BEELINE_SUBSCRIPTION_TTL = 86400; // 24 часа // За сколько секунд до истечения переподписываемся (должно превышать интервал cron-задачи). const BEELINE_SUBSCRIPTION_RENEW_MARGIN = 1800; // 30 минут // Пакет событий BroadWorks Xsi. В XML-схеме это <event> из enum EventPackage // («Advanced Call» — события жизненного цикла вызова). ЭМПИРИЧЕСКИ: JSON-поле Билайн // для пакета — НЕ 'subscriptionType' (любое его значение → HTTP 400 BadRequest, // строгая схема отклоняет неизвестное поле). Пустое → запрос проходит валидацию, но // подписка «на весь клиент» падает с SubscriptionError (нужен конкретный абонент — // targetId, см. ниже BEELINE_SUBSCRIPTION_PATTERN). Оставляем пустым до уточнения // точного JSON-контракта Билайн. const BEELINE_SUBSCRIPTION_TYPE = ''; // Абонент/номер (targetId) для подписки. Пусто — «весь клиент» (Билайн отвечает // SubscriptionError, т.к. per-user — штатный режим BroadWorks). Заполнить userId/номером, // когда будет подтверждён формат pattern у Билайн. const BEELINE_SUBSCRIPTION_PATTERN = ''; $pidFile = '/var/run/beeline-subscription.pid'; $logger = new Logger('Subscription', 'ModuleBeelinePbx'); // Перехватываем все необработанные исключения — иначе MikoPBX может автоматически // отключить модуль из-за ошибки в воркере крона. set_exception_handler(static function (\Throwable $e) use ($logger) { $logger->writeError([ 'exception' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), ], 'Uncaught exception in manageSubscription.php'); exit(1); }); /** * Single-instance guard: не запускаем второй экземпляр, пока работает предыдущий. */ function beelineSubscriptionProcessExists(string $pidFile): bool { $pid = posix_getpid(); if (file_exists($pidFile)) { $psPath = Util::which('ps'); $busyboxPath = Util::which('busybox'); $oldPid = trim(file_get_contents($pidFile)); $output = trim(shell_exec("$psPath -A -o 'pid' | $busyboxPath awk '{print $1}' | $busyboxPath grep '^$oldPid\$'") ?? ''); if (!empty($output)) { echo "Old PID: $oldPid is still running...".PHP_EOL; return true; } } file_put_contents($pidFile, $pid); return false; } /** * Сохраняет настройки, тихо логируя ошибку (чтобы не свалить cron-воркер). */ function beelineSaveSettings(ModuleBeelinePbx $settings, Logger $logger): void { if ($settings->save() === false) { $logger->writeError($settings->getMessages(), 'Fail save subscription settings'); } } if (beelineSubscriptionProcessExists($pidFile)) { echo "Process exists...".PHP_EOL; exit(12); } $settings = ModuleBeelinePbx::findFirst(); if (!$settings) { echo "empty settings".PHP_EOL; exit(1); } $enabled = (int)$settings->pushSubscriptionEnabled === 1; $currentId = (string)$settings->subscriptionId; // --- Подписка выключена: снимаем активную, если она есть, и выходим. --- if (!$enabled) { if ($currentId !== '') { $api = new BeelineApi((string)$settings->authApiKey, $logger); $api->deleteSubscription($currentId); $settings->subscriptionId = ''; $settings->subscriptionExpiresAt = ''; $settings->subscriptionError = ''; beelineSaveSettings($settings, $logger); $logger->writeInfo(['subscriptionId' => $currentId], 'Subscription disabled, unsubscribed'); } exit(0); } // --- Подписка включена: проверяем предпосылки. --- if (empty($settings->authApiKey)) { $settings->subscriptionError = 'Не задан токен API'; beelineSaveSettings($settings, $logger); $logger->writeError('Cannot subscribe: empty authApiKey'); exit(1); } if (empty($settings->pbxPublicUrl)) { $settings->subscriptionError = 'Не задан публичный адрес АТС'; beelineSaveSettings($settings, $logger); $logger->writeError('Cannot subscribe: empty pbxPublicUrl'); exit(1); } $callbackUrl = rtrim((string)$settings->pbxPublicUrl, '/') . '/pbxcore/beeline-pbx/event'; $now = time(); $expiresAt = (int)$settings->subscriptionExpiresAt; $needSubscribe = ($currentId === '') || ($expiresAt <= $now + BEELINE_SUBSCRIPTION_RENEW_MARGIN); if (!$needSubscribe) { $logger->writeInfo([ 'subscriptionId' => $currentId, 'expiresInSec' => $expiresAt - $now, ], 'Subscription still valid, nothing to do'); exit(0); } $api = new BeelineApi((string)$settings->authApiKey, $logger); $result = $api->createSubscription( $callbackUrl, BEELINE_SUBSCRIPTION_TTL, BEELINE_SUBSCRIPTION_PATTERN, BEELINE_SUBSCRIPTION_TYPE ); if ($result === null || empty($result['subscriptionId'])) { $settings->subscriptionError = 'Не удалось создать подписку (см. Subscription.log)'; beelineSaveSettings($settings, $logger); $logger->writeError(['callbackUrl' => $callbackUrl], 'Subscription request failed'); exit(1); } $newId = (string)$result['subscriptionId']; // expires в ответе — длительность (сек). Считаем абсолютный момент истечения. $grantedTtl = (int)($result['expires'] ?? BEELINE_SUBSCRIPTION_TTL); if ($grantedTtl <= 0) { $grantedTtl = BEELINE_SUBSCRIPTION_TTL; } $settings->subscriptionId = $newId; $settings->subscriptionExpiresAt = (string)($now + $grantedTtl); $settings->subscriptionError = ''; beelineSaveSettings($settings, $logger); $logger->writeInfo([ 'subscriptionId' => $newId, 'ttl' => $grantedTtl, 'callbackUrl' => $callbackUrl, ], 'Subscription created/renewed'); // Снимаем старую подписку, чтобы не плодить дубликаты, шлющие на тот же callback. if ($currentId !== '' && $currentId !== $newId) { $api->deleteSubscription($currentId); $logger->writeInfo(['subscriptionId' => $currentId], 'Old subscription removed'); } exit(0);