Completed
Push — master ( 5cace7...20fac0 )
by Sam
32:27 queued 20:32
created

HTMLEditorField_Embed::getEmbed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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