Passed
Push — develop ( a59bc8...87c409 )
by Nikolay
13:24 queued 14s
created

BaseController::sanitizeData()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 22
rs 9.2222
c 0
b 0
f 0
cc 6
nc 6
nop 2
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
declare(strict_types=1);
21
22
namespace MikoPBX\PBXCoreREST\Controllers;
23
24
use MikoPBX\Common\Handlers\CriticalErrorsHandler;
25
use MikoPBX\Common\Providers\BeanstalkConnectionWorkerApiProvider;
26
use MikoPBX\Core\System\BeanstalkClient;
27
use MikoPBX\PBXCoreREST\Http\Response;
28
use MikoPBX\PBXCoreREST\Lib\PbxExtensionsProcessor;
29
use Phalcon\Filter;
30
use Phalcon\Mvc\Controller;
31
use Pheanstalk\Pheanstalk;
32
use Throwable;
33
34
/**
35
 * Class BaseController
36
 * @property \MikoPBX\PBXCoreREST\Http\Response $response
37
 * @property \MikoPBX\PBXCoreREST\Http\Request $request
38
 */
39
class BaseController extends Controller
40
{
41
42
43
    /**
44
     * Send a request to the backend worker.
45
     *
46
     * @param string $processor The name of the processor.
47
     * @param string $actionName The name of the action.
48
     * @param mixed|null $payload The payload data to send with the request.
49
     * @param string $moduleName The name of the module (only for 'modules' processor).
50
     * @param int $maxTimeout The maximum timeout for the request in seconds.
51
     * @param int $priority The priority of the request.
52
     *
53
     * @return void
54
     *
55
     */
56
    public function sendRequestToBackendWorker(
57
        string $processor,
58
        string $actionName,
59
        $payload = null,
60
        string $moduleName='',
61
        int $maxTimeout = 10,
62
        int $priority = Pheanstalk::DEFAULT_PRIORITY
63
    ): void
64
    {
65
        list($debug, $requestMessage) = $this->prepareRequestMessage($processor, $payload, $actionName, $moduleName);
66
67
        try {
68
            $message = json_encode($requestMessage, JSON_THROW_ON_ERROR);
69
            $beanstalkQueue = $this->di->getShared(BeanstalkConnectionWorkerApiProvider::SERVICE_NAME);
70
            if ($debug){
71
                $maxTimeout = 9999;
72
            }
73
            $response       = $beanstalkQueue->request($message, $maxTimeout, $priority);
74
            if ($response !== false) {
75
                $response = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
76
                if (array_key_exists(BeanstalkClient::QUEUE_ERROR, $response)){
77
                    $this->response->setPayloadError($response[BeanstalkClient::QUEUE_ERROR]);
78
                } elseif (array_key_exists(BeanstalkClient::RESPONSE_IN_FILE, $response)){
79
                    $tempFile = $response[BeanstalkClient::RESPONSE_IN_FILE];
80
                    $response = unserialize(file_get_contents($tempFile));
81
                    $this->response->setPayloadSuccess($response);
82
                } else {
83
                    $this->response->setPayloadSuccess($response);
84
                }
85
            } else {
86
                $this->sendError(Response::INTERNAL_SERVER_ERROR);
87
            }
88
        } catch (Throwable $e) {
89
            CriticalErrorsHandler::handleExceptionWithSyslog($e);
90
            $this->sendError(Response::BAD_REQUEST, $e->getMessage());
91
        }
92
    }
93
94
    /**
95
     * Sets the response with an error code
96
     *
97
     * @param int    $code
98
     * @param string $description
99
     */
100
    protected function sendError(int $code, string $description = ''): void
101
    {
102
        $this
103
            ->response
104
            ->setPayloadError($this->response->getHttpCodeDescription($code) . ' ' . $description)
105
            ->setStatusCode($code);
106
    }
107
108
    /**
109
     * Prepare a request message for sending to backend worker
110
     *
111
     * @param string $processor
112
     * @param $payload
113
     * @param string $actionName
114
     * @param string $moduleName
115
     * @return array
116
     */
117
    public function prepareRequestMessage(string $processor, $payload, string $actionName, string $moduleName): array
118
    {
119
        // Old style modules, we can remove it after 2025
120
        if ($processor === 'modules') {
121
            $processor = PbxExtensionsProcessor::class;
122
        }
123
124
        // Start xdebug session, don't forget to install xdebug.remote_mode = jit on xdebug.ini
125
        // and set XDEBUG_SESSION Cookie header on REST request to debug it
126
        // The set will break the WorkerApiCommands() execution on prepareAnswer method
127
        $debug = strpos($this->request->getHeader('Cookie'), 'XDEBUG_SESSION') !== false;
128
129
        $requestMessage = [
130
            'processor' => $processor,
131
            'data' => $payload,
132
            'action' => $actionName,
133
            'async'=> false,
134
            'asyncChannelId'=> '',
135
            'debug' => $debug
136
        ];
137
        if ($this->request->isAsyncRequest()){
138
            $requestMessage['async']= true;
139
            $requestMessage['asyncChannelId']= $this->request->getAsyncRequestChannelId();
140
        }
141
142
        if ($processor === PbxExtensionsProcessor::class) {
143
            $requestMessage['module'] = $moduleName;
144
        }
145
        return array($debug, $requestMessage);
146
    }
147
148
    /**
149
     * Recursively sanitizes input data based on the provided filter.
150
     *
151
     * @param array $data The data to be sanitized.
152
     * @param \Phalcon\Filter\FilterInterface $filter The filter object used for sanitization.
153
     *
154
     * @return array The sanitized data.
155
     */
156
    public static function sanitizeData(array $data, \Phalcon\Filter\FilterInterface $filter): array
157
    {
158
        foreach ($data as $key => $value) {
159
            if (is_array($value)) {
160
                // Recursively sanitize array values
161
                $data[$key] = self::sanitizeData($value, $filter);
162
            } elseif (is_string($value)) {
163
                // Check if the string starts with 'http'
164
                if (stripos($value, 'http') === 0) {
165
                    // If the string starts with 'http', sanitize it as a URL
166
                    $data[$key] = $filter->sanitize($value, FILTER::FILTER_URL);
167
                } else {
168
                    // Sanitize regular strings (trim and remove illegal characters)
169
                    $data[$key] = $filter->sanitize($value, [FILTER::FILTER_STRING, FILTER::FILTER_TRIM]);
170
                }
171
            } elseif (is_numeric($value)) {
172
                // Sanitize numeric values as integers
173
                $data[$key] = $filter->sanitize($value, FILTER::FILTER_INT);
174
            }
175
        }
176
177
        return $data;
178
    }
179
}