1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Sugarcrm\UpgradeSpec\Data\Provider\Doc; |
4
|
|
|
|
5
|
|
|
use GuzzleHttp\Client; |
6
|
|
|
use GuzzleHttp\Pool; |
7
|
|
|
use League\HTMLToMarkdown\HtmlConverter; |
8
|
|
|
use Psr\Http\Message\ResponseInterface; |
9
|
|
|
use Sugarcrm\UpgradeSpec\Cache\Cache; |
10
|
|
|
use Sugarcrm\UpgradeSpec\Purifier\Html; |
11
|
|
|
use Symfony\Component\DomCrawler\Crawler; |
12
|
|
|
|
13
|
|
|
class SupportSugarcrm implements DocProviderInterface |
14
|
|
|
{ |
15
|
|
|
/** |
16
|
|
|
* @var Client |
17
|
|
|
*/ |
18
|
|
|
private $httpClient; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var Cache |
22
|
|
|
*/ |
23
|
|
|
private $cache; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var HtmlConverter |
27
|
|
|
*/ |
28
|
|
|
private $htmlConverter; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* SupportSugarcrm constructor. |
32
|
|
|
* |
33
|
|
|
* @param Cache $cache |
34
|
|
|
* @param HtmlConverter $htmlConverter |
35
|
|
|
*/ |
36
|
|
|
public function __construct(Cache $cache, HtmlConverter $htmlConverter) |
37
|
|
|
{ |
38
|
|
|
$this->httpClient = new Client(['base_uri' => 'http://support.sugarcrm.com/']); |
39
|
|
|
$this->cache = $cache; |
40
|
|
|
$this->htmlConverter = $htmlConverter; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Get all available SugarCRM versions (sorted ASC). |
45
|
|
|
* |
46
|
|
|
* @param $flav |
47
|
|
|
* |
48
|
|
|
* @return mixed |
49
|
|
|
*/ |
50
|
|
|
public function getVersions($flav) |
51
|
|
|
{ |
52
|
|
|
if ($this->cache->has('versions')) { |
53
|
|
|
return $this->cache->get('versions'); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
$crawler = new Crawler($this->getContent('/Documentation/Sugar_Versions/index.html')); |
57
|
|
|
$majors = []; |
58
|
|
|
foreach ($crawler->filter('section.content-body > h1') as $node) { |
59
|
|
|
$major = $node->textContent; |
60
|
|
|
if (preg_match('/^\d+\.\d+$/', $major)) { |
61
|
|
|
$majors[] = $major; |
62
|
|
|
} |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$versions = []; |
66
|
|
|
foreach ($majors as $major) { |
67
|
|
|
$crawler = new Crawler($this->getContent($this->getVersionUri($flav, $major))); |
68
|
|
|
foreach ($crawler->filter('#Release_Notes')->first()->nextAll()->filter('a') as $node) { |
69
|
|
|
if (preg_match_all('/\d+(\.\d+){1,3}/', $node->textContent, $match)) { |
70
|
|
|
$versions[] = $match[0][0]; |
71
|
|
|
} |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
// sort versions (ASC) |
76
|
|
View Code Duplication |
usort($versions, function ($v1, $v2) { |
|
|
|
|
77
|
|
|
return version_compare($v1, $v2, '<') ? -1 : (version_compare($v1, $v2, '>') ? 1 : 0); |
78
|
|
|
}); |
79
|
|
|
|
80
|
|
|
$this->cache->set('versions', $versions); |
81
|
|
|
|
82
|
|
|
return $versions; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @param $flav |
87
|
|
|
* @param array $versions |
88
|
|
|
* |
89
|
|
|
* @return array |
90
|
|
|
*/ |
91
|
|
|
public function getReleaseNotes($flav, array $versions) |
92
|
|
|
{ |
93
|
|
|
$newVersions = array_filter($versions, function ($version) use ($flav) { |
94
|
|
|
return !$this->cache->has($this->getCacheKey([$flav, 'release_notes', $version])); |
95
|
|
|
}); |
96
|
|
|
|
97
|
|
|
$requests = function () use ($flav, $newVersions) { |
98
|
|
|
foreach ($newVersions as $version) { |
99
|
|
|
yield $version => function () use ($flav, $version) { |
100
|
|
|
return $this->httpClient->getAsync($this->getReleaseNotesUri($flav, $version)); |
101
|
|
|
}; |
102
|
|
|
} |
103
|
|
|
}; |
104
|
|
|
|
105
|
|
|
if ($newVersions) { |
|
|
|
|
106
|
|
|
$this->processRequestPool($requests, [ |
107
|
|
|
'fulfilled' => function (ResponseInterface $response, $version) use ($flav) { |
108
|
|
|
$html = $response->getBody()->getContents(); |
109
|
|
|
$crawler = new Crawler($this->purifyHtml($html, $this->getReleaseNotesUri($flav, $version))); |
110
|
|
|
|
111
|
|
|
$identifiers = [ |
112
|
|
|
'feature_enhancements' => '#Feature_Enhancements', |
113
|
|
|
'development_changes' => '#Development_Changes', |
114
|
|
|
]; |
115
|
|
|
|
116
|
|
|
$releaseNote = []; |
117
|
|
|
foreach ($identifiers as $key => $identifier) { |
118
|
|
|
$nodes = $crawler->filter($identifier); |
119
|
|
|
if (count($nodes)) { |
120
|
|
|
$nextSiblings = $nodes->nextAll(); |
121
|
|
|
|
122
|
|
|
$content = []; |
123
|
|
View Code Duplication |
foreach (['p', 'ul'] as $tag) { |
|
|
|
|
124
|
|
|
if (count($filtered = $nextSiblings->filter($tag))) { |
125
|
|
|
$content[] = sprintf('<%1$s>%2$s</%1$s>', $tag, $filtered->first()->html()); |
126
|
|
|
} |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
if ($content) { |
|
|
|
|
130
|
|
|
$releaseNote[$key] = $this->htmlConverter->convert(implode('<br>', $content)); |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$this->cache->set($this->getCacheKey([$flav, 'release_notes', $version]), $releaseNote); |
136
|
|
|
}, |
137
|
|
|
'rejected' => function ($reason, $version) { |
138
|
|
|
throw new \RuntimeException( |
139
|
|
|
sprintf('Can\'t get release notes for version: %s (reason: %s)', $version, $reason) |
140
|
|
|
); |
141
|
|
|
}, |
142
|
|
|
]); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
$releaseNotes = []; |
146
|
|
|
foreach ($versions as $version) { |
147
|
|
|
$releaseNote = $this->cache->get($this->getCacheKey([$flav, 'release_notes', $version]), null); |
148
|
|
|
if ($releaseNote) { |
149
|
|
|
$releaseNotes[$version] = $releaseNote; |
150
|
|
|
} |
151
|
|
|
} |
152
|
|
|
|
153
|
|
View Code Duplication |
uksort($releaseNotes, function ($v1, $v2) { |
|
|
|
|
154
|
|
|
return version_compare($v1, $v2, '<') ? -1 : (version_compare($v1, $v2, '>') ? 1 : 0); |
155
|
|
|
}); |
156
|
|
|
|
157
|
|
|
return $releaseNotes; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Gets all required information to perform health check. |
162
|
|
|
* |
163
|
|
|
* @param $version |
164
|
|
|
* |
165
|
|
|
* @return mixed |
166
|
|
|
*/ |
167
|
|
|
public function getHealthCheckInfo($version) |
168
|
|
|
{ |
169
|
|
|
$version = $this->getMajorVersion($version); |
|
|
|
|
170
|
|
|
$cacheKey = $this->getCacheKey(['health_check', $version]); |
171
|
|
|
|
172
|
|
|
if ($this->cache->has($cacheKey)) { |
173
|
|
|
return $this->cache->get($cacheKey); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
$url = $this->getHealthCheckInfoUri($version); |
177
|
|
|
$crawler = new Crawler($this->purifyHtml($this->getContent($url), $url)); |
178
|
|
|
|
179
|
|
|
$infoNode = $crawler->filter('#Performing_the_Health_Check_2')->nextAll()->first(); |
180
|
|
|
$info = str_replace(['**<', '>**'], '**', $this->htmlConverter->convert($infoNode->html())); |
181
|
|
|
|
182
|
|
|
$this->cache->set($cacheKey, $info); |
183
|
|
|
|
184
|
|
|
return $info; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Gets all required information to perform upgrade. |
189
|
|
|
* |
190
|
|
|
* @param $version |
191
|
|
|
* |
192
|
|
|
* @return mixed |
193
|
|
|
*/ |
194
|
|
|
public function getUpgraderInfo($version) |
195
|
|
|
{ |
196
|
|
|
$version = $this->getMajorVersion($version); |
|
|
|
|
197
|
|
|
$cacheKey = $this->getCacheKey(['upgrader', $version]); |
198
|
|
|
|
199
|
|
|
if ($this->cache->has($cacheKey)) { |
200
|
|
|
return $this->cache->get($cacheKey); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
$url = $this->getUpgraderInfoUri($version); |
204
|
|
|
$crawler = new Crawler($this->purifyHtml($this->getContent($url), $url)); |
205
|
|
|
|
206
|
|
|
$id = $version == '6.5' ? '#Upgrading_Via_Silent_Upgrader' : '#Performing_the_Upgrade_2'; |
207
|
|
|
$nodes = $crawler->filter($id)->nextAll(); |
208
|
|
|
|
209
|
|
|
$content = []; |
210
|
|
View Code Duplication |
foreach (['ol', 'p'] as $tag) { |
|
|
|
|
211
|
|
|
if (count($filtered = $nodes->filter($tag))) { |
212
|
|
|
$content[] = sprintf('<%1$s>%2$s</%1$s>', $tag, $filtered->first()->html()); |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
$info = str_replace(['**<', '>**'], '**', $this->htmlConverter->convert(implode('<br>', $content))); |
217
|
|
|
|
218
|
|
|
$this->cache->set($cacheKey, $info); |
219
|
|
|
|
220
|
|
|
return $info; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Returns the result (response body) of GET request. |
225
|
|
|
* |
226
|
|
|
* @param $url |
227
|
|
|
* |
228
|
|
|
* @return string |
229
|
|
|
*/ |
230
|
|
|
private function getContent($url) |
231
|
|
|
{ |
232
|
|
|
$response = $this->httpClient->request('GET', $url); |
233
|
|
|
|
234
|
|
|
return $response->getBody()->getContents(); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* @param $version |
239
|
|
|
* |
240
|
|
|
* @return string |
241
|
|
|
*/ |
242
|
|
|
private function getHealthCheckInfoUri($version) |
243
|
|
|
{ |
244
|
|
|
return $this->getUpgradeGuideUri($version); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* @param $version |
249
|
|
|
* |
250
|
|
|
* @return string |
251
|
|
|
*/ |
252
|
|
|
private function getUpgraderInfoUri($version) |
253
|
|
|
{ |
254
|
|
|
return $this->getUpgradeGuideUri($version); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* @param $version |
259
|
|
|
* |
260
|
|
|
* @return string |
261
|
|
|
*/ |
262
|
|
|
private function getUpgradeGuideUri($version) |
263
|
|
|
{ |
264
|
|
|
return sprintf( |
265
|
|
|
'Documentation/Sugar_Versions/%s/Ult/Installation_and_Upgrade_Guide/index.html', |
266
|
|
|
$version |
267
|
|
|
); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* @param $flav |
272
|
|
|
* @param $major |
273
|
|
|
* |
274
|
|
|
* @return string |
275
|
|
|
*/ |
276
|
|
|
private function getVersionUri($flav, $major) |
277
|
|
|
{ |
278
|
|
|
$flav = ucfirst(mb_strtolower($flav)); |
|
|
|
|
279
|
|
|
|
280
|
|
|
return sprintf('/Documentation/Sugar_Versions/%s/%s/index.html', $major, $flav); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Returns version specific release note uri. |
285
|
|
|
* |
286
|
|
|
* @param $flav |
287
|
|
|
* @param $version |
288
|
|
|
* |
289
|
|
|
* @return string |
290
|
|
|
*/ |
291
|
|
|
private function getReleaseNotesUri($flav, $version) |
292
|
|
|
{ |
293
|
|
|
return sprintf( |
294
|
|
|
'/Documentation/Sugar_Versions/%s/%s/Sugar_%s_Release_Notes/index.html', |
295
|
|
|
$this->getMajorVersion($version), |
296
|
|
|
ucfirst(mb_strtolower($flav)), |
297
|
|
|
$version |
298
|
|
|
); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* @param $version |
303
|
|
|
* |
304
|
|
|
* @return string |
305
|
|
|
*/ |
306
|
|
|
private function getMajorVersion($version) |
307
|
|
|
{ |
308
|
|
|
list($v1, $v2) = explode('.', $version); |
309
|
|
|
|
310
|
|
|
return implode('.', [$v1, $v2]); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Returns cache key. |
315
|
|
|
* |
316
|
|
|
* @param $keyParts |
317
|
|
|
* |
318
|
|
|
* @return string |
319
|
|
|
*/ |
320
|
|
|
private function getCacheKey(array $keyParts) |
321
|
|
|
{ |
322
|
|
|
$delimiter = '___'; |
323
|
|
|
|
324
|
|
|
return implode($delimiter, array_map(function ($key) { |
325
|
|
|
return preg_replace('/[^a-zA-Z0-9_\.]+/', '', mb_strtolower($key)); |
326
|
|
|
}, $keyParts)); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Lightweight HTML purifier. |
331
|
|
|
* |
332
|
|
|
* @param $html |
333
|
|
|
* @param string $url |
334
|
|
|
* @param array $options |
335
|
|
|
* |
336
|
|
|
* @return string |
337
|
|
|
*/ |
338
|
|
|
private function purifyHtml($html, $url = '', $options = []) |
339
|
|
|
{ |
340
|
|
|
$baseUrl = rtrim($this->httpClient->getConfig('base_uri'), '/') . '/'; |
341
|
|
|
if ($url) { |
342
|
|
|
$baseUrl = dirname($baseUrl . ltrim($url, '/')) . '/'; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
$options = array_merge([ |
|
|
|
|
346
|
|
|
'absolute_urls' => true, |
347
|
|
|
'no_tag_duplicates' => true, |
348
|
|
|
'pre_to_code' => true, |
349
|
|
|
], $options); |
350
|
|
|
|
351
|
|
|
return (new Html($baseUrl, $options))->purify($html); |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Processes request pool. |
356
|
|
|
* |
357
|
|
|
* @param callable $requests |
358
|
|
|
* @param array $config |
359
|
|
|
* |
360
|
|
|
* @return mixed |
361
|
|
|
*/ |
362
|
|
|
private function processRequestPool(callable $requests, $config = []) |
363
|
|
|
{ |
364
|
|
|
/* |
365
|
|
|
* 1. create request pool |
366
|
|
|
* 2. initiate the transfers and create a promise |
367
|
|
|
* 3. force the pool of requests to complete |
368
|
|
|
*/ |
369
|
|
|
return (new Pool($this->httpClient, $requests(), $config))->promise()->wait(); |
370
|
|
|
} |
371
|
|
|
} |
372
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.