Completed
Push — master ( 98eea6...214a1e )
by Damian
15s
created

HtmlEditorField::setValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 5
rs 9.4285
c 1
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
/**
3
 * A TinyMCE-powered WYSIWYG HTML editor field with image and link insertion and tracking capabilities. Editor fields
4
 * are created from <textarea> tags, which are then converted with JavaScript.
5
 *
6
 * @package forms
7
 * @subpackage fields-formattedinput
8
 */
9
class HtmlEditorField extends TextareaField {
10
11
	/**
12
	 * Should we check the valid_elements (& extended_valid_elements) rules from HtmlEditorConfig server side?
13
	 *
14
	 * @config
15
	 * @var bool
16
	 */
17
	private static $sanitise_server_side = false;
18
19
	/**
20
	 * Number of rows
21
	 *
22
	 * @config
23
	 * @var int
24
	 */
25
	private static $default_rows = 30;
26
27
	/**
28
	 * ID or instance of editorconfig
29
	 *
30
	 * @var string|HtmlEditorConfig
31
	 */
32
	protected $editorConfig = null;
33
34
	/**
35
	 * Gets the HtmlEditorConfig instance
36
	 *
37
	 * @return HtmlEditorConfig
38
	 */
39
	public function getEditorConfig() {
40
		// Instance override
41
		if($this->editorConfig instanceof HtmlEditorConfig) {
42
			return $this->editorConfig;
43
		}
44
45
		// Get named / active config
46
		return HtmlEditorConfig::get($this->editorConfig);
47
	}
48
49
	/**
50
	 * Assign a new configuration instance or identifier
51
	 *
52
	 * @param string|HtmlEditorConfig $config
53
	 * @return $this
54
	 */
55
	public function setEditorConfig($config) {
56
		$this->editorConfig = $config;
57
		return $this;
58
	}
59
60
	/**
61
	 * Creates a new HTMLEditorField.
62
	 * @see TextareaField::__construct()
63
	 *
64
	 * @param string $name The internal field name, passed to forms.
65
	 * @param string $title The human-readable field label.
66
	 * @param mixed $value The value of the field.
67
	 * @param string $config HtmlEditorConfig identifier to be used. Default to the active one.
68
	 */
69
	public function __construct($name, $title = null, $value = '', $config = null) {
70
		parent::__construct($name, $title, $value);
71
72
		if($config) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $config of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
73
			$this->setEditorConfig($config);
74
		}
75
76
		$this->setRows($this->config()->default_rows);
77
	}
78
79
	public function getAttributes() {
80
		return array_merge(
81
			parent::getAttributes(),
82
			$this->getEditorConfig()->getAttributes()
83
		);
84
	}
85
86
	public function saveInto(DataObjectInterface $record) {
87
		if($record->hasField($this->name) && $record->escapeTypeForField($this->name) != 'xml') {
88
			throw new Exception (
89
				'HtmlEditorField->saveInto(): This field should save into a HTMLText or HTMLVarchar field.'
90
			);
91
		}
92
93
		// Sanitise if requested
94
		$htmlValue = Injector::inst()->create('HTMLValue', $this->Value());
95
		if($this->config()->sanitise_server_side) {
96
			$santiser = Injector::inst()->create('HtmlEditorSanitiser', HtmlEditorConfig::get_active());
97
			$santiser->sanitise($htmlValue);
98
		}
99
100
		// optionally manipulate the HTML after a TinyMCE edit and prior to a save
101
		$this->extend('processHTML', $htmlValue);
102
103
		// Store into record
104
		$record->{$this->name} = $htmlValue->getContent();
105
	}
106
107
	public function setValue($value) {
108
		// Regenerate links prior to preview, so that the editor can see them.
109
		$value = Image::regenerate_html_links($value);
110
		return parent::setValue($value);
111
	}
112
113
	/**
114
	 * @return HtmlEditorField_Readonly
115
	 */
116
	public function performReadonlyTransformation() {
117
		$field = $this->castedCopy('HtmlEditorField_Readonly');
118
		$field->dontEscape = true;
119
120
		return $field;
121
	}
122
123
	public function performDisabledTransformation() {
124
		return $this->performReadonlyTransformation();
125
	}
126
127
	public function Field($properties = array()) {
128
		// Include requirements
129
		$this->getEditorConfig()->init();
130
		return parent::Field($properties);
131
	}
132
}
133
134
/**
135
 * Readonly version of an {@link HTMLEditorField}.
136
 * @package forms
137
 * @subpackage fields-formattedinput
138
 */
139
class HtmlEditorField_Readonly extends ReadonlyField {
140
	public function Field($properties = array()) {
141
		$valforInput = $this->value ? Convert::raw2att($this->value) : "";
142
		return "<span class=\"readonly typography\" id=\"" . $this->id() . "\">"
143
			. ( $this->value && $this->value != '<p></p>' ? $this->value : '<i>(not set)</i>' )
144
			. "</span><input type=\"hidden\" name=\"".$this->name."\" value=\"".$valforInput."\" />";
145
	}
146
	public function Type() {
147
		return 'htmleditorfield readonly';
148
	}
149
}
150
151
/**
152
 * Toolbar shared by all instances of {@link HTMLEditorField}, to avoid too much markup duplication.
153
 *  Needs to be inserted manually into the template in order to function - see {@link LeftAndMain->EditorToolbar()}.
154
 *
155
 * @package forms
156
 * @subpackage fields-formattedinput
157
 */
