1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Sugarcrm\UpgradeSpec\Element\Section; |
4
|
|
|
|
5
|
|
|
use Sugarcrm\UpgradeSpec\Data\DataAwareInterface; |
6
|
|
|
use Sugarcrm\UpgradeSpec\Data\DataAwareTrait; |
7
|
|
|
use Sugarcrm\UpgradeSpec\Element\ElementInterface; |
8
|
|
|
use Sugarcrm\UpgradeSpec\Spec\Context; |
9
|
|
|
use Sugarcrm\UpgradeSpec\Template\RendererAwareInterface; |
10
|
|
|
use Sugarcrm\UpgradeSpec\Template\RendererAwareTrait; |
11
|
|
|
use Symfony\Component\Finder\Finder; |
12
|
|
|
|
13
|
|
|
class UpgradeChanges implements ElementInterface, RendererAwareInterface, DataAwareInterface |
14
|
|
|
{ |
15
|
|
|
use RendererAwareTrait, DataAwareTrait; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* @return string |
19
|
|
|
*/ |
20
|
|
|
public function getTitle() |
21
|
|
|
{ |
22
|
|
|
return 'Review upgrade changes and fix possible customization conflicts'; |
23
|
|
|
} |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @return int |
27
|
|
|
*/ |
28
|
|
|
public function getOrder() |
29
|
|
|
{ |
30
|
|
|
return 3; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @param Context $context |
35
|
|
|
* |
36
|
|
|
* @return bool |
37
|
|
|
*/ |
38
|
|
|
public function isRelevantTo(Context $context) |
39
|
|
|
{ |
40
|
|
|
return $context->getPackagesPath() |
41
|
|
|
&& $this->getFlavPackages($context->getFlav(), $context->getPackagesPath()); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @param Context $context |
46
|
|
|
* |
47
|
|
|
* @return string |
48
|
|
|
*/ |
49
|
|
|
public function getBody(Context $context) |
50
|
|
|
{ |
51
|
|
|
$packages = $this->getSuitablePackages($context); |
52
|
|
|
|
53
|
|
|
$modified = $deleted = []; |
54
|
|
|
if ($packages) { |
|
|
|
|
55
|
|
|
list($modified, $deleted) = $this->getChangedFiles('/Users/m.kamornikov/Dev/sugarcrm/build/rome/builds/ult/sugarcrm', $packages); |
56
|
|
|
natsort($modified); |
57
|
|
|
natsort($deleted); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
return $this->renderer->render('upgrade_changes', [ |
61
|
|
|
'packages' => $packages, |
62
|
|
|
'upgrade_to' => $context->getUpgradeVersion(), |
63
|
|
|
'packages_path' => $context->getPackagesPath(), |
64
|
|
|
'modified_files' => $modified, |
65
|
|
|
'deleted_files' => $deleted, |
66
|
|
|
]); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
private function getChangedFiles($buildPath, $packages) |
70
|
|
|
{ |
71
|
|
|
$changedFiles = $deletedFiles = $packageZips = []; |
72
|
|
|
|
73
|
|
|
foreach ($packages as $package) { |
74
|
|
|
$zip = new \ZipArchive(); |
75
|
|
|
if (!$zip->open($package)) { |
76
|
|
|
throw new \RuntimeException(sprintf('Can\'t open zip archive: %s', $package)); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
$packageZips[$package] = $zip; |
80
|
|
|
|
81
|
|
|
eval(str_replace(['<?php', '<?', '?>'], '', $zip->getFromName(basename($package, '.zip') . DS . 'files.md5'))); |
|
|
|
|
82
|
|
|
$packageChangedFiles = array_keys($md5_string); |
|
|
|
|
83
|
|
|
|
84
|
|
|
if ($filesToRemove = $zip->getFromName('filesToRemove.txt')) { |
85
|
|
|
$packageDeletedFiles = explode(PHP_EOL, str_replace(["\r\n", "\r", "\n"], PHP_EOL, $filesToRemove)); |
86
|
|
|
} else if ($filesToRemove = $zip->getFromName('filesToRemove.json')) { |
87
|
|
|
$packageDeletedFiles = json_decode($filesToRemove); |
88
|
|
|
} else { |
89
|
|
|
throw new \RuntimeException('Can\'t open filesToRemove'); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
$changedFiles = array_merge($changedFiles, array_combine($packageChangedFiles, array_fill(0, count($packageChangedFiles), $package))); |
93
|
|
|
$deletedFiles = array_diff(array_merge($deletedFiles, $packageDeletedFiles), $packageChangedFiles); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$changedFiles = array_keys(array_filter($changedFiles, function ($package, $changedFile) use ($buildPath, $packageZips) { |
97
|
|
|
if (($buildFile = @file_get_contents($buildPath . DS . $changedFile)) === false) { |
98
|
|
|
return false; |
99
|
|
|
} |
100
|
|
|
$packageFile = $packageZips[$package]->getFromName(basename($package, '.zip') . DS . $changedFile); |
101
|
|
|
|
102
|
|
|
return $this->getCheckSum($buildFile) != $this->getCheckSum($packageFile); |
103
|
|
|
}, ARRAY_FILTER_USE_BOTH)); |
104
|
|
|
|
105
|
|
|
$deletedFiles = array_values(array_filter($deletedFiles)); |
106
|
|
|
|
107
|
|
|
foreach ($packageZips as $zip) { |
108
|
|
|
$zip->close(); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
$changedFiles = array_values(array_filter($changedFiles, function ($file) use ($buildPath) { |
112
|
|
|
return file_exists($buildPath . '/custom/' . $file); |
113
|
|
|
})); |
114
|
|
|
|
115
|
|
|
$deletedFiles = array_values(array_filter($deletedFiles, function ($file) use ($buildPath) { |
116
|
|
|
return file_exists($buildPath . '/custom/' . $file); |
117
|
|
|
})); |
118
|
|
|
|
119
|
|
|
return [$changedFiles, $deletedFiles]; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* @param $content |
124
|
|
|
* |
125
|
|
|
* @return string |
126
|
|
|
*/ |
127
|
|
|
private function getCheckSum($content) |
128
|
|
|
{ |
129
|
|
|
// remove license comments |
130
|
|
|
$content = preg_replace('/\/\*.*?Copyright \(C\) SugarCRM.*?\*\//is', '', $content); |
|
|
|
|
131
|
|
|
|
132
|
|
|
// remove blank lines |
133
|
|
|
$content = preg_replace('/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/', "\n", $content); |
|
|
|
|
134
|
|
|
|
135
|
|
|
// change all line breaks to system line break |
136
|
|
|
$content = str_replace(["\r\n", "\r", "\n"], PHP_EOL, $content); |
|
|
|
|
137
|
|
|
|
138
|
|
|
// remove trailing whitespaces |
139
|
|
|
$content = preg_replace('/^\s+|\s+$/m', '', $content); |
|
|
|
|
140
|
|
|
|
141
|
|
|
return md5($content); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* @param Context $context |
146
|
|
|
* |
147
|
|
|
* @return array |
148
|
|
|
*/ |
149
|
|
|
private function getSuitablePackages(Context $context) |
150
|
|
|
{ |
151
|
|
|
$chains = $this->getUpgradeChains( |
152
|
|
|
$this->getFlavPackages($context->getFlav(), $context->getPackagesPath()), |
153
|
|
|
$context->getBuildVersion(), |
154
|
|
|
$context->getUpgradeVersion() |
155
|
|
|
); |
156
|
|
|
|
157
|
|
|
if (!$chains) { |
|
|
|
|
158
|
|
|
return []; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
usort($chains, function ($a1, $a2) { |
162
|
|
|
return count($a1) < count($a2) ? -1 : (count($a1) > count($a2) ? 1 : 0); |
163
|
|
|
}); |
164
|
|
|
|
165
|
|
|
return $chains[0]; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Gets flav specific packages |
170
|
|
|
* |
171
|
|
|
* @param $flav |
172
|
|
|
* @param $packagesPath |
173
|
|
|
* |
174
|
|
|
* @return array |
175
|
|
|
*/ |
176
|
|
|
private function getFlavPackages($flav, $packagesPath) |
177
|
|
|
{ |
178
|
|
|
$versionPattern = '/\d+\.\d+(\.\d+|\.x){1,2}/'; |
179
|
|
|
$packagePattern = sprintf('/^Sugar%1$s-Upgrade-%2$s-to-%2$s.zip$/', |
180
|
|
|
ucfirst(mb_strtolower($flav)), |
181
|
|
|
trim($versionPattern, '/') |
182
|
|
|
); |
183
|
|
|
|
184
|
|
|
$packagesIterator = (new Finder())->files()->in($packagesPath)->name($packagePattern); |
185
|
|
|
|
186
|
|
|
$packages = []; |
187
|
|
|
foreach ($packagesIterator as $package) { |
188
|
|
|
if (preg_match_all($versionPattern, $package, $matches)) { |
189
|
|
|
$packages[$package->getFilename()] = [ |
190
|
|
|
'path' => $package->getRealPath(), |
191
|
|
|
'from' => str_replace('.x', '', $matches[0][0]), |
192
|
|
|
'to' => str_replace('.x', '', $matches[0][1]) |
193
|
|
|
]; |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
return $packages; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Calculates an array of possible upgrade chains |
202
|
|
|
* |
203
|
|
|
* @param $packages |
204
|
|
|
* @param $buildVersion |
205
|
|
|
* @param $upgradeTo |
206
|
|
|
* |
207
|
|
|
* @return array |
208
|
|
|
*/ |
209
|
|
|
private function getUpgradeChains($packages, $buildVersion, $upgradeTo) |
210
|
|
|
{ |
211
|
|
|
$versionMatrix = $this->getVersionMatrix($packages); |
212
|
|
|
$allVersions = array_keys($versionMatrix); |
213
|
|
|
|
214
|
|
|
$getExistingSubversions = function ($version) use ($allVersions) { |
|
|
|
|
215
|
|
|
$existingVersions = []; |
216
|
|
|
|
217
|
|
|
$fromParts = explode('.', $version); |
218
|
|
|
foreach (range(2, count($fromParts)) as $length) { |
219
|
|
|
$subversion = implode('.', array_slice($fromParts, 0, $length)); |
220
|
|
|
if (in_array($subversion, $allVersions)) { |
221
|
|
|
$existingVersions[] = $subversion; |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
return $existingVersions; |
226
|
|
|
}; |
227
|
|
|
|
228
|
|
|
// init chains with starting versions |
229
|
|
|
$chains = array_map(function ($version) use ($buildVersion) { |
230
|
|
|
return [$version => $buildVersion]; |
231
|
|
|
}, $getExistingSubversions($buildVersion)); |
232
|
|
|
|
233
|
|
|
// finish early if starting / ending version doesn't exist |
234
|
|
|
if (!$chains || !in_array($upgradeTo, $allVersions)) { |
|
|
|
|
235
|
|
|
return []; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
// gets last key of assoc array |
239
|
|
|
$getLastKey = function ($array) { |
240
|
|
|
end($array); |
241
|
|
|
|
242
|
|
|
return key($array); |
243
|
|
|
}; |
244
|
|
|
|
245
|
|
|
// find all chains |
246
|
|
|
while (true) { |
247
|
|
|
$fullChains = []; |
248
|
|
|
foreach ($chains as $index => $chain) { |
249
|
|
|
$fromVersion = $getLastKey($chain); |
250
|
|
|
|
251
|
|
|
// skip not interesting chains |
252
|
|
|
if (version_compare($fromVersion, $upgradeTo, '>=')) { |
253
|
|
|
continue; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
$validChain = false; |
257
|
|
|
foreach ($allVersions as $version) { |
258
|
|
|
if (!empty($versionMatrix[$fromVersion][$version])) { |
259
|
|
|
$to = $getExistingSubversions($version); |
260
|
|
|
foreach ($to as $toVersion) { |
261
|
|
|
|
262
|
|
|
if ($toVersion === $fromVersion |
263
|
|
|
|| version_compare($toVersion, $upgradeTo, '>') |
264
|
|
|
|| $chain[$getLastKey($chain)] === $version |
265
|
|
|
) { |
266
|
|
|
continue; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
$validChain = true; |
270
|
|
|
$fullChains[] = array_merge($chain, [$toVersion => $version]); |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
// remove invalid chain |
276
|
|
|
if (!$validChain) { |
277
|
|
|
unset($chains[$index]); |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
if (!$fullChains) { |
|
|
|
|
282
|
|
|
break; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
$chains = $fullChains; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
$chains = array_map(function ($chain) use ($versionMatrix) { |
289
|
|
|
$keys = array_keys($chain); |
290
|
|
|
$values = array_values($chain); |
291
|
|
|
|
292
|
|
|
$packages = []; |
293
|
|
|
foreach (range(1, count($keys) - 1) as $index) { |
294
|
|
|
$packages[] = $versionMatrix[$keys[$index - 1]][$values[$index]]; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
return $packages; |
298
|
|
|
}, array_values($chains)); |
299
|
|
|
|
300
|
|
|
return $chains; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Creates version matrix |
305
|
|
|
* |
306
|
|
|
* v1 v2 v3 v4 |
307
|
|
|
* v1 0 <path> <path> 0 |
308
|
|
|
* v2 0 0 <path> 0 |
309
|
|
|
* v3 0 0 0 <path> |
310
|
|
|
* v4 0 0 0 0 |
311
|
|
|
* |
312
|
|
|
* @param $packages |
313
|
|
|
* |
314
|
|
|
* @return array |
315
|
|
|
*/ |
316
|
|
|
private function getVersionMatrix($packages) |
317
|
|
|
{ |
318
|
|
|
$allVersions = array_unique(call_user_func_array('array_merge', array_map(function ($package) { |
319
|
|
|
return [$package['from'], $package['to']]; |
320
|
|
|
}, $packages))); |
321
|
|
|
|
322
|
|
|
// sort versions (ASC) |
323
|
|
View Code Duplication |
usort($allVersions, function ($v1, $v2) { |
|
|
|
|
324
|
|
|
return version_compare($v1, $v2, '<') ? -1 : (version_compare($v1, $v2, '>') ? 1 : 0); |
325
|
|
|
}); |
326
|
|
|
|
327
|
|
|
// create matrix and fill it with zeros |
328
|
|
|
$versionMatrix = call_user_func_array('array_merge',array_map(function ($version) use ($allVersions) { |
329
|
|
|
return [$version => array_combine($allVersions, array_fill(0, count($allVersions), 0))]; |
330
|
|
|
}, $allVersions)); |
331
|
|
|
|
332
|
|
|
// valid associations point to package path |
333
|
|
|
foreach ($packages as $name => $package) { |
334
|
|
|
$versionMatrix[$package['from']][$package['to']] = $package['path']; |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
return $versionMatrix; |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.