Completed
Push — master ( 3b4314...83c2af )
by Sam
12:26
created

HTMLEditorField_Readonly::Field()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 8
nop 1
dl 0
loc 6
rs 9.2
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A HTMLEditorField_Readonly::Type() 0 3 1
1
<?php
2
use Embed\Adapters\AdapterInterface;
3
use Embed\Embed;
4
use SilverStripe\ORM\DataObjectInterface;
5
use SilverStripe\ORM\DataObject;
6
use SilverStripe\ORM\FieldType\DBField;
7
8
9
/**
10
 * A TinyMCE-powered WYSIWYG HTML editor field with image and link insertion and tracking capabilities. Editor fields
11
 * are created from <textarea> tags, which are then converted with JavaScript.
12
 *
13
 * @package forms
14
 * @subpackage fields-formattedinput
15
 */
16
class HTMLEditorField extends TextareaField {
17
18
	private static $casting = [
19
		'Value' => 'HTMLText',
20
	];
21
22
	/**
23
	 * Use TinyMCE's GZIP compressor
24
	 *
25
	 * @config
26
	 * @var bool
27
	 */
28
	private static $use_gzip = true;
29
30
	/**
31
	 * Should we check the valid_elements (& extended_valid_elements) rules from HTMLEditorConfig server side?
32
	 *
33
	 * @config
34
	 * @var bool
35
	 */
36
	private static $sanitise_server_side = false;
37
38
	/**
39
	 * Number of rows
40
	 *
41
	 * @config
42
	 * @var int
43
	 */
44
	private static $default_rows = 30;
45
46
	/**
47
	 * ID or instance of editorconfig
48
	 *
49
	 * @var string|HTMLEditorConfig
50
	 */
51
	protected $editorConfig = null;
52
53
	/**
54
	 * Gets the HTMLEditorConfig instance
55
	 *
56
	 * @return HTMLEditorConfig
57
	 */
58
	public function getEditorConfig() {
59
		// Instance override
60
		if($this->editorConfig instanceof HTMLEditorConfig) {
61
			return $this->editorConfig;
62
		}
63
64
		// Get named / active config
65
		return HTMLEditorConfig::get($this->editorConfig);
66
	}
67
68
	/**
69
	 * Assign a new configuration instance or identifier
70
	 *
71
	 * @param string|HTMLEditorConfig $config
72
	 * @return $this
73
	 */
74
	public function setEditorConfig($config) {
75
		$this->editorConfig = $config;
76
		return $this;
77
	}
78
79
	/**
80
	 * Creates a new HTMLEditorField.
81
	 * @see TextareaField::__construct()
82
	 *
83
	 * @param string $name The internal field name, passed to forms.
84
	 * @param string $title The human-readable field label.
85
	 * @param mixed $value The value of the field.
86
	 * @param string $config HTMLEditorConfig identifier to be used. Default to the active one.
87
	 */
88
	public function __construct($name, $title = null, $value = '', $config = null) {
89
		parent::__construct($name, $title, $value);
90
91
		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...
92
			$this->setEditorConfig($config);
93
		}
94
95
		$this->setRows($this->config()->default_rows);
96
	}
97
98
	public function getAttributes() {
99
		return array_merge(
100
			parent::getAttributes(),
101
			$this->getEditorConfig()->getAttributes()
102
		);
103
	}
104
105
	public function saveInto(DataObjectInterface $record) {
106
		if($record->hasField($this->name) && $record->escapeTypeForField($this->name) != 'xml') {
107
			throw new Exception (
108
				'HTMLEditorField->saveInto(): This field should save into a HTMLText or HTMLVarchar field.'
109
			);
110
		}
111
112
		// Sanitise if requested
113
		$htmlValue = Injector::inst()->create('HTMLValue', $this->Value());
114
		if($this->config()->sanitise_server_side) {
115
			$santiser = Injector::inst()->create('HTMLEditorSanitiser', HTMLEditorConfig::get_active());
116
			$santiser->sanitise($htmlValue);
117
		}
118
119
		// optionally manipulate the HTML after a TinyMCE edit and prior to a save
120
		$this->extend('processHTML', $htmlValue);
121
122
		// Store into record
123
		$record->{$this->name} = $htmlValue->getContent();
124
	}
125
126
	public function setValue($value) {
127
		// Regenerate links prior to preview, so that the editor can see them.
128
		$value = Image::regenerate_html_links($value);
129
		return parent::setValue($value);
130
	}
131
132
	/**
133
	 * @return HTMLEditorField_Readonly
134
	 */
135
	public function performReadonlyTransformation() {
136
		return $this->castedCopy('HTMLEditorField_Readonly');
137
	}
138
139
	public function performDisabledTransformation() {
140
		return $this->performReadonlyTransformation();
141
	}
142
143
	public function Field($properties = array()) {
144
		// Include requirements
145
		$this->getEditorConfig()->init();
146
		return parent::Field($properties);
147
	}
148
}
149
150
/**
151
 * Readonly version of an {@link HTMLEditorField}.
152
 * @package forms
153
 * @subpackage fields-formattedinput
154
 */
155
class HTMLEditorField_Readonly extends HTMLReadonlyField {
156
	private static $casting = [
157
		'Value' => 'HTMLText',
158
	];
159
160
	public function Type() {
161
		return 'htmleditorfield readonly';
162
	}
163
}
164
165
/**
166
 * Toolbar shared by all instances of {@link HTMLEditorField}, to avoid too much markup duplication.
167
 *  Needs to be inserted manually into the template in order to function - see {@link LeftAndMain->EditorToolbar()}.
168
 *
169
 * @package forms
170
 * @subpackage fields-formattedinput
171
 */