158
class HtmlEditorField_Toolbar extends RequestHandler {
159
160
	private static $allowed_actions = array(
161
		'LinkForm',
162
		'MediaForm',
163
		'viewfile',
164
		'getanchors'
165
	);
166
167
	/**
168
	 * @var string
169
	 */
170
	protected $templateViewFile = 'HtmlEditorField_viewfile';
171
172
	protected $controller, $name;
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
173
174
	public function __construct($controller, $name) {
175
		parent::__construct();
176
177
		$this->controller = $controller;
178
		$this->name = $name;
179
	}
180
181
	public function forTemplate() {
182
		return sprintf(
183
			'<div id="cms-editor-dialogs" data-url-linkform="%s" data-url-mediaform="%s"></div>',
184
			Controller::join_links($this->controller->Link(), $this->name, 'LinkForm', 'forTemplate'),
185
			Controller::join_links($this->controller->Link(), $this->name, 'MediaForm', 'forTemplate')
186
		);
187
	}
188
189
	/**
190
	 * Searches the SiteTree for display in the dropdown
191
	 *
192
	 * @return callback
193
	 */
194
	public function siteTreeSearchCallback($sourceObject, $labelField, $search) {
195
		return DataObject::get($sourceObject)->filterAny(array(
196
			'MenuTitle:PartialMatch' => $search,
197
			'Title:PartialMatch' => $search
198
		));
199
	}
200
201
	/**
202
	 * Return a {@link Form} instance allowing a user to
203
	 * add links in the TinyMCE content editor.
204
	 *
205
	 * @return Form
206
	 */
207
	public function LinkForm() {
208
		$siteTree = TreeDropdownField::create('internal', _t('HtmlEditorField.PAGE', "Page"),
209
			'SiteTree', 'ID', 'MenuTitle', true);
210
		// mimic the SiteTree::getMenuTitle(), which is bypassed when the search is performed
211
		$siteTree->setSearchFunction(array($this, 'siteTreeSearchCallback'));
212
213
		$numericLabelTmpl = '<span class="step-label"><span class="flyout">%d</span><span class="arrow"></span>'
214
			. '<strong class="title">%s</strong></span>';
215
		$form = new Form(
216
			$this->controller,
217
			"{$this->name}/LinkForm",
218
			new FieldList(
219
				$headerWrap = new CompositeField(
220
					new LiteralField(
221
						'Heading',
222
						sprintf('<h3 class="htmleditorfield-mediaform-heading insert">%s</h3>',
223
							_t('HtmlEditorField.LINK', 'Insert Link'))
224
					)
225
				),
226
				$contentComposite = new CompositeField(
227
					OptionsetField::create(
228
						'LinkType',
229
						sprintf($numericLabelTmpl, '1', _t('HtmlEditorField.LINKTO', 'Link to')),
230
						array(
231
							'internal' => _t('HtmlEditorField.LINKINTERNAL', 'Page on the site'),
232
							'external' => _t('HtmlEditorField.LINKEXTERNAL', 'Another website'),
233
							'anchor' => _t('HtmlEditorField.LINKANCHOR', 'Anchor on this page'),
234
							'email' => _t('HtmlEditorField.LINKEMAIL', 'Email address'),
235
							'file' => _t('HtmlEditorField.LINKFILE', 'Download a file'),
236
						),
237
						'internal'
238
					),
239
					LiteralField::create('Step2',
240
						'<div class="step2">'
241
						. sprintf($numericLabelTmpl, '2', _t('HtmlEditorField.DETAILS', 'Details')) . '</div>'
242
					),
243
					$siteTree,
244
					TextField::create('external', _t('HtmlEditorField.URL', 'URL'), 'http://'),
245
					EmailField::create('email', _t('HtmlEditorField.EMAIL', 'Email address')),
246
					$fileField = UploadField::create('file', _t('HtmlEditorField.FILE', 'File')),
247
					TextField::create('Anchor', _t('HtmlEditorField.ANCHORVALUE', 'Anchor')),
248
					TextField::create('Subject', _t('HtmlEditorField.SUBJECT', 'Email subject')),
249
					TextField::create('Description', _t('HtmlEditorField.LINKDESCR', 'Link description')),
250
					CheckboxField::create('TargetBlank',
251
						_t('HtmlEditorField.LINKOPENNEWWIN', 'Open link in a new window?')),
252
					HiddenField::create('Locale', null, $this->controller->Locale)
253
				)
254
			),
255
			new FieldList()
256
		);
257
258
		$headerWrap->addExtraClass('CompositeField composite cms-content-header nolabel ');
259
		$contentComposite->addExtraClass('ss-insert-link content');
260
		$fileField->setAllowedMaxFileNumber(1);
261
262
		$form->unsetValidator();
263
		$form->loadDataFrom($this);
264
		$form->addExtraClass('htmleditorfield-form htmleditorfield-linkform cms-mediaform-content');
265
266
		$this->extend('updateLinkForm', $form);
267
268
		return $form;
269
	}
270
271
	/**
272
	 * Get the folder ID to filter files by for the "from cms" tab
273
	 *
274
	 * @return int
275
	 */
276
	protected function getAttachParentID() {
277
		$parentID = $this->controller->getRequest()->requestVar('ParentID');
278
		$this->extend('updateAttachParentID', $parentID);
279
		return $parentID;
280
	}
281
282
	/**
283
	 * Return a {@link Form} instance allowing a user to
284
	 * add images and flash objects to the TinyMCE content editor.
285
	 *
286
	 * @return Form
287
	 */
288
	public function MediaForm() {
289
		// TODO Handle through GridState within field - currently this state set too late to be useful here (during
290
		// request handling)
291
		$parentID = $this->getAttachParentID();
292
293
		$fileFieldConfig = GridFieldConfig::create()->addComponents(
294
			new GridFieldFilterHeader(),
295
			new GridFieldSortableHeader(),
296
			new GridFieldDataColumns(),
297
			new GridFieldPaginator(7),
298
			// TODO Shouldn't allow delete here, its too confusing with a "remove from editor view" action.
299
			// Remove once we can fit the search button in the last actual title column
300
			new GridFieldDeleteAction(),
301
			new GridFieldDetailForm()
302
		);
303
		$fileField = GridField::create('Files', false, null, $fileFieldConfig);
304
		$fileField->setList($this->getFiles($parentID));
305
		$fileField->setAttribute('data-selectable', true);
306
		$fileField->setAttribute('data-multiselect', true);
307
		$columns = $fileField->getConfig()->getComponentByType('GridFieldDataColumns');
308
		$columns->setDisplayFields(array(
309
			'StripThumbnail' => false,
310
			'Title' => _t('File.Title'),
311
			'Created' => singleton('File')->fieldLabel('Created'),
312
		));
313
		$columns->setFieldCasting(array(
314
			'Created' => 'SS_Datetime->Nice'
315
		));
316
317
		$fromCMS = new CompositeField(
318
			$select = TreeDropdownField::create('ParentID', "", 'Folder')
319
				->addExtraClass('noborder')
320
				->setValue($parentID),
321
			$fileField
322
		);
323
324
		$fromCMS->addExtraClass('content ss-uploadfield htmleditorfield-from-cms');
325
		$select->addExtraClass('content-select');
326
327
328
		$URLDescription = _t('HtmlEditorField.URLDESCRIPTION', 'Insert videos and images from the web into your page simply by entering the URL of the file. Make sure you have the rights or permissions before sharing media directly from the web.<br /><br />Please note that files are not added to the file store of the CMS but embeds the file from its original location, if for some reason the file is no longer available in its original location it will no longer be viewable on this page.');
329
		$fromWeb = new CompositeField(
330
			$description = new LiteralField('URLDescription', '<div class="url-description">' . $URLDescription . '</div>'),
331
			$remoteURL = new TextField('RemoteURL', 'http://'),
332
			new LiteralField('addURLImage',
333
				'<button type="button" class="action ui-action-constructive ui-button field font-icon-plus add-url">' .
334
				_t('HtmlEditorField.BUTTONADDURL', 'Add url').'</button>')
335
		);
336
337
		$remoteURL->addExtraClass('remoteurl');
338
		$fromWeb->addExtraClass('content ss-uploadfield htmleditorfield-from-web');
339
340
		Requirements::css(FRAMEWORK_DIR . '/css/AssetUploadField.css');
341
		$computerUploadField = Object::create('UploadField', 'AssetUploadField', '');
342
		$computerUploadField->setConfig('previewMaxWidth', 40);
0 ignored issues
show
Bug introduced by
The method setConfig() does not exist on HtmlEditorField_Toolbar. Did you maybe mean config()?

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...
343
		$computerUploadField->setConfig('previewMaxHeight', 30);
0 ignored issues
show
Bug introduced by
The method setConfig() does not exist on HtmlEditorField_Toolbar. Did you maybe mean config()?

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...
344
		$computerUploadField->addExtraClass('ss-assetuploadfield htmleditorfield-from-computer');
345
		$computerUploadField->removeExtraClass('ss-uploadfield');
346
		$computerUploadField->setTemplate('HtmlEditorField_UploadField');
347
		$computerUploadField->setFolderName(Config::inst()->get('Upload', 'uploads_folder'));
348
349
		$defaultPanel = new CompositeField(
350
			$computerUploadField,
351
			$fromCMS
352
		);
353
354
		$fromWebPanel = new CompositeField(
355
			$fromWeb
356
		);
357
358
		$defaultPanel->addExtraClass('htmleditorfield-default-panel');
359
		$fromWebPanel->addExtraClass('htmleditorfield-web-panel');
360
361
		$allFields = new CompositeField(
362
			$defaultPanel,
363
			$fromWebPanel,
364
			$editComposite = new CompositeField(
365
				new LiteralField('contentEdit', '<div class="content-edit ss-uploadfield-files files"></div>')
366
			)
367
		);
368
369
		$allFields->addExtraClass('ss-insert-media');
370
371
		$headings = new CompositeField(
372
			new LiteralField(
373
				'Heading',
374
				sprintf('<h3 class="htmleditorfield-mediaform-heading insert">%s</h3>',
375
					_t('HtmlEditorField.INSERTMEDIA', 'Insert media from')).
376
				sprintf('<h3 class="htmleditorfield-mediaform-heading update">%s</h3>',
377
					_t('HtmlEditorField.UpdateMEDIA', 'Update media'))
378
			)
379
		);
380
381
		$headings->addExtraClass('cms-content-header');
382
		$editComposite->addExtraClass('ss-assetuploadfield');
383
384
		$fields = new FieldList(
385
			$headings,
386
			$allFields
387
		);
388
389
		$form = new Form(
390
			$this->controller,
391
			"{$this->name}/MediaForm",
392
			$fields,
393
			new FieldList()
394
		);
395
396
397
		$form->unsetValidator();
398
		$form->disableSecurityToken();
399
		$form->loadDataFrom($this);
400
		$form->addExtraClass('htmleditorfield-form htmleditorfield-mediaform cms-dialog-content');
401
402
		// Allow other people to extend the fields being added to the imageform
403
		$this->extend('updateMediaForm', $form);
404
405
		return $form;
406
	}
407
408
	/**
409
	 * View of a single file, either on the filesystem or on the web.
410
	 *
411
	 * @param SS_HTTPRequest $request
412
	 * @return string
413
	 */
414
	public function viewfile($request) {
415
		// TODO Would be cleaner to consistently pass URL for both local and remote files,
416
		// but GridField doesn't allow for this kind of metadata customization at the moment.
417
		$file = null;
418
		if($url = $request->getVar('FileURL')) {
419
			// URLS should be used for remote resources (not local assets)
420
			$url = Director::absoluteURL($url);
421
		} elseif($id = $request->getVar('ID')) {
422
			// Use local dataobject
423
			$file = DataObject::get_by_id('File', $id);
424
			if(!$file) {
425
				throw new InvalidArgumentException("File could not be found");
426
			}
427
			$url = $file->getURL();
428
			if(!$url) {
429
				return $this->httpError(404, 'File not found');
430
			}
431
		} else {
432
			throw new LogicException('Need either "ID" or "FileURL" parameter to identify the file');
433
		}
434
435
		// Instanciate file wrapper and get fields based on its type
436
		// Check if appCategory is an image and exists on the local system, otherwise use oEmbed to refference a
437
		// remote image
438
		$fileCategory = File::get_app_category(File::get_file_extension($url));
439
		switch($fileCategory) {
440
			case 'image':
441
			case 'image/supported':
442
				$fileWrapper = new HtmlEditorField_Image($url, $file);
0 ignored issues
show
Bug introduced by
It seems like $file defined by \DataObject::get_by_id('File', $id) on line 423 can also be of type object<DataObject>; however, HtmlEditorField_Image::__construct() does only seem to accept null|object<File>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
443
				break;
444
			case 'flash':
445
				$fileWrapper = new HtmlEditorField_Flash($url, $file);
0 ignored issues
show
Bug introduced by
It seems like $file defined by \DataObject::get_by_id('File', $id) on line 423 can also be of type object<DataObject>; however, HtmlEditorField_File::__construct() does only seem to accept null|object<File>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
446
				break;
447
			default:
448
				// Only remote files can be linked via o-embed
449
				// {@see HtmlEditorField_Toolbar::getAllowedExtensions())
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
450
				if($file) {
451
					throw new InvalidArgumentException(
452
						"Oembed is only compatible with remote files"
453
					);
454
				}
455
				// Other files should fallback to oembed
456
				$fileWrapper = new HtmlEditorField_Embed($url, $file);
457
				break;
458
		}
459
460
		// Render fields and return
461
		$fields = $this->getFieldsForFile($url, $fileWrapper);
462
		return $fileWrapper->customise(array(
463
			'Fields' => $fields,
464
		))->renderWith($this->templateViewFile);
465
	}
466
467
	/**
468
	 * Find all anchors available on the given page.
469
	 *
470
	 * @return array
471
	 */
472
	public function getanchors() {
473
		$id = (int)$this->getRequest()->getVar('PageID');
474
		$anchors = array();
475
476
		if (($page = Page::get()->byID($id)) && !empty($page)) {
477
			if (!$page->canView()) {
478
				throw new SS_HTTPResponse_Exception(
479
					_t(
480
						'HtmlEditorField.ANCHORSCANNOTACCESSPAGE',
481
						'You are not permitted to access the content of the target page.'
482
					),
483
					403
484
				);
485
			}
486
487
			// Similar to the regex found in HtmlEditorField.js / getAnchors method.
488
			if (preg_match_all(
489
				"/\\s+(name|id)\\s*=\\s*([\"'])([^\\2\\s>]*?)\\2|\\s+(name|id)\\s*=\\s*([^\"']+)[\\s +>]/im",
490
				$page->Content,
491
				$matches
492
			)) {
493
				$anchors = array_values(array_unique(array_filter(
494
					array_merge($matches[3], $matches[5]))
495
				));
496
			}
497
498
		} else {
499
			throw new SS_HTTPResponse_Exception(
500
				_t('HtmlEditorField.ANCHORSPAGENOTFOUND', 'Target page not found.'),
501
				404
502
			);
503
		}
504
505
		return json_encode($anchors);
506
	}
507
508
	/**
509
	 * Similar to {@link File->getCMSFields()}, but only returns fields
510
	 * for manipulating the instance of the file as inserted into the HTML content,
511
	 * not the "master record" in the database - hence there's no form or saving logic.
512
	 *
513
	 * @param string $url Abolute URL to asset
514
	 * @param HtmlEditorField_File $file Asset wrapper
515
	 * @return FieldList
516
	 */
517
	protected function getFieldsForFile($url, HtmlEditorField_File $file) {
518
		$fields = $this->extend('getFieldsForFile', $url, $file);
519
		if(!$fields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
520
			$fields = $file->getFields();
521
			$file->extend('updateFields', $fields);
522
		}
523
		$this->extend('updateFieldsForFile', $fields, $url, $file);
524
		return $fields;
525
	}
526
527
528
	/**
529
	 * Gets files filtered by a given parent with the allowed extensions
530
	 *
531
	 * @param int $parentID
532
	 * @return DataList
533
	 */
534
	protected function getFiles($parentID = null) {
535
		$exts = $this->getAllowedExtensions();
536
		$dotExts = array_map(function($ext) {
537
			return ".{$ext}";
538
		}, $exts);
539
		$files = File::get()->filter('Name:EndsWith', $dotExts);
540
541
		// Limit by folder (if required)
542
		if($parentID) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parentID of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
543
			$files = $files->filter('ParentID', $parentID);
544
		}
545
546
		return $files;
547
	}
