Completed
Push — master ( 6e0e08...1475f4 )
by Nicola
03:59
created

CarouselImageExtension::getCarouselEditFields()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 13
rs 9.4285
cc 2
eloc 6
nc 2
nop 0
1
<?php
2
3
/**
4
 * Provide additional methods specially crafted for carousels.
5
 *
6
 * @package silverstripe-carousel
7
 */
8
class CarouselImageExtension extends DataExtension
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
    /**
11
     * Generate a scaled image suitable for a carousel.
12
     *
13
     * If $width and $height are greater than 0, it is equivalent to
14
     * Image_Backend::croppedResize().
15
     *
16
     * If only $width is greater than 0, it is equivalent to SetWidth().
17
     *
18
     * If only $height is greater than 0, it is equivalent to
19
     * SetHeight().
20
     *
21
     * If neither $width or $height are greater than 0, return the
22
     * original image.
23
     *
24
     * @param  integer $width   The width to set or 0.
25
     * @param  integer $height  The height to set or 0.
26
     * @return Image_Backend
27
     */
28
    public function MaybeCroppedImage($width, $height)
29
    {
30
        return $this->owner->getFormattedImage('MaybeCroppedImage', $width, $height);
31
    }
32
33
    /**
34
     * Low level function for CarouselImageExtension::MaybeCroppedImage().
35
     *
36
     * @param  Image_Backend $backend
37
     * @param  integer $width
38
     * @param  integer $height
39
     * @return Image_Backend
40
     */
41
    public function generateMaybeCroppedImage(Image_Backend $backend, $width, $height)
42
    {
43
        if ($width > 0 && $height > 0) {
44
            return $backend->croppedResize($width, $height);
45
        } elseif ($width > 0) {
46
            return $backend->resizeByWidth($width);
47
        } elseif ($height > 0) {
48
            return $backend->resizeByHeight($height);
49
        } else {
50
            return $backend;
51
        }
52
    }
53
54
    /**
55
     * Retrieve the fields used by SortableUploadField internal form.
56
     *
57
     * @return FieldList
58
     */
59
    public function getCarouselEditFields()
60
    {
61
        // This is *required* otherwise TinyMCE in SilverStripe 3.3 will
62
        // not be enabled and the <textarea> will simply disappear
63
        // without apparent reasons
64
        if (method_exists('HtmlEditorConfig', 'require_js')) {
65
            HtmlEditorConfig::require_js();
66
        }
67
68
        $fields = FieldList::create();
69
        $fields->push(CarouselCaptionField::create('Content', _t('CarouselPage.Caption')));
70
        return $fields;
71
    }
72
}
73
74
/**
75
 * This class is needed in order to handle the 'Content' field of the
76
 * 'File' table with the WYSIWYG editor. That field is TEXT, hence just
77
 * using HtmlEditorField will result in an error when the 'saveInto'
78
 * method is called.
79
 *
80
 * @package silverstripe-carousel
81
 */
82
class CarouselCaptionField extends HtmlEditorField
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
83
{
84
    /**
85
     * Override the default constructor to have saner settings.
86
     *
87
     * @param string      $name  The internal field name, passed to forms.
88
     * @param string|null $title The human-readable field label.
89
     * @param mixed       $value The value of the field.
90
     */
91
    public function __construct($name, $title = null, $value = '')
92
    {
93
        parent::__construct($name, $title, $value);
94
        $this->rows = 5;
95
        // The .htmleditor class enables TinyMCE
96
        $this->addExtraClass('htmleditor');
97
    }
98
99
    /**
100
     * Implementation directly borrowed from HtmlEditorField
101
     * without the blocking or useless code.
102
     *
103
     * @param DataObjectInterface $record
104
     */
105
    public function saveInto(DataObjectInterface $record)
106
    {
107
        $htmlValue = Injector::inst()->create('HTMLValue', $this->value);
108
109
        // Sanitise if requested
110
        if ($this->config()->sanitise_server_side) {
111
            $santiser = Injector::inst()->create('HtmlEditorSanitiser', HtmlEditorConfig::get_active());
112
            $santiser->sanitise($htmlValue);
113
        }
114
115
        $this->extend('processHTML', $htmlValue);
116
        $record->{$this->name} = $htmlValue->getContent();
117
    }
118
}
119
120
/**
121
 * Basic page type owning a carousel.
122
 *
123
 * @package silverstripe-carousel
124
 */
125
class CarouselPage extends Page
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
126
{
127
    private static $icon = 'carousel/img/carousel.png';
0 ignored issues
show
Unused Code introduced by
The property $icon is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
128
129
    private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
130
        'Captions' => 'Boolean',
131
        'Width'    => 'Int',
132
        'Height'   => 'Int',
133
    );
134
135
    private static $many_many = array(
0 ignored issues
show
Unused Code introduced by
The property $many_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
136
        'Images'   => 'Image',
137
    );