172
class HTMLEditorField_Toolbar extends RequestHandler {
173
174
	private static $allowed_actions = array(
175
		'LinkForm',
176
		'MediaForm',
177
		'viewfile',
178
		'getanchors'
179
	);
180
181
	/**
182
	 * @var string
183
	 */
184
	protected $templateViewFile = 'HTMLEditorField_viewfile';
185
186
	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...
187
188
	public function __construct($controller, $name) {
189
		parent::__construct();
190
191
		$this->controller = $controller;
192
		$this->name = $name;
193
	}
194
195
	public function forTemplate() {
196
		return sprintf(
197
			'<div id="cms-editor-dialogs" data-url-linkform="%s" data-url-mediaform="%s"></div>',
198
			Controller::join_links($this->controller->Link(), $this->name, 'LinkForm', 'forTemplate'),
199
			Controller::join_links($this->controller->Link(), $this->name, 'MediaForm', 'forTemplate')
200
		);
201
	}
202
203
	/**
204
	 * Searches the SiteTree for display in the dropdown
205
	 *
206
	 * @return callback
207
	 */
208
	public function siteTreeSearchCallback($sourceObject, $labelField, $search) {
209
		return DataObject::get($sourceObject)->filterAny(array(
210
			'MenuTitle:PartialMatch' => $search,
211
			'Title:PartialMatch' => $search
212
		));
213
	}
214
215
	/**
216
	 * Return a {@link Form} instance allowing a user to
217
	 * add links in the TinyMCE content editor.
218
	 *
219
	 * @return Form
220
	 */
221
	public function LinkForm() {
222
		$siteTree = TreeDropdownField::create('internal', _t('HTMLEditorField.PAGE', "Page"),
223
			'SiteTree', 'ID', 'MenuTitle', true);
224
		// mimic the SiteTree::getMenuTitle(), which is bypassed when the search is performed
225
		$siteTree->setSearchFunction(array($this, 'siteTreeSearchCallback'));
226
227
		$numericLabelTmpl = '<span class="step-label"><span class="flyout">%d</span><span class="arrow"></span>'
228
			. '<strong class="title">%s</strong></span>';
229
		$form = new Form(
230
			$this->controller,
231
			"{$this->name}/LinkForm",
232
			new FieldList(
233
				$headerWrap = new CompositeField(
234
					new LiteralField(
235
						'Heading',
236
						sprintf('<h3 class="htmleditorfield-mediaform-heading insert">%s</h3>',
237
							_t('HTMLEditorField.LINK', 'Insert Link'))
238
					)
239
				),
240
				$contentComposite = new CompositeField(
241
					OptionsetField::create(
242
						'LinkType',
243
						DBField::create_field(
244
							'HTMLFragment',
245
							sprintf($numericLabelTmpl, '1', _t('HTMLEditorField.LINKTO', 'Link to'))
246
						),
247
						array(
248
							'internal' => _t('HTMLEditorField.LINKINTERNAL', 'Page on the site'),
249
							'external' => _t('HTMLEditorField.LINKEXTERNAL', 'Another website'),
250
							'anchor' => _t('HTMLEditorField.LINKANCHOR', 'Anchor on this page'),
251
							'email' => _t('HTMLEditorField.LINKEMAIL', 'Email address'),
252
							'file' => _t('HTMLEditorField.LINKFILE', 'Download a file'),
253
						),
254
						'internal'
255
					),
256
					LiteralField::create('Step2',
257
						'<div class="step2">'
258
						. sprintf($numericLabelTmpl, '2', _t('HTMLEditorField.DETAILS', 'Details')) . '</div>'
259
					),
260
					$siteTree,
261
					TextField::create('external', _t('HTMLEditorField.URL', 'URL'), 'http://'),
262
					EmailField::create('email', _t('HTMLEditorField.EMAIL', 'Email address')),
263
					$fileField = UploadField::create('file', _t('HTMLEditorField.FILE', 'File')),
264
					TextField::create('Anchor', _t('HTMLEditorField.ANCHORVALUE', 'Anchor')),
265
					TextField::create('Subject', _t('HTMLEditorField.SUBJECT', 'Email subject')),
266
					TextField::create('Description', _t('HTMLEditorField.LINKDESCR', 'Link description')),
267
					CheckboxField::create('TargetBlank',
268
						_t('HTMLEditorField.LINKOPENNEWWIN', 'Open link in a new window?')),
269
					HiddenField::create('Locale', null, $this->controller->Locale)
270
				)
271
			),
272
			new FieldList()
273
		);
274
275
		$headerWrap->addExtraClass('CompositeField composite cms-content-header nolabel ');
276
		$contentComposite->addExtraClass('ss-insert-link content');
277
		$fileField->setAllowedMaxFileNumber(1);
278
279
		$form->unsetValidator();
280
		$form->loadDataFrom($this);
281
		$form->addExtraClass('htmleditorfield-form htmleditorfield-linkform cms-mediaform-content');
282
283
		$this->extend('updateLinkForm', $form);
284
285
		return $form;
286
	}
287
288
	/**
289
	 * Get the folder ID to filter files by for the "from cms" tab
290
	 *
291
	 * @return int
292
	 */
293
	protected function getAttachParentID() {
294
		$parentID = $this->controller->getRequest()->requestVar('ParentID');
295
		$this->extend('updateAttachParentID', $parentID);
296
		return $parentID;
297
	}
298
299
	/**
300
	 * Return a {@link Form} instance allowing a user to
301
	 * add images and flash objects to the TinyMCE content editor.
302
	 *
303
	 * @return Form
304
	 */
305
	public function MediaForm() {
306
		// TODO Handle through GridState within field - currently this state set too late to be useful here (during
307
		// request handling)
308
		$parentID = $this->getAttachParentID();
309
310
		$fileFieldConfig = GridFieldConfig::create()->addComponents(
311
			new GridFieldFilterHeader(),
312
			new GridFieldSortableHeader(),
313
			new GridFieldDataColumns(),
314
			new GridFieldPaginator(7),
315
			// TODO Shouldn't allow delete here, its too confusing with a "remove from editor view" action.
316
			// Remove once we can fit the search button in the last actual title column
317
			new GridFieldDeleteAction(),
318
			new GridFieldDetailForm()
319
		);
320
		$fileField = GridField::create('Files', false, null, $fileFieldConfig);
321
		$fileField->setList($this->getFiles($parentID));
322
		$fileField->setAttribute('data-selectable', true);
323
		$fileField->setAttribute('data-multiselect', true);
324
		$columns = $fileField->getConfig()->getComponentByType('GridFieldDataColumns');
325
		$columns->setDisplayFields(array(
326
			'StripThumbnail' => false,
327
			'Title' => _t('File.Title'),
328
			'Created' => singleton('File')->fieldLabel('Created'),
329
		));
330
		$columns->setFieldCasting(array(
331
			'Created' => 'DBDatetime->Nice'
332
		));
333
334
		$fromCMS = new CompositeField(
335
			$select = TreeDropdownField::create('ParentID', "", 'Folder')
336
				->addExtraClass('noborder')
337
				->setValue($parentID),
338
			$fileField
339
		);
340
341
		$fromCMS->addExtraClass('content ss-uploadfield htmleditorfield-from-cms');
342
		$select->addExtraClass('content-select');
343
344
345
		$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.');
346
		$fromWeb = new CompositeField(
347
			$description = new LiteralField('URLDescription', '<div class="url-description">' . $URLDescription . '</div>'),
348
			$remoteURL = new TextField('RemoteURL', 'http://'),
349
			new LiteralField('addURLImage',
350
				'<button type="button" class="action ui-action-constructive ui-button field font-icon-plus add-url">' .
351
				_t('HTMLEditorField.BUTTONADDURL', 'Add url').'</button>')
352
		);
353
354
		$remoteURL->addExtraClass('remoteurl');
355
		$fromWeb->addExtraClass('content ss-uploadfield htmleditorfield-from-web');
356
357
		Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/AssetUploadField.css');
358
		$computerUploadField = Object::create('UploadField', 'AssetUploadField', '');
359
		$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...
360
		$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...
361
		$computerUploadField->addExtraClass('ss-assetuploadfield htmleditorfield-from-computer');
362
		$computerUploadField->removeExtraClass('ss-uploadfield');
363
		$computerUploadField->setTemplate('HTMLEditorField_UploadField');
364
		$computerUploadField->setFolderName(Config::inst()->get('Upload', 'uploads_folder'));
365
366
		$defaultPanel = new CompositeField(
367
			$computerUploadField,
368
			$fromCMS
369
		);
370
371
		$fromWebPanel = new CompositeField(
372
			$fromWeb
373
		);
374
375
		$defaultPanel->addExtraClass('htmleditorfield-default-panel');
376
		$fromWebPanel->addExtraClass('htmleditorfield-web-panel');
377
378
		$allFields = new CompositeField(
379
			$defaultPanel,
380
			$fromWebPanel,
381
			$editComposite = new CompositeField(
382
				new LiteralField('contentEdit', '<div class="content-edit ss-uploadfield-files files"></div>')
383
			)
384
		);
385
386
		$allFields->addExtraClass('ss-insert-media');
387
388
		$headings = new CompositeField(
389
			new LiteralField(
390
				'Heading',
391
				sprintf('<h3 class="htmleditorfield-mediaform-heading insert">%s</h3>',
392
					_t('HTMLEditorField.INSERTMEDIA', 'Insert media from')).
393
				sprintf('<h3 class="htmleditorfield-mediaform-heading update">%s</h3>',
394
					_t('HTMLEditorField.UpdateMEDIA', 'Update media'))
395
			)
396
		);
397
398
		$headings->addExtraClass('cms-content-header');
399
		$editComposite->addExtraClass('ss-assetuploadfield');
400
401
		$fields = new FieldList(
402
			$headings,
403
			$allFields
404
		);
405
406
		$form = new Form(
407
			$this->controller,
408
			"{$this->name}/MediaForm",
409
			$fields,
410
			new FieldList()
411
		);
412
413
414
		$form->unsetValidator();
415
		$form->disableSecurityToken();
416
		$form->loadDataFrom($this);
417
		$form->addExtraClass('htmleditorfield-form htmleditorfield-mediaform cms-dialog-content');
418
419
		// Allow other people to extend the fields being added to the imageform
420
		$this->extend('updateMediaForm', $form);
421
422
		return $form;
423
	}
424
425
	/**
426
	 * List of allowed schemes (no wildcard, all lower case) or empty to allow all schemes
427
	 *
428
	 * @config
429
	 * @var array
430
	 */
431
	private static $fileurl_scheme_whitelist = array('http', 'https');
432
433
	/**
434
	 * List of allowed domains (no wildcard, all lower case) or empty to allow all domains
435
	 *
436
	 * @config
437
	 * @var array
438
	 */
439
	private static $fileurl_domain_whitelist = array();
440
441
	/**
442
	 * Find local File dataobject given ID
443
	 *
444
	 * @param int $id
445
	 * @return array
446
	 */
447
	protected function viewfile_getLocalFileByID($id) {
448
		/** @var File $file */
449
		$file = DataObject::get_by_id('File', $id);
450
		if ($file && $file->canView()) {
451
			return array($file, $file->getURL());
452
		}
453
		return [null, null];
454
	}
455
456
	/**
457
	 * Get remote File given url
458
	 *
459
	 * @param string $fileUrl Absolute URL
460
	 * @return array
461
	 * @throws SS_HTTPResponse_Exception
462
	 */
463
	protected function viewfile_getRemoteFileByURL($fileUrl) {
464
		if(!Director::is_absolute_url($fileUrl)) {
465
			throw $this->getErrorFor(_t(
466
				"HTMLEditorField_Toolbar.ERROR_ABSOLUTE",
467
				"Only absolute urls can be embedded"
468
			));
469
		}
470
		$scheme = strtolower(parse_url($fileUrl, PHP_URL_SCHEME));
471
		$allowed_schemes = self::config()->fileurl_scheme_whitelist;
0 ignored issues
show
Documentation introduced by
The property fileurl_scheme_whitelist 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...
472
		if (!$scheme || ($allowed_schemes && !in_array($scheme, $allowed_schemes))) {
473
			throw $this->getErrorFor(_t(
474
				"HTMLEditorField_Toolbar.ERROR_SCHEME",
475
				"This file scheme is not included in the whitelist"
476
			));
477
		}
478
		$domain = strtolower(parse_url($fileUrl, PHP_URL_HOST));
479
		$allowed_domains = self::config()->fileurl_domain_whitelist;
0 ignored issues
show
Documentation introduced by
The property fileurl_domain_whitelist 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...
480
		if (!$domain || ($allowed_domains && !in_array($domain, $allowed_domains))) {
481
			throw $this->getErrorFor(_t(
482
				"HTMLEditorField_Toolbar.ERROR_HOSTNAME",
483
				"This file hostname is not included in the whitelist"
484
			));
485
		}
486
		return [null, $fileUrl];
487
	}
488
489
	/**
490
	 * Prepare error for the front end
491
	 *
492
	 * @param string $message
493
	 * @param int $code
494
	 * @return SS_HTTPResponse_Exception
495
	 */
496
	protected function getErrorFor($message, $code = 400) {
497
		$exception = new SS_HTTPResponse_Exception($message, $code);
498
		$exception->getResponse()->addHeader('X-Status', $message);
499
		return $exception;
500
	}
501
502
	/**
503
	 * View of a single file, either on the filesystem or on the web.
504
	 *
505
	 * @throws SS_HTTPResponse_Exception
506
	 * @param SS_HTTPRequest $request
507
	 * @return string
508
	 */
509
	public function viewfile($request) {
510
		$file = null;
511
		$url = null;
512
		// Get file and url by request method
513
		if($fileUrl = $request->getVar('FileURL')) {
514
			// Get remote url
515
			list($file, $url) = $this->viewfile_getRemoteFileByURL($fileUrl);
516
		} elseif($id = $request->getVar('ID')) {
517
			// Or we could have been passed an ID directly
518
			list($file, $url) = $this->viewfile_getLocalFileByID($id);
519
		} else {
520
			// Or we could have been passed nothing, in which case panic
521
			throw $this->getErrorFor(_t(
522
				"HTMLEditorField_Toolbar.ERROR_ID",
523
				'Need either "ID" or "FileURL" parameter to identify the file'
524
			));
525
		}
526
527
		// Validate file exists
528
		if(!$url) {
529
			throw $this->getErrorFor(_t(
530
				"HTMLEditorField_Toolbar.ERROR_NOTFOUND",
531
				'Unable to find file to view'
532
			));
533
		}
534
535
		// Instantiate file wrapper and get fields based on its type
536
		// Check if appCategory is an image and exists on the local system, otherwise use Embed to reference a
537
		// remote image
538
		$fileCategory = $this->getFileCategory($url, $file);
539
		switch($fileCategory) {
540
			case 'image':
541
			case 'image/supported':
542
				$fileWrapper = new HTMLEditorField_Image($url, $file);
543
				break;
544
			case 'flash':
545
				$fileWrapper = new HTMLEditorField_Flash($url, $file);
546
				break;
547
			default:
548
				// Only remote files can be linked via o-embed
549
				// {@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...
550
				if($file) {
551
					throw $this->getErrorFor(_t(
552
						"HTMLEditorField_Toolbar.ERROR_OEMBED_REMOTE",
553
						"Embed is only compatible with remote files"
554
					));
555
				}
556
557
				// Other files should fallback to embed
558
				$fileWrapper = new HTMLEditorField_Embed($url, $file);
559
				break;
560
		}
561
562
		// Render fields and return
563
		$fields = $this->getFieldsForFile($url, $fileWrapper);
564
		return $fileWrapper->customise(array(
565
			'Fields' => $fields,
566
		))->renderWith($this->templateViewFile);
567
	}
568
569
	/**
570
	 * Guess file category from either a file or url
571
	 *
572
	 * @param string $url
573
	 * @param File $file
574
	 * @return string
575
	 */
576
	protected function getFileCategory($url, $file) {
577
		if($file) {
578
			return $file->appCategory();
579
		}
580
		if($url) {
581
			return File::get_app_category(File::get_file_extension($url));
582
		}
583
		return null;
584
	}
585
586
	/**
587
	 * Find all anchors available on the given page.
588
	 *
589
	 * @return array
590
	 * @throws SS_HTTPResponse_Exception
591
	 */
592
	public function getanchors() {
593
		$id = (int)$this->getRequest()->getVar('PageID');
594
		$anchors = array();
595
596
		if (($page = Page::get()->byID($id)) && !empty($page)) {
597
			if (!$page->canView()) {
598
				throw new SS_HTTPResponse_Exception(
599
					_t(
600
						'HTMLEditorField.ANCHORSCANNOTACCESSPAGE',
601
						'You are not permitted to access the content of the target page.'
602
					),
603
					403
604
				);
605
			}
606
607
			// Parse the shortcodes so [img id=x] doesn't end up as anchor x
608
			$htmlValue = $page->obj('Content')->forTemplate();
609
610
			// Similar to the regex found in HTMLEditorField.js / getAnchors method.
611
			if (preg_match_all(
612
				"/\\s+(name|id)\\s*=\\s*([\"'])([^\\2\\s>]*?)\\2|\\s+(name|id)\\s*=\\s*([^\"']+)[\\s +>]/im",
613
				$htmlValue,
614
				$matches
615
			)) {
616
				$anchors = array_values(array_unique(array_filter(
617
					array_merge($matches[3], $matches[5]))
618
				));
619
			}
620
621
		} else {
622
			throw new SS_HTTPResponse_Exception(
623
				_t('HTMLEditorField.ANCHORSPAGENOTFOUND', 'Target page not found.'),
624
				404
625
			);
626
		}
627
628
		return json_encode($anchors);
629
	}
630
631
	/**
632
	 * Similar to {@link File->getCMSFields()}, but only returns fields
633
	 * for manipulating the instance of the file as inserted into the HTML content,
634
	 * not the "master record" in the database - hence there's no form or saving logic.
635
	 *
636
	 * @param string $url Abolute URL to asset
637
	 * @param HTMLEditorField_File $file Asset wrapper
638
	 * @return FieldList
639
	 */
640
	protected function getFieldsForFile($url, HTMLEditorField_File $file) {
641
		$fields = $this->extend('getFieldsForFile', $url, $file);
642
		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...
643
			$fields = $file->getFields();
644
			$file->extend('updateFields', $fields);
645
		}
646
		$this->extend('updateFieldsForFile', $fields, $url, $file);
647
		return $fields;
648
	}