548
549
	/**
550
	 * @return Array All extensions which can be handled by the different views.
551
	 */
552
	protected function getAllowedExtensions() {
553
		$exts = array('jpg', 'gif', 'png', 'swf', 'jpeg');
554
		$this->extend('updateAllowedExtensions', $exts);
555
		return $exts;
556
	}
557
558
}
559
560
/**
561
 * Encapsulation of a file which can either be a remote URL
562
 * or a {@link File} on the local filesystem, exhibiting common properties
563
 * such as file name or the URL.
564
 *
565
 * @todo Remove once core has support for remote files
566
 * @package forms
567
 * @subpackage fields-formattedinput
568
 */
569
abstract class HtmlEditorField_File extends ViewableData {
570
571
	/**
572
	 * Default insertion width for Images and Media
573
	 *
574
	 * @config
575
	 * @var int
576
	 */
577
	private static $insert_width = 600;
578
579
	/**
580
	 * Default insert height for images and media
581
	 *
582
	 * @config
583
	 * @var int
584
	 */
585
	private static $insert_height = 360;
586
587
	/**
588
	 * Max width for insert-media preview.
589
	 *
590
	 * Matches CSS rule for .cms-file-info-preview
591
	 *
592
	 * @var int
593
	 */
594
	private static $media_preview_width = 176;
595
596
	/**
597
	 * Max height for insert-media preview.
598
	 *
599
	 * Matches CSS rule for .cms-file-info-preview
600
	 *
601
	 * @var int
602
	 */
603
	private static $media_preview_height = 128;
604
605
	private static $casting = array(
606
		'URL' => 'Varchar',
607
		'Name' => 'Varchar'
608
	);
609
610
	/**
611
	 * Absolute URL to asset
612
	 *
613
	 * @var string
614
	 */
615
	protected $url;
616
617
	/**
618
	 * File dataobject (if available)
619
	 *
620
	 * @var File
621
	 */
622
	protected $file;
623
624
	/**
625
	 * @param string $url
626
	 * @param File $file
627
	 */
628
	public function __construct($url, File $file = null) {
629
		$this->url = $url;
630
		$this->file = $file;
631
		$this->failover = $file;
632
		parent::__construct();
633
	}
634
635
	/**
636
	 * @return FieldList
637
	 */
638
	public function getFields() {
639
		$fields = new FieldList(
640
			CompositeField::create(
641
				CompositeField::create(LiteralField::create("ImageFull", $this->getPreview()))
642
					->setName("FilePreviewImage")
643
					->addExtraClass('cms-file-info-preview'),
644
				CompositeField::create($this->getDetailFields())
645
					->setName("FilePreviewData")
646
					->addExtraClass('cms-file-info-data')
647
			)
648
				->setName("FilePreview")
649
				->addExtraClass('cms-file-info'),
650
			TextField::create('CaptionText', _t('HtmlEditorField.CAPTIONTEXT', 'Caption text')),
651
			DropdownField::create(
652
				'CSSClass',
653
				_t('HtmlEditorField.CSSCLASS', 'Alignment / style'),
654
				array(
655
					'leftAlone' => _t('HtmlEditorField.CSSCLASSLEFTALONE', 'On the left, on its own.'),
656
					'center' => _t('HtmlEditorField.CSSCLASSCENTER', 'Centered, on its own.'),
657
					'left' => _t('HtmlEditorField.CSSCLASSLEFT', 'On the left, with text wrapping around.'),
658
					'right' => _t('HtmlEditorField.CSSCLASSRIGHT', 'On the right, with text wrapping around.')
659
				)
660
			),
661
			FieldGroup::create(_t('HtmlEditorField.IMAGEDIMENSIONS', 'Dimensions'),
662
				TextField::create(
663
					'Width',
664
					_t('HtmlEditorField.IMAGEWIDTHPX', 'Width'),
665
					$this->getInsertWidth()
666
				)->setMaxLength(5),
667
				TextField::create(
668
					'Height',
669
					" x " . _t('HtmlEditorField.IMAGEHEIGHTPX', 'Height'),
670
					$this->getInsertHeight()
671
				)->setMaxLength(5)
672
			)->addExtraClass('dimensions last'),
673
			HiddenField::create('URL', false, $this->getURL()),
674
			HiddenField::create('FileID', false, $this->getFileID())
675
		);
676
		return $fields;
677
	}
678
679
	/**
680
	 * Get list of fields for previewing this records details
681
	 *
682
	 * @return FieldList
683
	 */
684
	protected function getDetailFields() {
685
		$fields = new FieldList(
686
			ReadonlyField::create("FileType", _t('AssetTableField.TYPE','File type'), $this->getFileType()),
687
			ReadonlyField::create(
688
				'ClickableURL', _t('AssetTableField.URL','URL'), $this->getExternalLink()
689
			)->setDontEscape(true)
690
		);
691
		// Get file size
692
		if($this->getSize()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getSize() of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
693
			$fields->insertAfter(
694
				'FileType',
695
				ReadonlyField::create("Size", _t('AssetTableField.SIZE','File size'), $this->getSize())
696
			);
697
		}
698
		// Get modified details of local record
699
		if($this->getFile()) {
700
			$fields->push(new DateField_Disabled(
701
				"Created",
702
				_t('AssetTableField.CREATED', 'First uploaded'),
703
				$this->getFile()->Created
704
			));
705
			$fields->push(new DateField_Disabled(
706
				"LastEdited",
707
				_t('AssetTableField.LASTEDIT','Last changed'),
708
				$this->getFile()->LastEdited
709
			));
710
		}
711
		return $fields;
712
713
	}
714
715
	/**
716
	 * Get file DataObject
717
	 *
718
	 * Might not be set (for remote files)
719
	 *
720
	 * @return File
721
	 */
722
	public function getFile() {
723
		return $this->file;
724
	}
725
726
	/**
727
	 * Get file ID
728
	 *
729
	 * @return int
730
	 */
731
	public function getFileID() {
732
		if($file = $this->getFile()) {
733
			return $file->ID;
734
		}
735
	}
736
737
	/**
738
	 * Get absolute URL
739
	 *
740
	 * @return string
741
	 */
742
	public function getURL() {
743
		return $this->url;
744
	}
745
746
	/**
747
	 * Get basename
748
	 *
749
	 * @return string
750
	 */
751
	public function getName() {
752
		return $this->file
753
			? $this->file->Name
754
			: preg_replace('/\?.*/', '', basename($this->url));
755
	}
756
757
	/**
758
	 * Get descriptive file type
759
	 *
760
	 * @return string
761
	 */
762
	public function getFileType() {
763
		return File::get_file_type($this->getName());
764
	}
765
766
	/**
767
	 * Get file size (if known) as string
768
	 *
769
	 * @return string|false String value, or false if doesn't exist
770
	 */
771
	public function getSize() {
772
		if($this->file) {
773
			return $this->file->getSize();
774
		}
775
		return false;
776
	}
777
778
	/**
779
	 * HTML content for preview
780
	 *
781
	 * @return string HTML
782
	 */
783
	public function getPreview() {
784
		$preview = $this->extend('getPreview');
785
		if($preview) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $preview of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
786
			return $preview;
787
		}
788
789
		// Generate tag from preview
790
		$thumbnailURL = Convert::raw2att(
791
			Controller::join_links($this->getPreviewURL(), "?r=" . rand(1,100000))
792
		);
793
		$fileName = Convert::raw2att($this->Name);
794
		return sprintf(
795
			"<img id='thumbnailImage' class='thumbnail-preview'  src='%s' alt='%s' />\n",
796
			$thumbnailURL,
797
			$fileName
798
		);
799
	}
