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