649
650
651
	/**
652
	 * Gets files filtered by a given parent with the allowed extensions
653
	 *
654
	 * @param int $parentID
655
	 * @return DataList
656
	 */
657
	protected function getFiles($parentID = null) {
658
		$exts = $this->getAllowedExtensions();
659
		$dotExts = array_map(function($ext) {
660
			return ".{$ext}";
661
		}, $exts);
662
		$files = File::get()->filter('Name:EndsWith', $dotExts);
663
664
		// Limit by folder (if required)
665
		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...
666
			$files = $files->filter('ParentID', $parentID);
667
		}
668
669
		return $files;
670
	}
671
672
	/**
673
	 * @return Array All extensions which can be handled by the different views.
674
	 */
675
	protected function getAllowedExtensions() {
676
		$exts = array('jpg', 'gif', 'png', 'swf', 'jpeg');
677
		$this->extend('updateAllowedExtensions', $exts);
678
		return $exts;
679
	}
680
681
}
682
683
/**
684
 * Encapsulation of a file which can either be a remote URL
685
 * or a {@link File} on the local filesystem, exhibiting common properties
686
 * such as file name or the URL.
687
 *
688
 * @todo Remove once core has support for remote files
689
 * @package forms
690
 * @subpackage fields-formattedinput
691
 */