800
801
	/**
802
	 * HTML Content for external link
803
	 *
804
	 * @return string
805
	 */
806
	public function getExternalLink() {
807
		$title = $this->file
808
			? $this->file->getTitle()
809
			: $this->getName();
810
		return sprintf(
811
			'<a href="%1$s" title="%2$s" target="_blank" rel="external" class="file-url">%1$s</a>',
812
			Convert::raw2att($this->url),
813
			Convert::raw2att($title)
814
		);
815
	}
816
817
	/**
818
	 * Generate thumbnail url
819
	 *
820
	 * @return string
821
	 */
822
	public function getPreviewURL() {
823
		// Get preview from file
824
		if($this->file) {
825
			return $this->getFilePreviewURL();
826
		}
827
828
		// Generate default icon html
829
		return File::get_icon_for_extension($this->getExtension());
830
	}
831
832
	/**
833
	 * Generate thumbnail URL from file dataobject (if available)
834
	 *
835
	 * @return string
836
	 */
837
	protected function getFilePreviewURL() {
838
		// Get preview from file
839
		if($this->file) {
840
			$width = $this->config()->media_preview_width;
0 ignored issues
show
Documentation introduced by
The property media_preview_width does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
841
			$height = $this->config()->media_preview_height;
0 ignored issues
show
Documentation introduced by
The property media_preview_height does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
842
			return $this->file->ThumbnailURL($width, $height);
843
		}
844
	}
