Completed
Push — master ( a54945...1b6242 )
by Daniel
12:52
created

Folder::validate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 4
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 5
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
15
/**
16
 * Represents a logical folder, which may be used to organise assets
17
 * stored in the configured backend.
18
 *
19
 * Unlike {@see File} dataobjects, there is not necessarily a physical filesystem entite which
20
 * represents a Folder, and it may be purely logical. However, a physical folder may exist
21
 * if the backend creates one.
22
 *
23
 * Additionally, folders do not have URLs (relative or absolute), nor do they have paths.
24
 *
25
 * When a folder is moved or renamed, records within it will automatically be copied to the updated
26
 * location.
27
 *
28
 * Deleting a folder will remove all child records, but not any physical files.
29
 *
30
 * See {@link File} documentation for more details about the
31
 * relationship between the database and filesystem in the SilverStripe file APIs.
32
 */
33
class Folder extends File {
34
35
	private static $singular_name = "Folder";
36
37
	private static $plural_name = "Folders";
38
39
	private static $table_name = 'Folder';
40
41
	public function exists() {
42
		return $this->isInDB();
43
	}
44
45
	/**
46
	 *
47
	 */
48
	public function populateDefaults() {
49
		parent::populateDefaults();
50
51
		if(!$this->Name) {
52
			$this->Name = _t('AssetAdmin.NEWFOLDER', "NewFolder");
53
		}
54
	}
55
56
	/**
57
	 * Find the given folder or create it as a database record
58
	 *
59
	 * @param string $folderPath Directory path relative to assets root
60
	 * @return Folder|null
61
	 */
62
	public static function find_or_make($folderPath) {
63
		// replace leading and trailing slashes
64
		$folderPath = preg_replace('/^\/?(.*)\/?$/', '$1', trim($folderPath));
65
		$parts = explode("/",$folderPath);
66
67
		$parentID = 0;
68
		$item = null;
69
		$filter = FileNameFilter::create();
70
		foreach($parts as $part) {
71
			if(!$part) {
72
				continue; // happens for paths with a trailing slash
73
			}
74
75
			// Ensure search includes folders with illegal characters removed, but
76
			// err in favour of matching existing folders if $folderPath
77
			// includes illegal characters itself.
78
			$partSafe = $filter->filter($part);
79
			$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...
80
				'ParentID' => $parentID,
81
				'Name' => array($partSafe, $part)
82
			))->first();
83
84
			if(!$item) {
85
				$item = new Folder();
86
				$item->ParentID = $parentID;
87
				$item->Name = $partSafe;
88
				$item->Title = $part;
89
				$item->write();
90
			}
91
			$parentID = $item->ID;
92
		}
93
94
		return $item;
95
	}
96
97
	public function onBeforeDelete() {
98
		foreach($this->AllChildren() as $child) {
99
			$child->delete();
100
		}
101
102
		parent::onBeforeDelete();
103
	}
104
105
	/**
106
 	 * Return the relative URL of an icon for this file type
107
 	 *
108
 	 * @return string
109
 	 */
110
 	public function getIcon() {
111
 		return FRAMEWORK_DIR . "/client/dist/images/app_icons/folder_32.png";
112
 	}
113
114
	/**
115
	 * Override setting the Title of Folders to that Name and Title are always in sync.
116
	 * Note that this is not appropriate for files, because someone might want to create a human-readable name
117
	 * of a file that is different from its name on disk. But folders should always match their name on disk.
118
	 *
119
	 * @param string $title
120
	 * @return $this
121
	 */
122
	public function setTitle($title) {
123
		$this->setName($title);
124
		return $this;
125
	}
126
127
	/**
128
	 * Get the folder title
129
	 *
130
	 * @return string
131
	 */
132
	public function getTitle() {
133
		return $this->Name;
134
	}
135
136
	/**
137
	 * Override setting the Title of Folders to that Name and Title are always in sync.
138
	 * Note that this is not appropriate for files, because someone might want to create a human-readable name
139
	 * of a file that is different from its name on disk. But folders should always match their name on disk.
140
	 *
141
	 * @param string $name
142
	 * @return $this
143
	 */
144
	public function setName($name) {
145
		parent::setName($name);
146
		$this->setField('Title', $this->Name);
147
		return $this;
148
	}
149
150
	/**
151
	 * A folder doesn't have a (meaningful) file size.
152
	 *
153
	 * @return null
154
	 */
155
	public function getSize() {
156
		return null;
157
	}
158
159
	/**
160
	 * Returns all children of this folder
161
	 *
162
	 * @return DataList
163
	 */
164
	public function myChildren() {
165
		return File::get()->filter("ParentID", $this->ID);
166
	}