692
abstract class HTMLEditorField_File extends ViewableData {
693
694
	/**
695
	 * Default insertion width for Images and Media
696
	 *
697
	 * @config
698
	 * @var int
699
	 */
700
	private static $insert_width = 600;
701
702
	/**
703
	 * Default insert height for images and media
704
	 *
705
	 * @config
706
	 * @var int
707
	 */
708
	private static $insert_height = 360;
709
710
	/**
711
	 * Max width for insert-media preview.
712
	 *
713
	 * Matches CSS rule for .cms-file-info-preview
714
	 *
715
	 * @var int
716
	 */
717
	private static $media_preview_width = 176;
718
719
	/**
720
	 * Max height for insert-media preview.
721
	 *
722
	 * Matches CSS rule for .cms-file-info-preview
723
	 *
724
	 * @var int
725
	 */
726
	private static $media_preview_height = 128;
727
728
	private static $casting = array(
729
		'URL' => 'Varchar',
730
		'Name' => 'Varchar'
731
	);
732
733
	/**
734
	 * Absolute URL to asset
735
	 *
736
	 * @var string
737
	 */
738
	protected $url;
739
740
	/**
741
	 * File dataobject (if available)
742
	 *
743
	 * @var File
744
	 */
745
	protected $file;
746
747
	/**
748
	 * @param string $url
749
	 * @param File $file
750
	 */
751
	public function __construct($url, File $file = null) {
752
		$this->url = $url;
753
		$this->file = $file;
754
		$this->failover = $file;
755
		parent::__construct();
756
	}
757
758
	/**
759
	 * @return FieldList
760
	 */
761
	public function getFields() {
762
		$fields = new FieldList(
763
			CompositeField::create(
764
				CompositeField::create(LiteralField::create("ImageFull", $this->getPreview()))
765
					->setName("FilePreviewImage")
766
					->addExtraClass('cms-file-info-preview'),
767
				CompositeField::create($this->getDetailFields())
768
					->setName("FilePreviewData")
769
					->addExtraClass('cms-file-info-data')
770
			)
771
				->setName("FilePreview")
772
				->addExtraClass('cms-file-info'),
773
			TextField::create('CaptionText', _t('HTMLEditorField.CAPTIONTEXT', 'Caption text')),
774
			DropdownField::create(
775
				'CSSClass',
776
				_t('HTMLEditorField.CSSCLASS', 'Alignment / style'),
777
				array(
778
					'leftAlone' => _t('HTMLEditorField.CSSCLASSLEFTALONE', 'On the left, on its own.'),
779
					'center' => _t('HTMLEditorField.CSSCLASSCENTER', 'Centered, on its own.'),
780
					'left' => _t('HTMLEditorField.CSSCLASSLEFT', 'On the left, with text wrapping around.'),
781
					'right' => _t('HTMLEditorField.CSSCLASSRIGHT', 'On the right, with text wrapping around.')
782
				)
783
			),
784
			FieldGroup::create(_t('HTMLEditorField.IMAGEDIMENSIONS', 'Dimensions'),
785
				TextField::create(
786
					'Width',
787
					_t('HTMLEditorField.IMAGEWIDTHPX', 'Width'),
788
					$this->getInsertWidth()
789
				)->setMaxLength(5),
790
				TextField::create(
791
					'Height',
792
					" x " . _t('HTMLEditorField.IMAGEHEIGHTPX', 'Height'),
793
					$this->getInsertHeight()
794
				)->setMaxLength(5)
795
			)->addExtraClass('dimensions last'),
796
			HiddenField::create('URL', false, $this->getURL()),
797
			HiddenField::create('FileID', false, $this->getFileID())
798
		);
799
		return $fields;
800
	}
801
802
	/**
803
	 * Get list of fields for previewing this records details
804
	 *
805
	 * @return FieldList
806
	 */
807
	protected function getDetailFields() {
808
		$fields = new FieldList(
809
			ReadonlyField::create("FileType", _t('AssetTableField.TYPE','File type'), $this->getFileType()),
810
			HTMLReadonlyField::create(
811
				'ClickableURL', _t('AssetTableField.URL','URL'), $this->getExternalLink()
812
			)
813
		);
814
		// Get file size
815
		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...
816
			$fields->insertAfter(
817
				'FileType',
818
				ReadonlyField::create("Size", _t('AssetTableField.SIZE','File size'), $this->getSize())
819
			);
820
		}
821
		// Get modified details of local record
822
		if($this->getFile()) {
823
			$fields->push(new DateField_Disabled(
824
				"Created",
825
				_t('AssetTableField.CREATED', 'First uploaded'),
826
				$this->getFile()->Created
827
			));
828
			$fields->push(new DateField_Disabled(
829
				"LastEdited",
830
				_t('AssetTableField.LASTEDIT','Last changed'),
831
				$this->getFile()->LastEdited
832
			));
833
		}
834
		return $fields;
835
836
	}
