Completed
Pull Request — master (#5157)
by Damian
11:28
created

Folder::onAfterDelete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
/**
3
 * Represents a logical folder, which may be used to organise assets
4
 * stored in the configured backend.
5
 *
6
 * Unlike {@see File} dataobjects, there is not necessarily a physical filesystem entite which
7
 * represents a Folder, and it may be purely logical. However, a physical folder may exist
8
 * if the backend creates one.
9
 *
10
 * Additionally, folders do not have URLs (relative or absolute), nor do they have paths.
11
 *
12
 * When a folder is moved or renamed, records within it will automatically be copied to the updated
13
 * location.
14
 *
15
 * Deleting a folder will remove all child records, but not any physical files.
16
 *
17
 * See {@link File} documentation for more details about the
18
 * relationship between the database and filesystem in the SilverStripe file APIs.
19
 *
20
 * @package framework
21
 * @subpackage filesystem
22
 */
23
class Folder extends File {
24
25
	private static $singular_name = "Folder";
26
27
	private static $plural_name = "Folders";
28
29
	public function exists() {
30
		return $this->isInDB();
31
	}
32
33
	/**
34
	 *
35
	 */
36
	public function populateDefaults() {
37
		parent::populateDefaults();
38
39
		if(!$this->Name) {
40
			$this->Name = _t('AssetAdmin.NEWFOLDER', "NewFolder");
41
		}
42
	}
43
44
	/**
45
	 * Find the given folder or create it as a database record
46
	 *
47
	 * @param string $folderPath Directory path relative to assets root
48
	 * @return Folder|null
49
	 */
50
	public static function find_or_make($folderPath) {
51
		// replace leading and trailing slashes
52
		$folderPath = preg_replace('/^\/?(.*)\/?$/', '$1', trim($folderPath));
53
		$parts = explode("/",$folderPath);
54
55
		$parentID = 0;
56
		$item = null;
57
		$filter = FileNameFilter::create();
58
		foreach($parts as $part) {
59
			if(!$part) {
60
				continue; // happens for paths with a trailing slash
61
			}
62
63
			// Ensure search includes folders with illegal characters removed, but
64
			// err in favour of matching existing folders if $folderPath
65
			// includes illegal characters itself.
66
			$partSafe = $filter->filter($part);
67
			$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...
68
				'ParentID' => $parentID,
69
				'Name' => array($partSafe, $part)
70
			))->first();
71
72
			if(!$item) {
73
				$item = new Folder();
74
				$item->ParentID = $parentID;
75
				$item->Name = $partSafe;
76
				$item->Title = $part;
77
				$item->write();
78
			}
79
			$parentID = $item->ID;
80
		}
81
82
		return $item;
83
	}
84
85
	public function onBeforeDelete() {
86
		foreach($this->AllChildren() as $child) {
87
			$child->delete();
88
		}
89
90
		parent::onBeforeDelete();
91
	}
92
93
	/**
94
	 * Override setting the Title of Folders to that Name and Title are always in sync.
95
	 * Note that this is not appropriate for files, because someone might want to create a human-readable name
96
	 * of a file that is different from its name on disk. But folders should always match their name on disk.
97
	 *
98
	 * @param string $title
99
	 * @return $this
100
	 */
101
	public function setTitle($title) {
102
		$this->setName($title);
103
		return $this;
104
	}
105
106
	/**
107
	 * Get the folder title
108
	 *
109
	 * @return string
110
	 */
111
	public function getTitle() {
112
		return $this->Name;
113
	}
114
115
	/**
116
	 * Override setting the Title of Folders to that Name and Title are always in sync.
117
	 * Note that this is not appropriate for files, because someone might want to create a human-readable name
118
	 * of a file that is different from its name on disk. But folders should always match their name on disk.
119
	 *
120
	 * @param string $name
121
	 * @return $this
122
	 */
123
	public function setName($name) {
124
		parent::setName($name);
125
		$this->setField('Title', $this->Name);
126
		return $this;
127
	}
128
129
	/**
130
	 * A folder doesn't have a (meaningful) file size.
131
	 *
132
	 * @return null
133
	 */
134
	public function getSize() {
135
		return null;
136
	}
137
138
	/**
139
	 * Returns all children of this folder
140
	 *
141
	 * @return DataList
142
	 */
143
	public function myChildren() {
144
		return File::get()->filter("ParentID", $this->ID);
145
	}
146
147
	/**
148
	 * Returns true if this folder has children
149
	 *
150
	 * @return bool
151
	 */