845
846
	/**
847
	 * Get file extension
848
	 *
849
	 * @return string
850
	 */
851
	public function getExtension() {
852
		$extension = File::get_file_extension($this->getName());
853
		return strtolower($extension);
854
	}
855
856
	/**
857
	 * Category name
858
	 *
859
	 * @return string
860
	 */
861
	public function appCategory() {
862
		if($this->file) {
863
			return $this->file->appCategory();
864
		} else {
865
			return File::get_app_category($this->getExtension());
866
		}
867
	}
868
869
	/**
870
	 * Get height of this item
871
	 */
872
	public function getHeight() {
873
		if($this->file) {
874
			$height = $this->file->getHeight();
875
			if($height) {
876
				return $height;
877
			}
878
		}
879
		return $this->config()->insert_height;
880
	}
881
882
	/**
883
	 * Get width of this item
884
	 *
885
	 * @return type
886
	 */
887
	public function getWidth() {
888
		if($this->file) {
889
			$width = $this->file->getWidth();
890
			if($width) {
891
				return $width;
892
			}
893
		}
894
		return $this->config()->insert_width;
895
	}
896
897
	/**
898
	 * Provide an initial width for inserted media, restricted based on $embed_width
899
	 *
900
	 * @return int
901
	 */
902
	public function getInsertWidth() {
903
		$width = $this->getWidth();
904
		$maxWidth = $this->config()->insert_width;
0 ignored issues
show
Documentation introduced by
The property insert_width does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
905
		return ($width <= $maxWidth) ? $width : $maxWidth;
906
	}