837
838
	/**
839
	 * Get file DataObject
840
	 *
841
	 * Might not be set (for remote files)
842
	 *
843
	 * @return File
844
	 */
845
	public function getFile() {
846
		return $this->file;
847
	}
848
849
	/**
850
	 * Get file ID
851
	 *
852
	 * @return int
853
	 */
854
	public function getFileID() {
855
		if($file = $this->getFile()) {
856
			return $file->ID;
857
		}
858
	}
859
860
	/**
861
	 * Get absolute URL
862
	 *
863
	 * @return string
864
	 */
865
	public function getURL() {
866
		return $this->url;
867
	}
868
869
	/**
870
	 * Get basename
871
	 *
872
	 * @return string
873
	 */
874
	public function getName() {
875
		return $this->file
876
			? $this->file->Name
877
			: preg_replace('/\?.*/', '', basename($this->url));
878
	}
879
880
	/**
881
	 * Get descriptive file type
882
	 *
883
	 * @return string
884
	 */
885
	public function getFileType() {
886
		return File::get_file_type($this->getName());
887
	}
888
889
	/**
890
	 * Get file size (if known) as string
891
	 *
892
	 * @return string|false String value, or false if doesn't exist
893
	 */
894
	public function getSize() {
895
		if($this->file) {
896
			return $this->file->getSize();
897
		}
898
		return false;
899
	}
