@@ -27,20 +27,36 @@ class SourceManifest
2727 */
2828 protected $ builds = [];
2929
30+ /**
31+ * @var array The version map where forks occurred.
32+ */
33+ protected $ forks ;
34+
35+ /**
36+ * @var string The URL to the forked version manifest
37+ */
38+ protected $ forksUrl = 'https://raw.githubusercontent.com/wintercms/meta/master/manifest/forks.json ' ;
39+
3040 /**
3141 * Constructor
3242 *
3343 * @param string $manifest Manifest file to load
44+ * @param string $forks Forks manifest file to load
3445 * @param bool $autoload Loads the manifest on construct
3546 */
36- public function __construct ($ source = null , $ autoload = true )
47+ public function __construct ($ source = null , $ forks = null , $ autoload = true )
3748 {
3849 if (isset ($ source )) {
3950 $ this ->setSource ($ source );
4051 }
4152
53+ if (isset ($ forks )) {
54+ $ this ->setForksSource ($ forks );
55+ }
56+
4257 if ($ autoload ) {
43- $ this ->load ();
58+ $ this ->loadSource ();
59+ $ this ->loadForks ();
4460 }
4561 }
4662
@@ -57,12 +73,25 @@ public function setSource($source)
5773 }
5874 }
5975
76+ /**
77+ * Sets the forked version manifest URL.
78+ *
79+ * @param string $forks
80+ * @return void
81+ */
82+ public function setForksSource ($ forks )
83+ {
84+ if (is_string ($ forks )) {
85+ $ this ->forksUrl = $ forks ;
86+ }
87+ }
88+
6089 /**
6190 * Loads the manifest file.
6291 *
6392 * @throws Exception If the manifest is invalid, or cannot be parsed.
6493 */
65- public function load ()
94+ public function loadSource ()
6695 {
6796 $ source = file_get_contents ($ this ->source );
6897 if (empty ($ source )) {
@@ -85,7 +114,9 @@ public function load()
85114 }
86115
87116 foreach ($ data ['manifest ' ] as $ build ) {
88- $ this ->builds [$ build ['build ' ]] = [
117+ $ this ->builds [$ this ->getVersionInt ($ build ['build ' ])] = [
118+ 'version ' => $ build ['build ' ],
119+ 'parent ' => $ build ['parent ' ],
89120 'modules ' => $ build ['modules ' ],
90121 'files ' => $ build ['files ' ],
91122 ];
@@ -95,47 +126,81 @@ public function load()
95126 }
96127
97128 /**
98- * Gets all builds .
129+ * Loads the forked version manifest file .
99130 *
100- * @return array
131+ * @throws Exception If the manifest is invalid, or cannot be parsed.
101132 */
102- public function getBuilds ()
133+ public function loadForks ()
103134 {
104- return $ this ->builds ;
135+ $ forks = file_get_contents ($ this ->forksUrl );
136+ if (empty ($ forks )) {
137+ throw new Exception (
138+ 'Forked version manifest not found '
139+ );
140+ }
141+
142+ $ data = json_decode ($ forks , true );
143+
144+ if (json_last_error () !== JSON_ERROR_NONE ) {
145+ throw new Exception (
146+ 'Unable to decode forked version manifest JSON data. JSON Error: ' . json_last_error_msg ()
147+ );
148+ }
149+ if (!isset ($ data ['forks ' ]) || !is_array ($ data ['forks ' ])) {
150+ throw new Exception (
151+ 'The forked version manifest at " ' . $ this ->forksUrl . '" does not appear to be a valid forked version
152+ manifest file. '
153+ );
154+ }
155+
156+ // Map forks to int values
157+ foreach ($ data ['forks ' ] as $ child => $ parent ) {
158+ $ this ->forks [$ this ->getVersionInt ($ child )] = $ this ->getVersionInt ($ parent );
159+ }
160+
161+ return $ this ;
105162 }
106163
107164 /**
108- * Gets the maximum build number in the manifest .
165+ * Gets all builds .
109166 *
110- * @return int
167+ * @return array
111168 */
112- public function getMaxBuild ()
169+ public function getBuilds ()
113170 {
114- if (!count ($ this ->builds )) {
115- return null ;
116- }
117-
118- return max (array_keys ($ this ->builds ));
171+ return array_values (array_map (function ($ build ) {
172+ return $ build ['version ' ];
173+ }, $ this ->builds ));
119174 }
120175
121176 /**
122177 * Gets the filelist state at a selected build.
123178 *
124- * This method will list all expected files and hashsums at the specified build number.
179+ * This method will list all expected files and hashsums at the specified build number. It does this by following
180+ * the history, switching branches as necessary.
125181 *
126- * @param integer $build Build number to get the filelist state for.
182+ * @param string| integer $build Build version to get the filelist state for.
127183 * @throws Exception If the specified build has not been added to the source manifest.
128184 * @return array
129185 */
130186 public function getState ($ build )
131187 {
188+ if (is_string ($ build )) {
189+ $ build = $ this ->getVersionInt ($ build );
190+ }
191+
132192 if (!isset ($ this ->builds [$ build ])) {
133193 throw new \Exception ('The specified build has not been added. ' );
134194 }
135195
136196 $ state = [];
137197
138198 foreach ($ this ->builds as $ number => $ details ) {
199+ // Follow fork if necessary
200+ if (isset ($ this ->forks ) && array_key_exists ($ build , $ this ->forks )) {
201+ $ state = $ this ->getState ($ this ->forks [$ build ]);
202+ }
203+
139204 if (isset ($ details ['files ' ]['added ' ])) {
140205 foreach ($ details ['files ' ]['added ' ] as $ filename => $ sum ) {
141206 $ state [$ filename ] = $ sum ;
@@ -181,12 +246,14 @@ public function compare(FileManifest $manifest, $detailed = false)
181246 $ modules = $ manifest ->getModuleChecksums ();
182247
183248 // Look for an unmodified version
184- foreach ($ this ->getBuilds () as $ build => $ details ) {
185- $ matched = array_intersect_assoc ($ details ['modules ' ], $ modules );
249+ foreach ($ this ->getBuilds () as $ buildString ) {
250+ $ build = $ this ->builds [$ this ->getVersionInt ($ buildString )];
251+
252+ $ matched = array_intersect_assoc ($ build ['modules ' ], $ modules );
186253
187254 if (count ($ matched ) === count ($ modules )) {
188255 $ details = [
189- 'build ' => $ build ,
256+ 'build ' => $ buildString ,
190257 'modified ' => false ,
191258 'confident ' => true ,
192259 ];
@@ -203,8 +270,10 @@ public function compare(FileManifest $manifest, $detailed = false)
203270 // install.
204271 $ buildMatch = [];
205272
206- foreach ($ this ->getBuilds () as $ build => $ details ) {
207- $ state = $ this ->getState ($ build );
273+ foreach ($ this ->getBuilds () as $ buildString ) {
274+ $ build = $ this ->builds [$ this ->getVersionInt ($ buildString )];
275+
276+ $ state = $ this ->getState ($ buildString );
208277
209278 // Include only the files that match the modules being loaded in this file manifest
210279 $ availableModules = array_keys ($ modules );
@@ -244,7 +313,7 @@ public function compare(FileManifest $manifest, $detailed = false)
244313 $ changedPercent = count ($ filesChanged ) / $ filesExpected ;
245314
246315 $ score = ((1 * $ foundPercent ) - $ changedPercent );
247- $ buildMatch [$ build ] = round ($ score * 100 , 2 );
316+ $ buildMatch [$ buildString ] = round ($ score * 100 , 2 );
248317 }
249318
250319 // Find likely version
@@ -269,8 +338,8 @@ public function compare(FileManifest $manifest, $detailed = false)
269338 * Will return an array of added, modified and removed files.
270339 *
271340 * @param FileManifest $manifest The current build's file manifest.
272- * @param FileManifest|integer $previous Either a previous manifest, or the previous build number as an int,
273- * used to determine changes with this build.
341+ * @param FileManifest|string| integer $previous Either a previous manifest, or the previous build number as an int
342+ * or string, used to determine changes with this build.
274343 * @return array
275344 */
276345 protected function processChanges (FileManifest $ manifest , $ previous = null )
@@ -283,7 +352,7 @@ protected function processChanges(FileManifest $manifest, $previous = null)
283352 }
284353
285354 // Only save files if they are changing the "state" of the manifest (ie. the file is modified, added or removed)
286- if (is_int ($ previous )) {
355+ if (is_int ($ previous ) || is_string ( $ previous ) ) {
287356 $ state = $ this ->getState ($ previous );
288357 } else {
289358 $ state = $ previous ->getFiles ();
@@ -319,4 +388,26 @@ protected function processChanges(FileManifest $manifest, $previous = null)
319388
320389 return $ changes ;
321390 }
391+
392+
393+ /**
394+ * Converts a version string into an integer for comparison.
395+ *
396+ * @param string $version
397+ * @throws Exception if a version string does not match the format "major.minor.path"
398+ * @return int
399+ */
400+ protected function getVersionInt (string $ version )
401+ {
402+ // Get major.minor.patch versions
403+ if (!preg_match ('/^([0-9]+)\.([0-9]+)\.([0-9]+)/ ' , $ version , $ versionParts )) {
404+ throw new Exception ('Invalid version string - must be of the format "major.minor.path" ' );
405+ }
406+
407+ $ int = $ versionParts [1 ] * 1000000 ;
408+ $ int += $ versionParts [2 ] * 1000 ;
409+ $ int += $ versionParts [3 ];
410+
411+ return $ int ;
412+ }
322413}
0 commit comments