Passed
Push — develop ( 5a71b5...d30961 )
by Nikolay
22:52
created

ModulesControllerBase::handleResponse()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 21
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 21
rs 8.8333
c 0
b 0
f 0
cc 7
nc 7
nop 1
1
<?php
2
3
namespace MikoPBX\PBXCoreREST\Controllers\Modules;
4
5
use MikoPBX\Common\Handlers\CriticalErrorsHandler;
6
use MikoPBX\Common\Providers\BeanstalkConnectionWorkerApiProvider;
7
use MikoPBX\PBXCoreREST\Controllers\BaseController;
8
use MikoPBX\PBXCoreREST\Http\Response;
9
use MikoPBX\PBXCoreREST\Lib\PbxExtensionsProcessor;
10
use Pheanstalk\Contract\PheanstalkInterface;
11
12
/**
13
 * Base controller for handling module-related actions.
14
 *
15
 * @package MikoPBX\PBXCoreREST\Controllers\Modules
16
 *
17
 * @RoutePrefix("/pbxcore/api/modules/{moduleUniqueID}/{action}")
18
 *
19
 * @example
20
 * API for additional modules.
21
 * Module check:
22
 *   curl http://127.0.0.1/pbxcore/api/modules/ModuleSmartIVR/check
23
 *   curl http://127.0.0.1/pbxcore/api/modules/ModuleCTIClient/check
24
 *   curl http://127.0.0.1/pbxcore/api/modules/ModuleTelegramNotify/check
25
 *   curl http://127.0.0.1/pbxcore/api/modules/ModuleBitrix24Notify/check
26
 *   curl http://127.0.0.1/pbxcore/api/modules/ModuleBitrix24Integration/check
27
 *
28
 * Module restart with config regeneration:
29
 *   curl http://127.0.0.1/pbxcore/api/modules/ModuleSmartIVR/reload
30
 *   curl http://127.0.0.1/pbxcore/api/modules/ModuleCTIClient/reload
31
 *   curl http://127.0.0.1/pbxcore/api/modules/ModuleBitrix24Integration/reload
32
 *
33
 * Execution of actions without main authorization.
34
 * curl http://127.0.0.1/pbxcore/api/modules/ModuleAutoprovision/getcfg?mac=00135E874B49&solt=test
35
 * curl http://127.0.0.1/pbxcore/api/modules/ModuleAutoprovision/getimg?file=logo-yealink-132x32.dob
36
 *
37
 * curl http://84.201.142.45/pbxcore/api/modules/ModuleBitrix24Notify/customAction?portal=b24-uve4uz.bitrix24.ru
38
 * curl http://84.201.142.45/pbxcore/api/modules/ModuleBitrix24Notify/customAction?portal=miko24.ru
39
 * curl http://84.201.142.45/pbxcore/api/modules/ModuleBitrix24Notify/customAction
40
 *
41
 */