907
908
	/**
909
	 * Provide an initial height for inserted media, scaled proportionally to the initial width
910
	 *
911
	 * @return int
912
	 */
913
	public function getInsertHeight() {
914
		$width = $this->getWidth();
915
		$height = $this->getHeight();
916
		$maxWidth = $this->config()->insert_width;
0 ignored issues
show
Documentation introduced by
The property insert_width does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
917
		return ($width <= $maxWidth) ? $height : round($height*($maxWidth/$width));
918
	}
919
920
}
921
922
/**
923
 * Encapsulation of an oembed tag, linking to an external media source.
924
 *
925
 * @see Oembed
926
 * @package forms
927
 * @subpackage fields-formattedinput
928
 */
929
class HtmlEditorField_Embed extends HtmlEditorField_File {
930
931
	private static $casting = array(
932
		'Type' => 'Varchar',
933
		'Info' => 'Varchar'
934
	);
935
936
	/**
937
	 * Oembed result
938
	 *
939
	 * @var Oembed_Result
940
	 */
941
	protected $oembed;
942
943
	public function __construct($url, File $file = null) {
944
		parent::__construct($url, $file);
945
		$this->oembed = Oembed::get_oembed_from_url($url);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Oembed::get_oembed_from_url($url) can also be of type false. However, the property $oembed is declared as type object<Oembed_Result>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
946
		if(!$this->oembed) {
947
			$controller = Controller::curr();
948
			$response = $controller->getResponse();
949
			$response->addHeader('X-Status',
950
				rawurlencode(_t(
951
					'HtmlEditorField.URLNOTANOEMBEDRESOURCE',
952
					"The URL '{url}' could not be turned into a media resource.",
953
					"The given URL is not a valid Oembed resource; the embed element couldn't be created.",
954
					array('url' => $url)
955
				)));
956
			$response->setStatusCode(404);
957
958
			throw new SS_HTTPResponse_Exception($response);
959
		}
960
	}
961
962
	/**
963
	 * Get file-edit fields for this filed
964
	 *
965
	 * @return FieldList
966
	 */
967
	public function getFields() {
968
		$fields = parent::getFields();
969
		if($this->Type === 'photo') {
970
			$fields->insertBefore('CaptionText', new TextField(
971
				'AltText',
972
				_t('HtmlEditorField.IMAGEALTTEXT', 'Alternative text (alt) - shown if image can\'t be displayed'),
973
				$this->Title,
974
				80
975
			));
976
			$fields->insertBefore('CaptionText', new TextField(
977
				'Title',
978
				_t('HtmlEditorField.IMAGETITLE', 'Title text (tooltip) - for additional information about the image')
979
			));
980
		}
981
		return $fields;
982
	}
983
984
	/**
985
	 * Get width of this oembed
986
	 *
987
	 * @return int
988
	 */
989
	public function getWidth() {
990
		return $this->oembed->Width ?: 100;
991
	}
992
993
	/**
994
	 * Get height of this oembed
995
	 *
996
	 * @return int
997
	 */
998
	public function getHeight() {
999
		return $this->oembed->Height ?: 100;
1000
	}
1001
1002
	public function getPreviewURL() {
1003
		// Use thumbnail url
1004
		if(!empty($this->oembed->thumbnail_url)) {
1005
			return $this->oembed->thumbnail_url;
1006
		}
1007
1008
		// Use direct image type
1009
		if($this->getType() == 'photo' && !empty($this->Oembed->url)) {
0 ignored issues
show
Bug introduced by
The property Oembed does not seem to exist. Did you mean oembed?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1010
			return $this->Oembed->url;
0 ignored issues
show
Bug introduced by
The property Oembed does not seem to exist. Did you mean oembed?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1011
		}
1012
1013
		// Default media
1014
		return FRAMEWORK_DIR . '/images/default_media.png';
1015
	}
1016
1017
	public function getName() {
1018
		if(isset($this->oembed->title)) {
1019
			return $this->oembed->title;
1020
		} else {
1021
			return parent::getName();
1022
		}
1023
	}
1024
1025
	/**
1026
	 * Get OEmbed type
1027
	 *
1028
	 * @return string
1029
	 */
1030
	public function getType() {
1031
		return $this->oembed->type;
0 ignored issues
show
Documentation introduced by
The property $type is declared protected in Oembed_Result. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1032
	}
1033
1034
	public function getFileType() {
1035
		return $this->getType()
1036
			?: parent::getFileType();
1037
	}
1038
1039
	/**
1040
	 * @return Oembed_Result
1041
	 */
1042
	public function getOembed() {
1043
		return $this->oembed;
1044
	}
1045
1046
	public function appCategory() {
1047
		return 'embed';
1048
	}
1049
1050
	/**
1051
	 * Info for this oembed
1052
	 *
1053
	 * @return string
1054
	 */
1055
	public function getInfo() {
1056
		return $this->oembed->info;
1057
	}
1058
}
1059
1060
/**
1061
 * Encapsulation of an image tag, linking to an image either internal or external to the site.
1062
 *
1063
 * @package forms
1064
 * @subpackage fields-formattedinput
1065
 */