900
901
	/**
902
	 * HTML content for preview
903
	 *
904
	 * @return string HTML
905
	 */
906
	public function getPreview() {
907
		$preview = $this->extend('getPreview');
908
		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...
909
			return $preview;
910
		}
911
912
		// Generate tag from preview
913
		$thumbnailURL = Convert::raw2att(
914
			Controller::join_links($this->getPreviewURL(), "?r=" . rand(1,100000))
915
		);
916
		$fileName = Convert::raw2att($this->Name);
917
		return sprintf(
918
			"<img id='thumbnailImage' class='thumbnail-preview'  src='%s' alt='%s' />\n",
919
			$thumbnailURL,
920
			$fileName
921
		);
922
	}
923
924
	/**
925
	 * HTML Content for external link
926
	 *
927
	 * @return string
928
	 */
929
	public function getExternalLink() {
930
		$title = $this->file
931
			? $this->file->getTitle()
932
			: $this->getName();
933
		return sprintf(
934
			'<a href="%1$s" title="%2$s" target="_blank" rel="external" class="file-url">%1$s</a>',
935
			Convert::raw2att($this->url),
936
			Convert::raw2att($title)
937
		);
938
	}
939
940
	/**
941
	 * Generate thumbnail url
942
	 *
943
	 * @return string
944
	 */
945
	public function getPreviewURL() {
946
		// Get preview from file
947
		if($this->file) {
948
			return $this->getFilePreviewURL();
949
		}
950
951
		// Generate default icon html
952
		return File::get_icon_for_extension($this->getExtension());
953
	}
954
955
	/**
956
	 * Generate thumbnail URL from file dataobject (if available)
957
	 *
958
	 * @return string
959
	 */
960
	protected function getFilePreviewURL() {
961
		// Get preview from file
962
		if($this->file) {
963
			$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...
964
			$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...
965
			return $this->file->ThumbnailURL($width, $height);
966
		}
967
	}
968
969
	/**
970
	 * Get file extension
971
	 *
972
	 * @return string
973
	 */
974
	public function getExtension() {
975
		$extension = File::get_file_extension($this->getName());
976
		return strtolower($extension);
977
	}
978
979
	/**
980
	 * Category name
981
	 *
982
	 * @return string
983
	 */
984
	public function appCategory() {
985
		if($this->file) {
986
			return $this->file->appCategory();
987
		} else {
988
			return File::get_app_category($this->getExtension());
989
		}
990
	}
991
992
	/**
993
	 * Get height of this item
994
	 */
995
	public function getHeight() {
996
		if($this->file) {
997
			$height = $this->file->getHeight();
998
			if($height) {
999
				return $height;
1000
			}
1001
		}
1002
		return $this->config()->insert_height;
1003
	}
1004
1005
	/**
1006
	 * Get width of this item
1007
	 *
1008
	 * @return type
1009
	 */