42
class ModulesControllerBase extends BaseController
43
{
44
    /**
45
     * Handles the call action for a specific module.
46
     *
47
     * @param string $moduleName The name of the module.
48
     * @param string $actionName The name of the action.
49
     * @return void
50
     */
51
    public function callActionForModule(string $moduleName, string $actionName): void
52
    {
53
        $maxTimeout = max(10, $this->request->getRequestTimeout());
54
        $priority = max(PheanstalkInterface::DEFAULT_PRIORITY, $this->request->getRequestPriority());
55
        // Old style modules, we can remove it after 2025
56
        $_REQUEST['ip_srv'] = $_SERVER['SERVER_ADDR'];
57
58
        $payload = file_get_contents('php://input');
59
        list($debug, $requestMessage) = $this->prepareRequestMessage(
60
            PbxExtensionsProcessor::class,
61
            $payload,
62
            $actionName,
63
            $moduleName
64
        );
65
66
        try {
67
            $message = json_encode($requestMessage, JSON_THROW_ON_ERROR);
68
            /** @var BeanstalkConnectionWorkerApiProvider $beanstalkQueue */
69
            $beanstalkQueue = $this->di->getShared(BeanstalkConnectionWorkerApiProvider::SERVICE_NAME);
70
            if ($debug) {
71
                $maxTimeout = 9999;
72
            }
73
            $response = $beanstalkQueue->request($message, $maxTimeout, $priority);
74
75
            if ($response !== false) {
76
                $response = json_decode($response, true);
77
                $this->handleResponse($response);
78
            } else {
79
                $this->sendError(Response::INTERNAL_SERVER_ERROR);
80
            }
81
        } catch (\Throwable $e) {
82
            CriticalErrorsHandler::handleExceptionWithSyslog($e);
83
            $this->sendError(Response::BAD_REQUEST, $e->getMessage());
84
        }
85
    }
86
87
    /**
88
     * Prepare a request message for sending to a backend worker.
89
     *
90
     * @param string $processor
91
     * @param mixed $payload
92
     * @param string $actionName
93
     * @param string $moduleName
94
     * @return array
95
     */
96
    public function prepareRequestMessage(
97
        string $processor,
98
        mixed $payload,
99
        string $actionName,
100
        string $moduleName
101
    ): array {
102
103
        $requestMessage = [
104
            'data' => $_REQUEST,
105
            'module' => $moduleName,
106
            'input' => $payload,
107
            'action' => $actionName,
108
            'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
109
            'processor' => $processor,
110
            'async' => false,
111
            'asyncChannelId' => ''
112
        ];
113
114
        if ($this->request->isAsyncRequest()) {
115
            $requestMessage['async'] = true;
116
            $requestMessage['asyncChannelId'] = $this->request->getAsyncRequestChannelId();
117
        }
118
119
        $requestMessage['debug'] = $this->request->isDebugRequest();
120
        return [$requestMessage['debug'], $requestMessage];
121
    }
122
123
    /**
124
     * Handles the response from the backend worker.
125
     *
126
     * @param array $response
127
     * @return void
128
     */
129
    private function handleResponse(array $response): void
130
    {
131
        if (isset($response['data']['fpassthru'])) {
132
            $this->handleFilePassThrough($response['data']);
133
        } elseif (isset($response['html'])) {
134
            echo $response['html'];
135
            $this->response->sendRaw();
136
        } elseif (isset($response['redirect'])) {
137
            $this->response->redirect($response['redirect'], true);
138
            $this->response->sendRaw();
139
        } elseif (isset($response['echo'], $response['headers'])) {
140
            foreach ($response['headers'] as $name => $value) {
141
                $this->response->setHeader($name, $value);
142
            }
143
            $this->response->setPayloadSuccess($response['echo']);
144
        } elseif (isset($response['echo_file'])) {
145
            $this->response->setStatusCode(Response::OK, 'OK')->sendHeaders();
146
            $this->response->setFileToSend($response['echo_file']);
147
            $this->response->sendRaw();
148
        } else {
149
            $this->response->setPayloadSuccess($response);
150
        }
151
    }
152
153
    /**
154
     * Handles file pass-through responses.
155
     *
156
     * @param array $data
157
     * @return void
158
     */
159
    private function handleFilePassThrough(array $data): void
160
    {
161
        $filename = $data['filename'] ?? '';
162
        $fp = fopen($filename, "rb");
163
        if ($fp !== false) {
164
            $size = filesize($filename);
165
            $name = basename($filename);
166
            $this->response->setHeader('Content-Description', "config file");
167
            $this->response->setHeader('Content-Disposition', "attachment; filename=$name");
168
            $this->response->setHeader('Content-Type', "text/plain");
169
            $this->response->setHeader('Content-Transfer-Encoding', "binary");
170
            $this->response->setContentLength($size);
171
            $this->response->sendHeaders();
172
            fpassthru($fp);
173
            fclose($fp);
174
        }
175
        if (!empty($data['need_delete'])) {
176
            unlink($filename);
177
        }
178
    }
179
}
180