1066
class HtmlEditorField_Image extends HtmlEditorField_File {
1067
1068
	/**
1069
	 * @var int
1070
	 */
1071
	protected $width;
1072
1073
	/**
1074
	 * @var int
1075
	 */
1076
	protected $height;
1077
1078
	/**
1079
	 * File size details
1080
	 *
1081
	 * @var string
1082
	 */
1083
	protected $size;
1084
1085
	public function __construct($url, File $file = null) {
1086
		parent::__construct($url, $file);
1087
1088
		if($file) {
1089
			return;
1090
		}
1091
1092
		// Get size of remote file
1093
		$size = @filesize($url);
1094
		if($size) {
1095
			$this->size = $size;
1096
		}
1097
1098
		// Get dimensions of remote file
1099
		$info = @getimagesize($url);
1100
		if($info) {
1101
			$this->width = $info[0];
1102
			$this->height = $info[1];
1103
		}
1104
	}
1105
1106
	public function getFields() {
1107
		$fields = parent::getFields();
1108
1109
		// Alt text
1110
		$fields->insertBefore(
1111
			'CaptionText',
1112
			TextField::create(
1113
				'AltText',
1114
				_t('HtmlEditorField.IMAGEALT', 'Alternative text (alt)'),
1115
				$this->Title,
1116
				80
1117
			)->setDescription(
1118
				_t('HtmlEditorField.IMAGEALTTEXTDESC', 'Shown to screen readers or if image can\'t be displayed')
1119
			)
1120
		);
1121
1122
		// Tooltip
1123
		$fields->insertAfter(
1124
			'AltText',
1125
			TextField::create(
1126
				'Title',
1127
				_t('HtmlEditorField.IMAGETITLETEXT', 'Title text (tooltip)')
1128
			)->setDescription(
1129
				_t('HtmlEditorField.IMAGETITLETEXTDESC', 'For additional information about the image')
1130
			)
1131
		);
1132
1133
		return $fields;
1134
	}
1135
1136
	protected function getDetailFields() {
1137
		$fields = parent::getDetailFields();
1138
		$width = $this->getOriginalWidth();
1139
		$height = $this->getOriginalHeight();
1140
1141
		// Show dimensions of original
1142
		if($width && $height) {
1143
			$fields->insertAfter(
1144
				'ClickableURL',
1145
				ReadonlyField::create(
1146
					"OriginalWidth",
1147
					_t('AssetTableField.WIDTH','Width'),
1148
					$width
1149
				)
1150
			);
1151
			$fields->insertAfter(
1152
				'OriginalWidth',
1153
				ReadonlyField::create(
1154
					"OriginalHeight",
1155
					_t('AssetTableField.HEIGHT','Height'),
1156
					$height
1157
				)
1158
			);
1159
		}
1160
		return $fields;
1161
	}
1162
1163
	/**
1164
	 * Get width of original, if known
1165
	 *
1166
	 * @return int
1167
	 */
1168
	public function getOriginalWidth() {
1169
		if($this->width) {
1170
			return $this->width;
1171
		}
1172
		if($this->file) {
1173
			$width = $this->file->getWidth();
1174
			if($width) {
1175
				return $width;
1176
			}
1177
		}
1178
	}
1179
1180
	/**
1181
	 * Get height of original, if known
1182
	 *
1183
	 * @return int
1184
	 */
1185
	public function getOriginalHeight() {
1186
		if($this->height) {
1187
			return $this->height;
1188
		}
1189
1190
		if($this->file) {
1191
			$height = $this->file->getHeight();
1192
			if($height) {
1193
				return $height;
1194
			}
1195
		}
1196
	}
1197
1198
	public function getWidth() {
1199
		if($this->width) {
1200
			return $this->width;
1201
		}
1202
		return parent::getWidth();
1203
	}
1204
1205
	public function getHeight() {
1206
		if($this->height) {
1207
			return $this->height;
1208
		}
1209
		return parent::getHeight();
1210
	}
1211
1212
	public function getSize() {
1213
		if($this->size) {
1214
			return File::format_size($this->size);
1215
		}
1216
		parent::getSize();
1217
	}
1218
1219
	/**
1220
	 * Provide an initial width for inserted image, restricted based on $embed_width
1221
	 *
1222
	 * @return int
1223
	 */
1224
	public function getInsertWidth() {
1225
		$width = $this->getWidth();
1226
		$maxWidth = $this->config()->insert_width;
0 ignored issues
show
Documentation introduced by
The property insert_width does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1227
		return $width <= $maxWidth
1228
			? $width
1229
			: $maxWidth;
1230
	}
1231
1232
	/**
1233
	 * Provide an initial height for inserted image, scaled proportionally to the initial width
1234
	 *
1235
	 * @return int
1236
	 */
1237
	public function getInsertHeight() {
1238
		$width = $this->getWidth();
1239
		$height = $this->getHeight();
1240
		$maxWidth = $this->config()->insert_width;
0 ignored issues
show
Documentation introduced by
The property insert_width does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1241
		return ($width <= $maxWidth) ? $height : round($height*($maxWidth/$width));
1242
	}
1243
1244
	public function getPreviewURL() {
1245
		// Get preview from file
1246
		if($this->file) {
1247
			return $this->getFilePreviewURL();
1248
		}
1249
1250
		// Embed image directly
1251
		return $this->url;
1252
	}
1253
}
1254
1255
/**
1256
 * Generate flash file embed
1257
 */
1258
class HtmlEditorField_Flash extends HtmlEditorField_File {
1259
1260
	public function getFields() {
1261
		$fields = parent::getFields();
1262
		$fields->removeByName('CaptionText', true);
1263
		return $fields;
1264
	}
1265
}
1266