167
168
	/**
169
	 * Returns true if this folder has children
170
	 *
171
	 * @return bool
172
	 */
173
	public function hasChildren() {
174
		return $this->myChildren()->exists();
175
	}
176
177
	/**
178
	 * Returns true if this folder has children
179
	 *
180
	 * @return bool
181
	 */
182
	public function hasChildFolders() {
183
		return $this->ChildFolders()->exists();
184
	}
185
186
	/**
187
	 * Return the FieldList used to edit this folder in the CMS.
188
	 * You can modify this FieldList by subclassing folder, or by creating a {@link DataExtension}
189
	 * and implemeting updateCMSFields(FieldList $fields) on that extension.
190
	 *
191
	 * @return FieldList
192
	 */
193
	public function getCMSFields() {
194
		// Don't show readonly path until we can implement parent folder selection,
195
		// it's too confusing when readonly (makes sense for files only).
196
197
		$fields = FieldList::create([
198
			HeaderField::create('TitleHeader', $this->Title, 1),
199
			LiteralField::create("ImageFull", $this->PreviewThumbnail()),
200
			TextField::create("Name", $this->fieldLabel('Filename')),
201
			HiddenField::create('ID', $this->ID)
202
		]);
203
204
		$this->extend('updateCMSFields', $fields);
205
206
		return $fields;
207
	}
208
209
	/**
210
	 * Get the children of this folder that are also folders.
211
	 *
212
	 * @return DataList
213
	 */
214
	public function ChildFolders() {
215
		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...
216
	}
217
218
	/**
219
	 * Get the number of children of this folder that are also folders.
220
	 *
221
	 * @return int
222
	 */
223
	public function numChildFolders() {
224
		return $this->ChildFolders()->count();
225
	}
226
	/**
227
	 * @return string
228
	 */
229
	public function CMSTreeClasses() {
230
		$classes = sprintf('class-%s', $this->class);
231
232
		if(!$this->canDelete()) {
233
			$classes .= " nodelete";
234
		}
235
236
		if(!$this->canEdit()) {
237
			$classes .= " disabled";
238
		}
239
240
		$classes .= $this->markingClasses('numChildFolders');
241
242
		return $classes;
243
	}
244
245
	/**
246
	 * @return string
247
	 */
248
	public function getTreeTitle() {
249
		return sprintf(
250
			"<span class=\"jstree-foldericon\"></span><span class=\"item\">%s</span>",
251
			Convert::raw2att(preg_replace('~\R~u', ' ', $this->Title))
252
		);
253
	}
254
255
	public function getFilename() {
256
		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...
257
	}
258
259
	/**
260
	 * Folders do not have public URLs
261
	 *
262
	 * @param bool $grant
263
	 * @return null|string
264
	 */
265
	public function getURL($grant = true) {
266
		return null;
267
	}
268
269
	/**
270
	 * Folders do not have public URLs
271
	 *
272
	 * @return string
273
	 */
274
	public function getAbsoluteURL() {
275
		return null;
276
	}
277
278
	public function onAfterWrite() {
279
		parent::onAfterWrite();
280
281
		// No publishing UX for folders, so just cascade changes live
282
		if(Versioned::get_stage() === Versioned::DRAFT) {
283
			$this->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE);
284
		}
285
286
		// Update draft version of all child records
287
		$this->updateChildFilesystem();
288
	}
289
290
	public function onAfterDelete() {
291
		parent::onAfterDelete();
292
293
		// Cascade deletions to live
294
		if(Versioned::get_stage() === Versioned::DRAFT) {
295
			$this->deleteFromStage(Versioned::LIVE);
296
		}
297
	}
298
299
	public function updateFilesystem() {
300
		// No filesystem changes to update
301
	}
302
303
	/**
304
	 * If a write is skipped due to no changes, ensure that nested records still get asked to update
305
	 */
306
	public function onAfterSkippedWrite() {
307
		$this->updateChildFilesystem();
308
	}
309
310
	/**
311
	 * Update filesystem of all children
312
	 */
313
	public function updateChildFilesystem() {
314
		// Don't synchronise on live (rely on publishing instead)
315
		if(Versioned::get_stage() === Versioned::LIVE) {
316
			return;
317
		}
318
319
		$this->flushCache();
320
		// Writing this record should trigger a write (and potential updateFilesystem) on each child
321
		foreach ($this->AllChildren() as $child) {
322
			$child->write();
323
		}
324
	}
325
326
	public function StripThumbnail() {
327
		return null;
328
	}
329
330
	public function validate() {
331
		$result = ValidationResult::create();
332
		$this->extend('validate', $result);
333
		return $result;
334
	}
335
}
336