Completed
Push — master ( 955d75...4d8fb1 )
by Ingo
36:25 queued 24:15
created

Folder::updateChildFilesystem()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 12
rs 9.4285
1
<?php
2
3
namespace SilverStripe\Assets;
4
5
use SilverStripe\Core\Convert;
6
use SilverStripe\Forms\FieldList;
7
use SilverStripe\Forms\HeaderField;
8
use SilverStripe\Forms\HiddenField;
9
use SilverStripe\Forms\LiteralField;
10
use SilverStripe\Forms\TextField;
11
use SilverStripe\ORM\DataList;
12
use SilverStripe\ORM\ValidationResult;
13
use SilverStripe\ORM\Versioning\Versioned;
14
use SilverStripe\Forms\Tab;
15
use SilverStripe\Forms\TabSet;
16
17
/**
18
 * Represents a logical folder, which may be used to organise assets
19
 * stored in the configured backend.
20
 *
21
 * Unlike {@see File} dataobjects, there is not necessarily a physical filesystem entite which
22
 * represents a Folder, and it may be purely logical. However, a physical folder may exist
23
 * if the backend creates one.
24
 *
25
 * Additionally, folders do not have URLs (relative or absolute), nor do they have paths.
26
 *
27
 * When a folder is moved or renamed, records within it will automatically be copied to the updated
28
 * location.
29
 *
30
 * Deleting a folder will remove all child records, but not any physical files.
31
 *
32
 * See {@link File} documentation for more details about the
33
 * relationship between the database and filesystem in the SilverStripe file APIs.
34
 */