138
139
    private static $many_many_extraFields = array(
0 ignored issues
show
Unused Code introduced by
The property $many_many_extraFields is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
140
        'Images'   => array(
141
            'SortOrder' => 'Int',
142
        ),
143
    );
144
145
146
    /**
147
     * Search the first class name (that must have a 'Page' suffix) in
148
     * the object hierarchy that has a correspoding folder in
149
     * ASSETS_PATH, that is a folder with the same name with the 'Page'
150
     * suffix stripped out. This folder will be returned and used as
151
     * custom folder in the upload field.
152
     *
153
     * For example, if this class is `HomePage` and it is inherited from
154
     * `CarouselPage`, this function will check for `Home` first and
155
     * `Carousel` after.
156
     *
157
     * If no valid folders are found, `false` is returned.
158
     *
159
     * @return string|false
160
     */
161
    protected function getClassFolder()
162
    {
163
        for ($class = $this->class; $class; $class = get_parent_class($class)) {
164
            $folder = preg_replace('/Page$/', '', $class);
165
            if ($folder != $class && is_dir(ASSETS_PATH . '/' . $folder)) {
166
                return $folder;
167
            }
168
        }
169
170
        // Why false? Because false is the proper value to set in
171
        // setFolderName() to get the default folder (i.e. 'Uploads').
172
        return false;
173
    }
174
175
    /**
176
     * Add the "Images" tab to the content form of the page.
177
     *
178
     * The images are linked to the page with a many-many relationship,
179
     * so if an image is shared among different carousels there is no
180
     * need to upload it multiple times.
181
     *
182
     * @return FieldList
183
     */
184
    public function getCMSFields()
185
    {
186
        $fields = parent::getCMSFields();
187
188
        $field = SortableUploadField::create('Images', _t('CarouselPage.db_Images'));
189
        $field->setFolderName($this->getClassFolder());
190
        $field->setFileEditFields('getCarouselEditFields');
191
192
        $root = $fields->fieldByName('Root');
193
        $tab = $root->fieldByName('Images');
194
        if (! $tab) {
195
            $tab = Tab::create('Images');
196
            $tab->setTitle(_t('CarouselPage.db_Images'));
197
            $root->insertAfter($tab, 'Main');
198
        }
199
        $tab->push($field);
200
201
        return $fields;
202
    }
203
204
    /**
205
     * Add carousel related fields to the page settings.
206
     *
207
     * Every CarouselPage instance can have its own settings, that is
208
     * different pages can own carousels of different sizes.
209
     *
210
     * @return FieldList
211
     */
212
    public function getSettingsFields()
213
    {
214
        $fields = parent::getSettingsFields();
215
216
        $settings = FieldGroup::create(
217
            FieldGroup::create(
218
                NumericField::create('Width', _t('CarouselPage.db_Width')),
219
                NumericField::create('Height', _t('CarouselPage.db_Height')),
220
                CheckboxField::create('Captions', _t('CarouselPage.db_Captions'))
221
            )
222
        );
223
        $settings->setName('Carousel');
224
        $settings->setTitle(_t('CarouselPage.SINGULARNAME'));
225
        $fields->addFieldToTab('Root.Settings', $settings);
226
227
        return $fields;
228
    }
229
230
    /**
231
     * Ensure ThumbnailWidth and ThumbnailHeight are valorized.
232
     *
233
     * Although width and height for the images in the carousel can be
234
     * omitted (see CarouselImageExtension::MaybeCroppedImage() for
235
     * algorithm details) the thumbnail extents must be defined.
236
     *
237
     * @return Validator
238
     */
239
    public function getCMSValidator()
240
    {
241
        return RequiredFields::create(
242
            'ThumbnailWidth',
243
            'ThumbnailHeight'
244
        );
245
    }
246
247
    /**
248
     * Out of the box support for silverstripe/silverstripe-translatable.
249
     *
250
     * Duplicate the image list whenever a new translation is created.
251
     * It the translatable module is not used, this will simply be a
252
     * dead method.
253
     *
254
     * @param boolean $save Whether the new page should be saved to the
255
     *                      database.
256
     */
257
    public function onTranslatableCreate($save)
258
    {
259
        // Chain up the parent method, if it exists
260
        if (method_exists('Page', 'onTranslatableCreate')) {
261
            parent::onTranslatableCreate($save);
262
        }
263
264
        $master = $this->getTranslation(Translatable::default_locale());
265
266
        foreach ($master->Images() as $master_image) {
267
            $image = $master_image->duplicate($save);
268
            $this->Images()->add($image);
269
        }
270
    }
271
}
272
273
/**
274
 * Controller for CarouselPage.
275
 *
276
 * @package silverstripe-carousel
277
 */
278
class CarouselPage_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
279
{
280
    /**
281
     * From the controller the images are returned in proper order.
282
     * This means `<% loop $Images %>` returns the expected result.
283
     */
284
    public function Images()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
285
    {
286
        return $this->dataRecord->Images()->Sort('SortOrder');
287
    }
288
}
289