Passed
Push — develop ( a56058...ddfce4 )
by Nikolay
04:39
created

InstallFromRepoAction::start()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 69
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 42
c 0
b 0
f 0
dl 0
loc 69
rs 8.0035
cc 8
nc 5
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
namespace MikoPBX\PBXCoreREST\Lib\Modules;
21
22
use MikoPBX\Common\Handlers\CriticalErrorsHandler;
23
use MikoPBX\Core\System\Util;
24
use MikoPBX\PBXCoreREST\Lib\Files\FilesConstants;
25
use MikoPBX\PBXCoreREST\Lib\LicenseManagementProcessor;
26
use MikoPBX\PBXCoreREST\Lib\PBXApiResult;
27
28
/**
29
 * Handles the installation of new modules.
30
 * This class provides functionality to install new additional extension modules for MikoPBX.
31
 *
32
 * @package MikoPBX\PBXCoreREST\Lib\Modules
33
 */
34
class InstallFromRepoAction extends ModuleInstallationBase
35
{
36
    const DOWNLOAD_TIMEOUT = 120;
37
38
    // Optional release ID for the module. Defaults to 0.
39
    private int $moduleReleaseId = 0;
40
41
    /**
42
     * Class constructor
43
     *
44
     * @param string $asyncChannelId Pub/sub nchan channel id to send response to frontend
45
     * @param string $moduleUniqueId The unique identifier for the module to be installed.
46
     * @param int $moduleReleaseId Optional release ID for the module. Defaults to 0.
47
     */
48
    public function __construct(string $asyncChannelId, string $moduleUniqueId, int $moduleReleaseId=0)
49
    {
50
        $this->asyncChannelId = $asyncChannelId;
51
        $this->moduleUniqueId = $moduleUniqueId;
52
        $this->moduleReleaseId = $moduleReleaseId;
53
    }
54
55
56
    /**
57
     * Main entry point to install a new module.
58
     * This function handles the entire process of installing a new module, including
59
     * acquiring a mutex, checking the license, downloading, and installing the module.
60
     */
61
    public function start(): void
62
    {
63
        // Calculate total mutex timeout and extra 5 seconds to prevent installing the same module in the second thread
64
        $mutexTimeout = self::INSTALLATION_TIMEOUT+self::DOWNLOAD_TIMEOUT+5;
65
66
        // Create a mutex to ensure synchronized access
67
        $mutex = Util::createMutex(self::INSTALLATION_MUTEX, $this->moduleUniqueId, $mutexTimeout);
68
69
        $res = new PBXApiResult();
70
        $res->processor = __METHOD__;
71
        $res->success = true;
72
73
        // Singleton the installation process
74
        try{
75
            $mutex->synchronized(
76
            function () use (&$res): void {
77
78
                // Retrieve release information
79
                list($releaseInfoResult, $res->success) = $this->getReleaseInfo();
80
                $this->pushMessageToBrowser(self::STAGE_I_GET_RELEASE, $releaseInfoResult);
81
                if (!$res->success) {
82
                    $res->messages['error'] = $releaseInfoResult;
83
                    return;
84
                }
85
86
                // Capture the license for the module
87
                list($licenseResult, $res->success) = $this->captureFeature($releaseInfoResult);
88
                $this->pushMessageToBrowser( self::STAGE_II_CHECK_LICENSE, $licenseResult);
89
                if (!$res->success) {
90
                    $res->messages = $licenseResult;
91
                    return;
92
                }
93
94
                // Get the download link for the module
95
                list($moduleLinkResult, $res->success) = $this->getModuleLink($releaseInfoResult);
96
                $this->pushMessageToBrowser( self::STAGE_III_GET_LINK, $moduleLinkResult);
97
                if (!$res->success) {
98
                    $res->messages = $moduleLinkResult;
99
                    return;
100
                }
101
102
                // Download the module
103
                list($downloadResult, $res->success) = $this->downloadModule($moduleLinkResult);
104
                if (!$res->success) {
105
                    $res->messages = $downloadResult;
106
                    return;
107
                } else {
108
                    $filePath = $downloadResult; // Path to the downloaded module
109
                }
110
111
                // Install the downloaded module
112
                list($installationResult, $res->success) = $this->installNewModule($filePath);
113
                if (!$res->success) {
114
                    $res->messages = $installationResult;
115
                    return;
116
                }
117
118
                // Enable the module if it was previously enabled
119
                list($enableResult, $res->success) = $this->enableModule($installationResult);
120
                if (!$res->success) {
121
                    $res->messages = $enableResult;
122
                }
123
            });
124
        } catch (\Throwable $e) {
125
            $res->success = false;
126
            $res->messages['error'][] = $e->getMessage();
127
            CriticalErrorsHandler::handleExceptionWithSyslog($e);
128
        } finally {
129
            $this->pushMessageToBrowser( self::STAGE_VII_FINAL_STATUS, $res->getResult());
130
        }
131
    }
132
133
    /**
134
     * Retrieves the release information for a specific module.
135
     * This function gets detailed information about the release based on the unique module ID and release ID.
136
     *
137
     * @return array An array containing the release information and a success flag.
138
     */
139
    private function getReleaseInfo(): array
140
    {
141
        // Retrieve module information
142
        $moduleInfo = GetModuleInfoAction::main($this->moduleUniqueId);
143
144
        // Check if release information is available
145
        if (empty($moduleInfo->data['releases'])) {
146
            return [[self::ERR_EMPTY_REPO_RESULT], false];
147
        }
148
        $releaseInfo =[];
149
        $releaseInfo['releaseID'] = 0;
150
151
        // Find the specified release or the latest one
152
        foreach ($moduleInfo->data['releases'] as $release) {
153
            if (intval($release['releaseID']) === $this->moduleReleaseId) {
154
                $releaseInfo['releaseID'] = $release['releaseID'];
155
                break;
156
            } elseif (intval($release['releaseID']) > $releaseInfo['releaseID']) {
157
                $releaseInfo['releaseID'] = $release['releaseID'];
158
            }
159
        }
160
        // Additional information for license management
161
        $releaseInfo['licFeatureId'] = intval($moduleInfo->data['lic_feature_id']);
162
        $releaseInfo['licProductId'] = intval($moduleInfo->data['lic_product_id']);
163
        return [$releaseInfo, true];
164
    }
165
166
    /**
167
     * Captures the feature license for the module installation.
168
     * This function checks and captures the necessary license for installing the module based on the release information.
169
     *
170
     * @param array $releaseInfo Release information array.
171
     *
172
     * @return array An array containing the license capture result and a success flag.
173
     */
174
    private function captureFeature(array $releaseInfo): array
175
    {
176
        // Check if a feature license is required
177
        if ($releaseInfo['licFeatureId'] === 0) {
178
            return [[self::MSG_NO_LICENSE_REQ], true]; // No license required
179
        }
180
181
        // Prepare license capture request
182
        $request = [
183
            'action' => 'captureFeatureForProductId',
184
            'data' => $releaseInfo
185
        ];
186
187
        // Perform license capture
188
        $res = LicenseManagementProcessor::callBack($request);
189
        return [$res->messages, $res->success];
190
    }
191
192
    /**
193
     * Retrieves the download link for the module.
194
     * This function gets the download link for the module based on its release ID.
195
     *
196
     * @param array $releaseInfo Release information array.
197
     *
198
     * @return array An array containing the download link and a success flag.
199
     */
200
    private function getModuleLink(array $releaseInfo): array
201
    {
202
        $res = GetModuleLinkAction::main($releaseInfo['releaseID']);
203
        if ($res->success){
204
            $modules =  $res->data['modules']??[];
205
            if (count($modules) > 0){
206
                return [$modules[0], true];
207
            }
208
            return [[self::ERR_EMPTY_GET_MODULE_LINK], false];
209
        }
210
        return [$res->messages, false];
211
    }
212
213
    /**
214
     * Downloads the module from the provided link.
215
     * This function handles the download process, ensuring that it completes within the allotted time.
216
     *
217
     * @param array $moduleLink Download link for the module and md5 hash
218
     *
219
     * @return array An array containing the path to the downloaded module or an error message, and a success flag.
220
     */
221
    private function downloadModule(array $moduleLink): array
222
    {
223
        // Initialization
224
        $url = $moduleLink['href'];
225
        $md5 = $moduleLink['md5'];
226
        $maximumDownloadTime = self::DOWNLOAD_TIMEOUT;
227
228
        // Start the download
229
        $res = StartDownloadAction::main($this->moduleUniqueId, $url, $md5);
230
        $this->pushMessageToBrowser( self::STAGE_IV_DOWNLOAD_MODULE, $res->getResult());
231
        if (!$res->success) {
232
            return [$res->messages, false];
233
        }
234
235
        // Monitor download progress
236
        while ($maximumDownloadTime > 0) {
237
            $resDownloadStatus = DownloadStatusAction::main($this->moduleUniqueId);
238
            $this->pushMessageToBrowser( self::STAGE_IV_DOWNLOAD_MODULE, $resDownloadStatus->getResult());
239
            if (!$resDownloadStatus->success) {
240
                return [$resDownloadStatus->messages, false];
241
            } elseif ($resDownloadStatus->data[FilesConstants::D_STATUS] === FilesConstants::DOWNLOAD_IN_PROGRESS) {
242
                sleep(1); // Adjust sleep time as needed
243
                $maximumDownloadTime--;
244
            } elseif ($resDownloadStatus->data[FilesConstants::D_STATUS] === FilesConstants::DOWNLOAD_COMPLETE) {
245
                return [$resDownloadStatus->data[FilesConstants::FILE_PATH], true];
246
            }
247
        }
248
249
        // Download timeout
250
        $this->pushMessageToBrowser( self::STAGE_IV_DOWNLOAD_MODULE, [self::ERR_DOWNLOAD_TIMEOUT]);
251
        return [self::ERR_DOWNLOAD_TIMEOUT, false];
252
    }
253
254
255
}