1010
	public function getWidth() {
1011
		if($this->file) {
1012
			$width = $this->file->getWidth();
1013
			if($width) {
1014
				return $width;
1015
			}
1016
		}
1017
		return $this->config()->insert_width;
1018
	}
1019
1020
	/**
1021
	 * Provide an initial width for inserted media, restricted based on $embed_width
1022
	 *
1023
	 * @return int
1024
	 */
1025
	public function getInsertWidth() {
1026
		$width = $this->getWidth();
1027
		$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...
1028
		return ($width <= $maxWidth) ? $width : $maxWidth;
1029
	}
1030
1031
	/**
1032
	 * Provide an initial height for inserted media, scaled proportionally to the initial width
1033
	 *
1034
	 * @return int
1035
	 */
1036
	public function getInsertHeight() {
1037
		$width = $this->getWidth();
1038
		$height = $this->getHeight();
1039
		$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...
1040
		return ($width <= $maxWidth) ? $height : round($height*($maxWidth/$width));
1041
	}
1042
1043
}
1044
1045
/**
1046
 * Encapsulation of an embed tag, linking to an external media source.
1047
 *
1048
 * @see Embed
1049
 * @package forms
1050
 * @subpackage fields-formattedinput
1051
 */
1052
class HTMLEditorField_Embed extends HTMLEditorField_File {
1053
1054
	private static $casting = array(
1055
		'Type' => 'Varchar',
1056
		'Info' => 'Varchar'
1057
	);
1058
1059
	/**
1060
	 * Embed result
1061
	 *
1062
	 * @var Embed
1063
	 */
1064
	protected $embed;
1065
1066
	public function __construct($url, File $file = null) {
1067
		parent::__construct($url, $file);
1068
		$this->embed = Embed::create($url);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Embed\Embed::create($url) of type object<Embed\Adapters\AdapterInterface> is incompatible with the declared type object<Embed\Embed> of property $embed.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1069
		if(!$this->embed) {
1070
			$controller = Controller::curr();
1071
			$response = $controller->getResponse();
1072
			$response->addHeader('X-Status',
1073
				rawurlencode(_t(
1074
					'HTMLEditorField.URLNOTANOEMBEDRESOURCE',
1075
					"The URL '{url}' could not be turned into a media resource.",
1076
					"The given URL is not a valid Oembed resource; the embed element couldn't be created.",
1077
					array('url' => $url)
1078
				)));
1079
			$response->setStatusCode(404);
1080
1081
			throw new SS_HTTPResponse_Exception($response);
1082
		}
1083
	}
1084
1085
	/**
1086
	 * Get file-edit fields for this filed
1087
	 *
1088
	 * @return FieldList
1089
	 */
1090
	public function getFields() {
1091
		$fields = parent::getFields();
1092
		if($this->Type === 'photo') {
1093
			$fields->insertBefore('CaptionText', new TextField(
1094
				'AltText',
1095
				_t('HTMLEditorField.IMAGEALTTEXT', 'Alternative text (alt) - shown if image can\'t be displayed'),
1096
				$this->Title,
1097
				80
1098
			));
1099
			$fields->insertBefore('CaptionText', new TextField(
1100
				'Title',
1101
				_t('HTMLEditorField.IMAGETITLE', 'Title text (tooltip) - for additional information about the image')
1102
			));
1103
		}
1104
		return $fields;
1105
	}
1106
1107
	/**
1108
	 * Get width of this Embed
1109
	 *
1110
	 * @return int
1111
	 */
1112
	public function getWidth() {
1113
		return $this->embed->width ?: 100;
0 ignored issues
show
Bug introduced by
The property width does not seem to exist in Embed\Embed.

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...
1114
	}
1115
1116
	/**
1117
	 * Get height of this Embed
1118
	 *
1119
	 * @return int
1120
	 */
1121
	public function getHeight() {
1122
		return $this->embed->height ?: 100;
0 ignored issues
show
Bug introduced by
The property height does not seem to exist in Embed\Embed.

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...
1123
	}
1124
1125
	public function getPreviewURL() {
1126
		// Use thumbnail url
1127
		if($this->embed->image) {
1128
			return $this->embed->image;
0 ignored issues
show
Bug introduced by
The property image does not seem to exist in Embed\Embed.

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...
1129
		}
1130
1131
		// Use direct image type
1132
		if($this->getType() == 'photo' && !empty($this->embed->url)) {
1133
			return $this->embed->url;
0 ignored issues
show
Bug introduced by
The property url does not seem to exist in Embed\Embed.

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...
1134
		}
1135
1136
		// Default media
1137
		return FRAMEWORK_DIR . '/images/default_media.png';
1138
	}
1139
1140
	public function getName() {
1141
		if($this->embed->title) {
1142
			return $this->embed->title;
0 ignored issues
show
Bug introduced by
The property title does not seem to exist in Embed\Embed.

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...
1143
		} else {
1144
			return parent::getName();
1145
		}
1146
	}
1147
1148
	/**
1149
	 * Get Embed type
1150
	 *
1151
	 * @return string
1152
	 */
1153
	public function getType() {
1154
		return $this->embed->type;
0 ignored issues
show
Bug introduced by
The property type does not seem to exist in Embed\Embed.

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...
1155
	}
1156
1157
	/**
1158
	 * Get filetype
1159
	 *
1160
	 * @return string
1161
	 */
1162
	public function getFileType() {
1163
		return $this->getType()
1164
			?: parent::getFileType();
1165
	}
1166
1167
	/**
1168
	 * @return AdapterInterface
1169
	 */
1170
	public function getEmbed() {
1171
		return $this->embed;
1172
	}
1173
1174
	public function appCategory() {
1175
		return 'embed';
1176
	}
1177
1178
	/**
1179
	 * Info for this Embed
1180
	 *
1181
	 * @return string
1182
	 */
1183
	public function getInfo() {
1184
		return $this->embed->info;
0 ignored issues
show
Bug introduced by
The property info does not seem to exist in Embed\Embed.

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...
1185
	}
1186
}
1187
1188
/**
1189
 * Encapsulation of an image tag, linking to an image either internal or external to the site.
1190
 *
1191
 * @package forms
1192
 * @subpackage fields-formattedinput
1193
 */
