@@ -15,18 +15,38 @@ class UserStateSerializer
1515 private $ path ;
1616
1717 /**
18- * @param string $path
18+ * @var string
1919 */
20- public function __construct ($ path )
21- {
22- $ this ->path = $ path ;
20+ private $ workshopName ;
2321
24- if (file_exists ($ path )) {
25- return ;
26- }
22+ /**
23+ * @var string
24+ */
25+ const LEGACY_SAVE_FILE = '.phpschool.json ' ;
26+
27+ /**
28+ * @var string
29+ */
30+ const SAVE_FILE = '.phpschool-save.json ' ;
2731
28- if (!file_exists (dirname ($ path ))) {
29- mkdir (dirname ($ path ), 0777 , true );
32+ /**
33+ * @var ExerciseRepository
34+ */
35+ private $ exerciseRepository ;
36+
37+ /**
38+ * @param string $saveFileDirectory
39+ * @param string $workshopName
40+ * @param ExerciseRepository $exerciseRepository
41+ */
42+ public function __construct ($ saveFileDirectory , $ workshopName , ExerciseRepository $ exerciseRepository )
43+ {
44+ $ this ->workshopName = $ workshopName ;
45+ $ this ->path = $ saveFileDirectory ;
46+ $ this ->exerciseRepository = $ exerciseRepository ;
47+
48+ if (!file_exists ($ this ->path )) {
49+ mkdir ($ this ->path , 0777 , true );
3050 }
3151 }
3252
@@ -37,60 +57,65 @@ public function __construct($path)
3757 */
3858 public function serialize (UserState $ state )
3959 {
40- return file_put_contents ($ this ->path , json_encode ([
60+ $ saveFile = sprintf ('%s/%s ' , $ this ->path , static ::SAVE_FILE );
61+
62+ $ data = file_exists ($ saveFile )
63+ ? $ this ->readJson ($ saveFile )
64+ : [];
65+
66+ $ data [$ this ->workshopName ] = [
4167 'completed_exercises ' => $ state ->getCompletedExercises (),
4268 'current_exercise ' => $ state ->getCurrentExercise (),
43- ]));
69+ ];
70+
71+ return file_put_contents ($ saveFile , json_encode ($ data ));
4472 }
4573
4674 /**
4775 * @return UserState
4876 */
4977 public function deSerialize ()
5078 {
51- if (! file_exists ( $ this ->path )) {
52- return new UserState ();
53- }
79+ $ legacySaveFile = sprintf ( ' %s/%s ' , $ this ->path , static :: LEGACY_SAVE_FILE );
80+ if ( file_exists ( $ legacySaveFile )) {
81+ $ userState = $ this -> migrateData ( $ legacySaveFile );
5482
55- $ data = file_get_contents ($ this ->path );
83+ if ($ userState instanceof UserState) {
84+ return $ userState ;
85+ }
86+ }
5687
57- if (trim ($ data ) === "" ) {
88+ $ json = $ this ->readJson (sprintf ('%s/%s ' , $ this ->path , static ::SAVE_FILE ));
89+ if (null === $ json ) {
5890 $ this ->wipeFile ();
5991 return new UserState ();
6092 }
6193
62- $ json = @json_decode ($ data , true );
63-
64- if (null === $ json && JSON_ERROR_NONE !== json_last_error ()) {
65- $ this ->wipeFile ();
94+ if (!isset ($ json [$ this ->workshopName ])) {
6695 return new UserState ();
6796 }
6897
98+ $ json = $ json [$ this ->workshopName ];
6999 if (!array_key_exists ('completed_exercises ' , $ json )) {
70- $ this ->wipeFile ();
71100 return new UserState ();
72101 }
73102
74- if (!is_array ($ json ['completed_exercises ' ])) {
75- $ this ->wipeFile ();
103+ if (!array_key_exists ('current_exercise ' , $ json )) {
76104 return new UserState ();
77105 }
78106
79- foreach ($ json ['completed_exercises ' ] as $ exercise ) {
80- if (!is_string ($ exercise )) {
81- $ this ->wipeFile ();
82- return new UserState ();
83- }
107+ if (!is_array ($ json ['completed_exercises ' ])) {
108+ $ json ['completed_exercises ' ] = [];
84109 }
85110
86- if (!array_key_exists ('current_exercise ' , $ json )) {
87- $ this ->wipeFile ();
88- return new UserState ();
111+ foreach ($ json ['completed_exercises ' ] as $ i => $ exercise ) {
112+ if (!is_string ($ exercise )) {
113+ unset($ json ['completed_exercises ' ][$ i ]);
114+ }
89115 }
90116
91117 if (null !== $ json ['current_exercise ' ] && !is_string ($ json ['current_exercise ' ])) {
92- $ this ->wipeFile ();
93- return new UserState ();
118+ $ json ['current_exercise ' ] = null ;
94119 }
95120
96121 return new UserState (
@@ -99,11 +124,80 @@ public function deSerialize()
99124 );
100125 }
101126
127+ /**
128+ * On early versions of the workshop the save data was not namespaced
129+ * and therefore it was impossible to have data for more than one workshop
130+ * at the same time. Therefore we must try to migrate that data in to the new namespaced
131+ * format in order to preserve users save data.
132+ *
133+ * We can only migrate data when using the workshop the data was originally saved from
134+ *
135+ * @param string $legacySaveFile
136+ * @return null|UserState
137+ */
138+ private function migrateData ($ legacySaveFile )
139+ {
140+ $ data = $ this ->readJson ($ legacySaveFile );
141+
142+ if (null === $ data ) {
143+ unlink ($ legacySaveFile );
144+ return null ;
145+ }
146+
147+ //lets check if the data is in the old format
148+ if (!isset ($ data ['completed_exercises ' ]) || !isset ($ data ['current_exercise ' ])) {
149+ unlink ($ legacySaveFile );
150+ return null ;
151+ }
152+
153+ $ completedExercises = $ data ['completed_exercises ' ];
154+ $ availableExercises = $ this ->exerciseRepository ->getAllNames ();
155+
156+ //check to see if this old data represents THIS workshop
157+ //if not we bail
158+ foreach ($ completedExercises as $ completedExercise ) {
159+ if (!in_array ($ completedExercise , $ availableExercises )) {
160+ return null ;
161+ }
162+ }
163+
164+ $ userState = new UserState ($ data ['completed_exercises ' ], $ data ['current_exercise ' ]);
165+ $ this ->serialize ($ userState );
166+
167+ unlink ($ legacySaveFile );
168+ return $ userState ;
169+ }
170+
171+ /**
172+ * @param string $filePath
173+ * @return array|null
174+ */
175+ private function readJson ($ filePath )
176+ {
177+ if (!file_exists ($ filePath )) {
178+ return null ;
179+ }
180+
181+ $ data = file_get_contents ($ filePath );
182+
183+ if (trim ($ data ) === "" ) {
184+ return null ;
185+ }
186+
187+ $ data = @json_decode ($ data , true );
188+
189+ if (null === $ data && JSON_ERROR_NONE !== json_last_error ()) {
190+ return null ;
191+ }
192+
193+ return $ data ;
194+ }
195+
102196 /**
103197 * Remove the file
104198 */
105199 private function wipeFile ()
106200 {
107- unlink ($ this ->path );
201+ @ unlink ($ this ->path );
108202 }
109203}
0 commit comments