Completed
Push — master ( 6bd7a6...d192a4 )
by Sam
08:56
created

Folder   B

Complexity

Total Complexity 35

Size/Duplication

Total Lines 321
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 16

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 321
rs 7.6153
wmc 35
lcom 3
cbo 16

25 Methods

Rating   Name   Duplication   Size   Complexity  
A exists() 0 4 1
B find_or_make() 0 35 4
A onBeforeDelete() 0 8 2
A getIcon() 0 4 1
A setTitle() 0 7 1
A getTitle() 0 4 1
A getSize() 0 4 1
A myChildren() 0 4 1
A hasChildren() 0 4 1
A hasChildFolders() 0 4 1
B getCMSFields() 0 31 1
A ChildFolders() 0 4 1
A numChildFolders() 0 4 1
A CMSTreeClasses() 0 16 3
A getTreeTitle() 0 7 1
A getFilename() 0 4 1
A getURL() 0 4 1
A getAbsoluteURL() 0 4 1
A onAfterWrite() 0 12 2
A onAfterDelete() 0 9 2
A updateFilesystem() 0 4 1
A onAfterSkippedWrite() 0 4 1
A updateChildFilesystem() 0 13 3
A StripThumbnail() 0 4 1
A validate() 0 6 1
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
38
    private static $singular_name = "Folder";
39
40
    private static $plural_name = "Folders";
41
42
    private static $table_name = 'Folder';
43
44
    public function exists()
45
    {
46
        return $this->isInDB();
47
    }
48
49
    /**
50
     * Find the given folder or create it as a database record
51
     *
52
     * @param string $folderPath Directory path relative to assets root
53
     * @return Folder|null
54
     */
55
    public static function find_or_make($folderPath)
56
    {
57
        // replace leading and trailing slashes
58
        $folderPath = preg_replace('/^\/?(.*)\/?$/', '$1', trim($folderPath));
59
        $parts = explode("/", $folderPath);
60
61
        $parentID = 0;
62
        $item = null;
63
        $filter = FileNameFilter::create();
64
        foreach ($parts as $part) {
65
            if (!$part) {
66
                continue; // happens for paths with a trailing slash
67
            }
68
69
            // Ensure search includes folders with illegal characters removed, but
70
            // err in favour of matching existing folders if $folderPath
71
            // includes illegal characters itself.
72
            $partSafe = $filter->filter($part);
73
            $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...
74
                'ParentID' => $parentID,
75
                'Name' => array($partSafe, $part)
76
            ))->first();
77
78
            if (!$item) {
79
                $item = new Folder();
80
                $item->ParentID = $parentID;
81
                $item->Name = $partSafe;
82
                $item->Title = $part;
83
                $item->write();
84
            }
85
            $parentID = $item->ID;
86
        }
87
88
        return $item;
89
    }
90
91
    public function onBeforeDelete()
92
    {
93
        foreach ($this->AllChildren() as $child) {
94
            $child->delete();
95
        }
96
97
        parent::onBeforeDelete();
98
    }
99
100
    /**
101
     * Return the relative URL of an icon for this file type
102
     *
103
     * @return string
104
     */
105
    public function getIcon()
106
    {
107
        return FRAMEWORK_DIR . "/client/dist/images/app_icons/folder_icon_large.png";
108
    }
109
110
    /**
111
     * Override setting the Title of Folders to that Name and Title are always in sync.
112
     * Note that this is not appropriate for files, because someone might want to create a human-readable name
113
     * of a file that is different from its name on disk. But folders should always match their name on disk.
114
     *
115
     * @param string $title
116
     * @return $this
117
     */
118
    public function setTitle($title)
119
    {
120
        $this->setField('Title', $title);
121
        $this->setField('Name', $title);
122
123
        return $this;
124
    }
125
126
    /**
127
     * Get the folder title
128
     *
129
     * @return string
130
     */
131
    public function getTitle()
132
    {
133
        return $this->Name;
134
    }
135
136
    /**
137
     * A folder doesn't have a (meaningful) file size.
138
     *
139
     * @return null
140
     */
141
    public function getSize()
142
    {
143
        return null;
144
    }
145
146
    /**
147
     * Returns all children of this folder
148
     *
149
     * @return DataList
150
     */
151
    public function myChildren()
152
    {
153
        return File::get()->filter("ParentID", $this->ID);
154
    }
155
156
    /**
157
     * Returns true if this folder has children
158
     *
159
     * @return bool
160
     */
161
    public function hasChildren()
162
    {
163
        return $this->myChildren()->exists();
164
    }
165
166
    /**
167
     * Returns true if this folder has children
168
     *
169
     * @return bool
170
     */