35
class Folder extends File {
36
37
	private static $singular_name = "Folder";
38
39
	private static $plural_name = "Folders";
40
41
	private static $table_name = 'Folder';
42
43
	public function exists() {
44
		return $this->isInDB();
45
	}
46
47
	/**
48
	 *
49
	 */
50
	public function populateDefaults() {
51
		parent::populateDefaults();
52
53
		if(!$this->Name) {
54
			$this->Name = _t('AssetAdmin.NEWFOLDER', "NewFolder");
55
		}
56
	}
57
58
	/**
59
	 * Find the given folder or create it as a database record
60
	 *
61
	 * @param string $folderPath Directory path relative to assets root
62
	 * @return Folder|null
63
	 */
64
	public static function find_or_make($folderPath) {
65
		// replace leading and trailing slashes
66
		$folderPath = preg_replace('/^\/?(.*)\/?$/', '$1', trim($folderPath));
67
		$parts = explode("/",$folderPath);
68
69
		$parentID = 0;
70
		$item = null;
71
		$filter = FileNameFilter::create();
72
		foreach($parts as $part) {
73
			if(!$part) {
74
				continue; // happens for paths with a trailing slash
75
			}
76
77
			// Ensure search includes folders with illegal characters removed, but
78
			// err in favour of matching existing folders if $folderPath
79
			// includes illegal characters itself.
80
			$partSafe = $filter->filter($part);
81
			$item = Folder::get()->filter(array(
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
82
				'ParentID' => $parentID,
83
				'Name' => array($partSafe, $part)
84
			))->first();
85
86
			if(!$item) {
87
				$item = new Folder();
88
				$item->ParentID = $parentID;
89
				$item->Name = $partSafe;
90
				$item->Title = $part;
91
				$item->write();
92
			}
93
			$parentID = $item->ID;
94
		}
95
96
		return $item;
97
	}
98
99
	public function onBeforeDelete() {
100
		foreach($this->AllChildren() as $child) {
101
			$child->delete();
102
		}
103
104
		parent::onBeforeDelete();
105
	}
106
107
	/**
108
 	 * Return the relative URL of an icon for this file type
109
 	 *
110
 	 * @return string
111
 	 */
112
 	public function getIcon() {
113
 		return FRAMEWORK_DIR . "/client/dist/images/app_icons/folder_32.png";
114
 	}
115
116
	/**
117
	 * Override setting the Title of Folders to that Name and Title are always in sync.
118
	 * Note that this is not appropriate for files, because someone might want to create a human-readable name
119
	 * of a file that is different from its name on disk. But folders should always match their name on disk.
120
	 *
121
	 * @param string $title
122
	 * @return $this
123
	 */
124
	public function setTitle($title) {
125
		$this->setName($title);
126
		return $this;
127
	}
128
129
	/**
130
	 * Get the folder title
131
	 *
132
	 * @return string
133
	 */
134
	public function getTitle() {
135
		return $this->Name;
136
	}
137
138
	/**
139
	 * Override setting the Title of Folders to that Name and Title are always in sync.
140
	 * Note that this is not appropriate for files, because someone might want to create a human-readable name
141
	 * of a file that is different from its name on disk. But folders should always match their name on disk.
142
	 *
143
	 * @param string $name
144
	 * @return $this
145
	 */
146
	public function setName($name) {
147
		parent::setName($name);
148
		$this->setField('Title', $this->Name);
149
		return $this;
150
	}
151
152
	/**
153
	 * A folder doesn't have a (meaningful) file size.
154
	 *
155
	 * @return null
156
	 */
157
	public function getSize() {
158
		return null;
159
	}
160
161
	/**
162
	 * Returns all children of this folder
163
	 *
164
	 * @return DataList
165
	 */
166
	public function myChildren() {
167
		return File::get()->filter("ParentID", $this->ID);
168
	}
169
170
	/**
171
	 * Returns true if this folder has children
172
	 *
173
	 * @return bool
174
	 */
175
	public function hasChildren() {
176
		return $this->myChildren()->exists();
177
	}
178
179
	/**
180
	 * Returns true if this folder has children
181
	 *
182
	 * @return bool
183
	 */
184
	public function hasChildFolders() {
185
		return $this->ChildFolders()->exists();
186
	}
187
188
	/**
189
	 * Return the FieldList used to edit this folder in the CMS.
190
	 * You can modify this FieldList by subclassing folder, or by creating a {@link DataExtension}
191
	 * and implemeting updateCMSFields(FieldList $fields) on that extension.
192
	 *
193
	 * @return FieldList
194
	 */
195
	public function getCMSFields() {
196
		// Don't show readonly path until we can implement parent folder selection,
197
		// it's too confusing when readonly (makes sense for files only).
198
199
		$width = (int)Image::config()->get('asset_preview_width');
200
		$previewLink = Convert::raw2att($this->ScaleMaxWidth($width)->getIcon());
201
		$image = "<img src=\"{$previewLink}\" class=\"editor__thumbnail\" />";
202
203
		$content = Tab::create('Main',
204
			HeaderField::create('TitleHeader', $this->Title, 1)
205
				->addExtraClass('editor__heading'),
206
			LiteralField::create("IconFull", $image)
207
				->addExtraClass('editor__file-preview'),
208
			TabSet::create('Editor',
209
				Tab::create('Details',
210
					TextField::create("Name", $this->fieldLabel('Filename'))
211
				)
212
			),
213
			HiddenField::create('ID', $this->ID)
214
		);
215
216
		$fields = FieldList::create(TabSet::create('Root', $content));
217
218
		$this->extend('updateCMSFields', $fields);
219
220
		return $fields;
221
	}
222
223
	/**
224
	 * Get the children of this folder that are also folders.
225
	 *
226
	 * @return DataList
227
	 */
228
	public function ChildFolders() {
229
		return Folder::get()->filter('ParentID', $this->ID);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
230
	}
231
232
	/**
233
	 * Get the number of children of this folder that are also folders.
234
	 *
235
	 * @return int
236
	 */
237
	public function numChildFolders() {
238
		return $this->ChildFolders()->count();
239
	}
240
	/**
241
	 * @return string
242
	 */
243
	public function CMSTreeClasses() {
244
		$classes = sprintf('class-%s', $this->class);
245
246
		if(!$this->canDelete()) {
247
			$classes .= " nodelete";
248
		}
249
250
		if(!$this->canEdit()) {
251
			$classes .= " disabled";
252
		}
253
254
		$classes .= $this->markingClasses('numChildFolders');
255
256
		return $classes;
257
	}
258
259
	/**
260
	 * @return string
261
	 */
262
	public function getTreeTitle() {
263
		return sprintf(
264
			"<span class=\"jstree-foldericon\"></span><span class=\"item\">%s</span>",
265
			Convert::raw2att(preg_replace('~\R~u', ' ', $this->Title))
266
		);
267
	}
268
269
	public function getFilename() {
270
		return parent::generateFilename() . '/';
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (generateFilename() instead of getFilename()). Are you sure this is correct? If so, you might want to change this to $this->generateFilename().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
271
	}
272
273
	/**
274
	 * Folders do not have public URLs
275
	 *
276
	 * @param bool $grant
277
	 * @return null|string
278
	 */
279
	public function getURL($grant = true) {
280
		return null;
281
	}
282
283
	/**
284
	 * Folders do not have public URLs
285
	 *
286
	 * @return string
287
	 */
288
	public function getAbsoluteURL() {
289
		return null;
290
	}
291
292
	public function onAfterWrite() {
293
		parent::onAfterWrite();
294
295
		// No publishing UX for folders, so just cascade changes live
296
		if(Versioned::get_stage() === Versioned::DRAFT) {
297
			$this->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
298
		}
299
300
		// Update draft version of all child records
301
		$this->updateChildFilesystem();
302
	}
303
304
	public function onAfterDelete() {
305
		parent::onAfterDelete();
306
307
		// Cascade deletions to live
308
		if(Versioned::get_stage() === Versioned::DRAFT) {
309
			$this->deleteFromStage(Versioned::LIVE);
310
		}
311
	}
312
313
	public function updateFilesystem() {
314
		// No filesystem changes to update
315
	}
316
317
	/**
318
	 * If a write is skipped due to no changes, ensure that nested records still get asked to update
319
	 */
320
	public function onAfterSkippedWrite() {
321
		$this->updateChildFilesystem();
322
	}
323
324
	/**
325
	 * Update filesystem of all children
326
	 */
327
	public function updateChildFilesystem() {
328
		// Don't synchronise on live (rely on publishing instead)
329
		if(Versioned::get_stage() === Versioned::LIVE) {
330
			return;
331
		}
332
333
		$this->flushCache();
334
		// Writing this record should trigger a write (and potential updateFilesystem) on each child
335
		foreach ($this->AllChildren() as $child) {
336
			$child->write();
337
		}
338
	}
339
340
	public function StripThumbnail() {
341
		return null;
342
	}
343
344
	public function validate() {
345
		$result = ValidationResult::create();
346
		$this->extend('validate', $result);
347
		return $result;
348
	}
349
}
350