@@ -38,14 +38,216 @@ public function attachFile(PhabricatorFile $file) {
38
38
return $ this ->file = $ file ;
39
39
}
40
40
41
+ public function isDirectory () {
42
+ return $ this ->latestVersionPHID === null ;
43
+ }
44
+
41
45
public function getLatestVersion () {
46
+ if ($ this ->latestVersionPHID === null ) {
47
+ return null ;
48
+ }
42
49
return $ this ->assertAttached ($ this ->latestVersion );
43
50
}
44
51
45
52
public function attachLatestVersion (PhragmentFragmentVersion $ version ) {
46
53
return $ this ->latestVersion = $ version ;
47
54
}
48
55
56
+
57
+ /* -( Updating ) --------------------------------------------------------- */
58
+
59
+
60
+ /**
61
+ * Create a new fragment from a file.
62
+ */
63
+ public static function createFromFile (
64
+ PhabricatorUser $ viewer ,
65
+ PhabricatorFile $ file = null ,
66
+ $ path ,
67
+ $ view_policy ,
68
+ $ edit_policy ) {
69
+
70
+ $ fragment = id (new PhragmentFragment ());
71
+ $ fragment ->setPath ($ path );
72
+ $ fragment ->setDepth (count (explode ('/ ' , $ path )));
73
+ $ fragment ->setLatestVersionPHID (null );
74
+ $ fragment ->setViewPolicy ($ view_policy );
75
+ $ fragment ->setEditPolicy ($ edit_policy );
76
+ $ fragment ->save ();
77
+
78
+ // Directory fragments have no versions associated with them, so we
79
+ // just return the fragment at this point.
80
+ if ($ file === null ) {
81
+ return $ fragment ;
82
+ }
83
+
84
+ if ($ file ->getMimeType () === "application/zip " ) {
85
+ $ fragment ->updateFromZIP ($ viewer , $ file );
86
+ } else {
87
+ $ fragment ->updateFromFile ($ viewer , $ file );
88
+ }
89
+
90
+ return $ fragment ;
91
+ }
92
+
93
+
94
+ /**
95
+ * Set the specified file as the next version for the fragment.
96
+ */
97
+ public function updateFromFile (
98
+ PhabricatorUser $ viewer ,
99
+ PhabricatorFile $ file ) {
100
+
101
+ $ existing = id (new PhragmentFragmentVersionQuery ())
102
+ ->setViewer ($ viewer )
103
+ ->withFragmentPHIDs (array ($ this ->getPHID ()))
104
+ ->execute ();
105
+ $ sequence = count ($ existing );
106
+
107
+ $ this ->openTransaction ();
108
+ $ version = id (new PhragmentFragmentVersion ());
109
+ $ version ->setSequence ($ sequence );
110
+ $ version ->setFragmentPHID ($ this ->getPHID ());
111
+ $ version ->setFilePHID ($ file ->getPHID ());
112
+ $ version ->save ();
113
+
114
+ $ this ->setLatestVersionPHID ($ version ->getPHID ());
115
+ $ this ->save ();
116
+ $ this ->saveTransaction ();
117
+ }
118
+
119
+ /**
120
+ * Apply the specified ZIP archive onto the fragment, removing
121
+ * and creating fragments as needed.
122
+ */
123
+ public function updateFromZIP (
124
+ PhabricatorUser $ viewer ,
125
+ PhabricatorFile $ file ) {
126
+
127
+ if ($ file ->getMimeType () !== "application/zip " ) {
128
+ throw new Exception ("File must have mimetype 'application/zip' " );
129
+ }
130
+
131
+ // First apply the ZIP as normal.
132
+ $ this ->updateFromFile ($ viewer , $ file );
133
+
134
+ // Ensure we have ZIP support.
135
+ $ zip = null ;
136
+ try {
137
+ $ zip = new ZipArchive ();
138
+ } catch (Exception $ e ) {
139
+ // The server doesn't have php5-zip, so we can't do recursive updates.
140
+ return ;
141
+ }
142
+
143
+ $ temp = new TempFile ();
144
+ Filesystem::writeFile ($ temp , $ file ->loadFileData ());
145
+ if (!$ zip ->open ($ temp )) {
146
+ throw new Exception ("Unable to open ZIP " );
147
+ }
148
+
149
+ // Get all of the paths and their data from the ZIP.
150
+ $ mappings = array ();
151
+ for ($ i = 0 ; $ i < $ zip ->numFiles ; $ i ++) {
152
+ $ path = trim ($ zip ->getNameIndex ($ i ), '/ ' );
153
+ $ stream = $ zip ->getStream ($ path );
154
+ $ data = null ;
155
+ // If the stream is false, then it is a directory entry. We leave
156
+ // $data set to null for directories so we know not to create a
157
+ // version entry for them.
158
+ if ($ stream !== false ) {
159
+ $ data = stream_get_contents ($ stream );
160
+ fclose ($ stream );
161
+ }
162
+ $ mappings [$ path ] = $ data ;
163
+ }
164
+
165
+ // Adjust the paths relative to this fragment so we can look existing
166
+ // fragments up in the DB.
167
+ $ base_path = $ this ->getPath ();
168
+ $ paths = array ();
169
+ foreach ($ mappings as $ p => $ data ) {
170
+ $ paths [] = $ base_path .'/ ' .$ p ;
171
+ }
172
+
173
+ // FIXME: What happens when a child exists, but the current user
174
+ // can't see it. We're going to create a new child with the exact
175
+ // same path and then bad things will happen.
176
+ $ children = id (new PhragmentFragmentQuery ())
177
+ ->setViewer ($ viewer )
178
+ ->needLatestVersion (true )
179
+ ->withPaths ($ paths )
180
+ ->execute ();
181
+ $ children = mpull ($ children , null , 'getPath ' );
182
+
183
+ // Iterate over the existing fragments.
184
+ foreach ($ children as $ full_path => $ child ) {
185
+ $ path = substr ($ full_path , strlen ($ base_path ) + 1 );
186
+ if (array_key_exists ($ path , $ mappings )) {
187
+ if ($ child ->isDirectory () && $ mappings [$ path ] === null ) {
188
+ // Don't create a version entry for a directory
189
+ // (unless it's been converted into a file).
190
+ continue ;
191
+ }
192
+
193
+ // The file is being updated.
194
+ $ file = PhabricatorFile::newFromFileData (
195
+ $ mappings [$ path ],
196
+ array ('name ' => basename ($ path )));
197
+ $ child ->updateFromFile ($ viewer , $ file );
198
+ } else {
199
+ // The file is being deleted.
200
+ $ child ->deleteFile ($ viewer );
201
+ }
202
+ }
203
+
204
+ // Iterate over the mappings to find new files.
205
+ foreach ($ mappings as $ path => $ data ) {
206
+ if (!array_key_exists ($ base_path .'/ ' .$ path , $ children )) {
207
+ // The file is being created. If the data is null,
208
+ // then this is explicitly a directory being created.
209
+ $ file = null ;
210
+ if ($ mappings [$ path ] !== null ) {
211
+ $ file = PhabricatorFile::newFromFileData (
212
+ $ mappings [$ path ],
213
+ array ('name ' => basename ($ path )));
214
+ }
215
+ PhragmentFragment::createFromFile (
216
+ $ viewer ,
217
+ $ file ,
218
+ $ base_path .'/ ' .$ path ,
219
+ $ this ->getViewPolicy (),
220
+ $ this ->getEditPolicy ());
221
+ }
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Delete the contents of the specified fragment.
227
+ */
228
+ public function deleteFile (PhabricatorUser $ viewer ) {
229
+ $ existing = id (new PhragmentFragmentVersionQuery ())
230
+ ->setViewer ($ viewer )
231
+ ->withFragmentPHIDs (array ($ this ->getPHID ()))
232
+ ->execute ();
233
+ $ sequence = count ($ existing );
234
+
235
+ $ this ->openTransaction ();
236
+ $ version = id (new PhragmentFragmentVersion ());
237
+ $ version ->setSequence ($ sequence );
238
+ $ version ->setFragmentPHID ($ this ->getPHID ());
239
+ $ version ->setFilePHID (null );
240
+ $ version ->save ();
241
+
242
+ $ this ->setLatestVersionPHID ($ version ->getPHID ());
243
+ $ this ->save ();
244
+ $ this ->saveTransaction ();
245
+ }
246
+
247
+
248
+ /* -( Policy Interface )--------------------------------------------------- */
249
+
250
+
49
251
public function getCapabilities () {
50
252
return array (
51
253
PhabricatorPolicyCapability::CAN_VIEW ,
0 commit comments