171
    public function hasChildFolders()
172
    {
173
        return $this->ChildFolders()->exists();
174
    }
175
176
    /**
177
     * Return the FieldList used to edit this folder in the CMS.
178
     * You can modify this FieldList by subclassing folder, or by creating a {@link DataExtension}
179
     * and implemeting updateCMSFields(FieldList $fields) on that extension.
180
     *
181
     * @return FieldList
182
     */
183
    public function getCMSFields()
184
    {
185
        // Don't show readonly path until we can implement parent folder selection,
186
        // it's too confusing when readonly (makes sense for files only).
187
188
        $width = (int)Image::config()->get('asset_preview_width');
189
        $previewLink = Convert::raw2att($this->ScaleMaxWidth($width)->getIcon());
190
        $image = "<img src=\"{$previewLink}\" class=\"editor__thumbnail\" />";
191
192
        $content = Tab::create(
193
            'Main',
194
            HeaderField::create('TitleHeader', $this->Title, 1)
195
                ->addExtraClass('editor__heading'),
196
            LiteralField::create("IconFull", $image)
197
                ->addExtraClass('editor__file-preview'),
198
            TabSet::create(
199
                'Editor',
200
                Tab::create(
201
                    'Details',
202
                    TextField::create("Name", $this->fieldLabel('Filename'))
203
                )
204
            ),
205
            HiddenField::create('ID', $this->ID)
206
        );
207
208
        $fields = FieldList::create(TabSet::create('Root', $content));
209
210
        $this->extend('updateCMSFields', $fields);
211
212
        return $fields;
213
    }
214
215
    /**
216
     * Get the children of this folder that are also folders.
217
     *
218
     * @return DataList
219
     */
220
    public function ChildFolders()
221
    {
222
        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...
223
    }
224
225
    /**
226
     * Get the number of children of this folder that are also folders.
227
     *
228
     * @return int
229
     */
230
    public function numChildFolders()
231
    {
232
        return $this->ChildFolders()->count();
233
    }
234
    /**
235
     * @return string
236
     */
237
    public function CMSTreeClasses()
238
    {
239
        $classes = sprintf('class-%s', $this->class);
240
241
        if (!$this->canDelete()) {
242
            $classes .= " nodelete";
243
        }
244
245
        if (!$this->canEdit()) {
246
            $classes .= " disabled";
247
        }
248
249
        $classes .= $this->markingClasses('numChildFolders');
250
251
        return $classes;
252
    }
253
254
    /**
255
     * @return string
256
     */
257
    public function getTreeTitle()
258
    {
259
        return sprintf(
260
            "<span class=\"jstree-foldericon\"></span><span class=\"item\">%s</span>",
261
            Convert::raw2att(preg_replace('~\R~u', ' ', $this->Title))
262
        );
263
    }
264
265
    public function getFilename()
266
    {
267
        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...
268
    }
269
270
    /**
271
     * Folders do not have public URLs
272
     *
273
     * @param bool $grant
274
     * @return null|string
275
     */
276
    public function getURL($grant = true)
277
    {
278
        return null;
279
    }
280
281
    /**
282
     * Folders do not have public URLs
283
     *
284
     * @return string
285
     */
286
    public function getAbsoluteURL()
287
    {
288
        return null;
289
    }
290
291
    public function onAfterWrite()
292
    {
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
    {
306
        parent::onAfterDelete();
307
308
        // Cascade deletions to live
309
        if (Versioned::get_stage() === Versioned::DRAFT) {
310
            $this->deleteFromStage(Versioned::LIVE);
311
        }
312
    }
313
314
    public function updateFilesystem()
315
    {
316
        // No filesystem changes to update
317
    }
318
319
    /**
320
     * If a write is skipped due to no changes, ensure that nested records still get asked to update
321
     */
322
    public function onAfterSkippedWrite()
323
    {
324
        $this->updateChildFilesystem();
325
    }
326
327
    /**
328
     * Update filesystem of all children
329
     */
330
    public function updateChildFilesystem()
331
    {
332
        // Don't synchronise on live (rely on publishing instead)
333
        if (Versioned::get_stage() === Versioned::LIVE) {
334
            return;
335
        }
336
337
        $this->flushCache();
338
        // Writing this record should trigger a write (and potential updateFilesystem) on each child
339
        foreach ($this->AllChildren() as $child) {
340
            $child->write();
341
        }
342
    }
343
344
    public function StripThumbnail()
345
    {
346
        return null;
347
    }
348
349
    public function validate()
350
    {
351
        $result = ValidationResult::create();
352
        $this->extend('validate', $result);
353
        return $result;
354
    }
355
}
356