152
	public function hasChildren() {
153
		return $this->myChildren()->exists();
154
	}
155
156
	/**
157
	 * Returns true if this folder has children
158
	 *
159
	 * @return bool
160
	 */
161
	public function hasChildFolders() {
162
		return $this->ChildFolders()->exists();
163
	}
164
165
	/**
166
	 * Return the FieldList used to edit this folder in the CMS.
167
	 * You can modify this FieldList by subclassing folder, or by creating a {@link DataExtension}
168
	 * and implemeting updateCMSFields(FieldList $fields) on that extension.
169
	 *
170
	 * @return FieldList
171
	 */
172
	public function getCMSFields() {
173
		// Hide field on root level, which can't be renamed
174
		if(!$this->ID || $this->ID === "root") {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->ID (integer) and 'root' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
175
			$titleField = new HiddenField("Name");
176
		} else {
177
			$titleField = new TextField("Name", $this->fieldLabel('Name'));
178
		}
179
180
		$fields = new FieldList(
181
			$titleField,
182
			new HiddenField('ParentID')
183
		);
184
		$this->extend('updateCMSFields', $fields);
185
186
		return $fields;
187
	}
188
189
	/**
190
	 * Get the children of this folder that are also folders.
191
	 *
192
	 * @return DataList
193
	 */
194
	public function ChildFolders() {
195
		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...
196
	}
197
198
	/**
199
	 * Get the number of children of this folder that are also folders.
200
	 *
201
	 * @return int
202
	 */
203
	public function numChildFolders() {
204
		return $this->ChildFolders()->count();
205
	}
206
	/**
207
	 * @return string
208
	 */
209
	public function CMSTreeClasses() {
210
		$classes = sprintf('class-%s', $this->class);
211
212
		if(!$this->canDelete()) {
213
			$classes .= " nodelete";
214
		}
215
216
		if(!$this->canEdit()) {
217
			$classes .= " disabled";
218
		}
219
220
		$classes .= $this->markingClasses('numChildFolders');
221
222
		return $classes;
223
	}
224
225
	/**
226
	 * @return string
227
	 */
228
	public function getTreeTitle() {
229
		return sprintf(
230
			"<span class=\"jstree-foldericon\"></span><span class=\"item\">%s</span>",
231
			Convert::raw2att(preg_replace('~\R~u', ' ', $this->Title))
232
		);
233
	}
234
235
	public function getFilename() {
236
		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...
237
	}
238
239
	/**
240
	 * Folders do not have public URLs
241
	 *
242
	 * @param bool $grant
243
	 * @return null|string
244
	 */
245
	public function getURL($grant = true) {
246
		return null;
247
	}
248
249
	/**
250
	 * Folders do not have public URLs
251
	 *
252
	 * @return string
253
	 */
254
	public function getAbsoluteURL() {
255
		return null;
256
	}
257
258
	public function onAfterWrite() {
259
		parent::onAfterWrite();
260
261
		// No publishing UX for folders, so just cascade changes live
262
		if(Versioned::get_stage() === Versioned::DRAFT) {
263
			$this->publish(Versioned::DRAFT, Versioned::LIVE);
0 ignored issues
show
Bug introduced by
The method publish() does not exist on Folder. Did you maybe mean onBeforePublish()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
264
		}
265
266
		// Update draft version of all child records
267
		$this->updateChildFilesystem();
268
	}
269
270
	public function onAfterDelete() {
271
		parent::onAfterDelete();
272
273
		// Cascade deletions to live
274
		if(Versioned::get_stage() === Versioned::DRAFT) {
275
			$this->deleteFromStage(Versioned::LIVE);
276
		}
277
	}
278
279
	public function updateFilesystem() {
280
		// No filesystem changes to update
281
	}
282
283
	/**
284
	 * If a write is skipped due to no changes, ensure that nested records still get asked to update
285
	 */
286
	public function onAfterSkippedWrite() {
287
		$this->updateChildFilesystem();
288
	}
289
290
	/**
291
	 * Update filesystem of all children
292
	 */
293
	public function updateChildFilesystem() {
294
		// Don't synchronise on live (rely on publishing instead)
295
		if(Versioned::get_stage() === Versioned::LIVE) {
296
			return;
297
		}
298
299
		$this->flushCache();
300
		// Writing this record should trigger a write (and potential updateFilesystem) on each child
301
		foreach ($this->AllChildren() as $child) {
302
			$child->write();
303
		}
304
	}
305
306
	public function StripThumbnail() {
307
		return null;
308
	}
309
}
310