1194
class HTMLEditorField_Image extends HTMLEditorField_File {
1195
1196
	/**
1197
	 * @var int
1198
	 */
1199
	protected $width;
1200
1201
	/**
1202
	 * @var int
1203
	 */
1204
	protected $height;
1205
1206
	/**
1207
	 * File size details
1208
	 *
1209
	 * @var string
1210
	 */
1211
	protected $size;
1212
1213
	public function __construct($url, File $file = null) {
1214
		parent::__construct($url, $file);
1215
1216
		if($file) {
1217
			return;
1218
		}
1219
1220
		// Get size of remote file
1221
		$size = @filesize($url);
1222
		if($size) {
1223
			$this->size = $size;
1224
		}
1225
1226
		// Get dimensions of remote file
1227
		$info = @getimagesize($url);
1228
		if($info) {
1229
			$this->width = $info[0];
1230
			$this->height = $info[1];
1231
		}
1232
	}
1233
1234
	public function getFields() {
1235
		$fields = parent::getFields();
1236
1237
		// Alt text
1238
		$fields->insertBefore(
1239
			'CaptionText',
1240
			TextField::create(
1241
				'AltText',
1242
				_t('HTMLEditorField.IMAGEALT', 'Alternative text (alt)'),
1243
				$this->Title,
1244
				80
1245
			)->setDescription(
1246
				_t('HTMLEditorField.IMAGEALTTEXTDESC', 'Shown to screen readers or if image can\'t be displayed')
1247
			)
1248
		);
1249
1250
		// Tooltip
1251
		$fields->insertAfter(
1252
			'AltText',
1253
			TextField::create(
1254
				'Title',
1255
				_t('HTMLEditorField.IMAGETITLETEXT', 'Title text (tooltip)')
1256
			)->setDescription(
1257
				_t('HTMLEditorField.IMAGETITLETEXTDESC', 'For additional information about the image')
1258
			)
1259
		);
1260
1261
		return $fields;
1262
	}
1263
1264
	protected function getDetailFields() {
1265
		$fields = parent::getDetailFields();
1266
		$width = $this->getOriginalWidth();
1267
		$height = $this->getOriginalHeight();
1268
1269
		// Show dimensions of original
1270
		if($width && $height) {
1271
			$fields->insertAfter(
1272
				'ClickableURL',
1273
				ReadonlyField::create(
1274
					"OriginalWidth",
1275
					_t('AssetTableField.WIDTH','Width'),
1276
					$width
1277
				)
1278
			);
1279
			$fields->insertAfter(
1280
				'OriginalWidth',
1281
				ReadonlyField::create(
1282
					"OriginalHeight",
1283
					_t('AssetTableField.HEIGHT','Height'),
1284
					$height
1285
				)
1286
			);
1287
		}
1288
		return $fields;
1289
	}
1290
1291
	/**
1292
	 * Get width of original, if known
1293
	 *
1294
	 * @return int
1295
	 */
1296
	public function getOriginalWidth() {
1297
		if($this->width) {
1298
			return $this->width;
1299
		}
1300
		if($this->file) {
1301
			$width = $this->file->getWidth();
1302
			if($width) {
1303
				return $width;
1304
			}
1305
		}
1306
	}
1307
1308
	/**
1309
	 * Get height of original, if known
1310
	 *
1311
	 * @return int
1312
	 */
1313
	public function getOriginalHeight() {
1314
		if($this->height) {
1315
			return $this->height;
1316
		}
1317
1318
		if($this->file) {
1319
			$height = $this->file->getHeight();
1320
			if($height) {
1321
				return $height;
1322
			}
1323
		}
1324
	}
1325
1326
	public function getWidth() {
1327
		if($this->width) {
1328
			return $this->width;
1329
		}
1330
		return parent::getWidth();
1331
	}
1332
1333
	public function getHeight() {
1334
		if($this->height) {
1335
			return $this->height;
1336
		}
1337
		return parent::getHeight();
1338
	}
1339
1340
	public function getSize() {
1341
		if($this->size) {
1342
			return File::format_size($this->size);
1343
		}
1344
		parent::getSize();
1345
	}
1346
1347
	/**
1348
	 * Provide an initial width for inserted image, restricted based on $embed_width
1349
	 *
1350
	 * @return int
1351
	 */
1352
	public function getInsertWidth() {
1353
		$width = $this->getWidth();
1354
		$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...
1355
		return $width <= $maxWidth
1356
			? $width
1357
			: $maxWidth;
1358
	}
1359
1360
	/**
1361
	 * Provide an initial height for inserted image, scaled proportionally to the initial width
1362
	 *
1363
	 * @return int
1364
	 */
1365
	public function getInsertHeight() {
1366
		$width = $this->getWidth();
1367
		$height = $this->getHeight();
1368
		$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...
1369
		return ($width <= $maxWidth) ? $height : round($height*($maxWidth/$width));
1370
	}
1371
1372
	public function getPreviewURL() {
1373
		// Get preview from file
1374
		if($this->file) {
1375
			return $this->getFilePreviewURL();
1376
		}
1377
1378
		// Embed image directly
1379
		return $this->url;
1380
	}
1381
}
1382
1383
/**
1384
 * Generate flash file embed
1385
 */
1386
class HTMLEditorField_Flash extends HTMLEditorField_File {
1387
1388
	public function getFields() {
1389
		$fields = parent::getFields();
1390
		$fields->removeByName('CaptionText', true);
1391
		return $fields;
1392
	}
1393
}
1394