Completed
Push — master ( a7f5ef...db6af3 )
by Hamish
22:46 queued 12:25
created

viewfile_getRemoteFileByURL()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1057
		if(!$this->oembed) {
1058
			$controller = Controller::curr();
1059
			$response = $controller->getResponse();
1060
			$response->addHeader('X-Status',
1061
				rawurlencode(_t(
1062
					'HtmlEditorField.URLNOTANOEMBEDRESOURCE',
1063
					"The URL '{url}' could not be turned into a media resource.",
1064
					"The given URL is not a valid Oembed resource; the embed element couldn't be created.",
1065
					array('url' => $url)
1066
				)));
1067
			$response->setStatusCode(404);
1068
1069
			throw new SS_HTTPResponse_Exception($response);
1070
		}
1071
	}
1072
1073
	/**
1074
	 * Get file-edit fields for this filed
1075
	 *
1076
	 * @return FieldList
1077
	 */
1078
	public function getFields() {
1079
		$fields = parent::getFields();
1080
		if($this->Type === 'photo') {
1081
			$fields->insertBefore('CaptionText', new TextField(
1082
				'AltText',
1083
				_t('HtmlEditorField.IMAGEALTTEXT', 'Alternative text (alt) - shown if image can\'t be displayed'),
1084
				$this->Title,
1085
				80
1086
			));
1087
			$fields->insertBefore('CaptionText', new TextField(
1088
				'Title',
1089
				_t('HtmlEditorField.IMAGETITLE', 'Title text (tooltip) - for additional information about the image')
1090
			));
1091
		}
1092
		return $fields;
1093
	}
1094
1095
	/**
1096
	 * Get width of this oembed
1097
	 *
1098
	 * @return int
1099
	 */
1100
	public function getWidth() {
1101
		return $this->oembed->Width ?: 100;
1102
	}
1103
1104
	/**
1105
	 * Get height of this oembed
1106
	 *
1107
	 * @return int
1108
	 */
1109
	public function getHeight() {
1110
		return $this->oembed->Height ?: 100;
1111
	}
1112
1113
	public function getPreviewURL() {
1114
		// Use thumbnail url
1115
		if(!empty($this->oembed->thumbnail_url)) {
1116
			return $this->oembed->thumbnail_url;
1117
		}
1118
1119
		// Use direct image type
1120
		if($this->getType() == 'photo' && !empty($this->Oembed->url)) {
0 ignored issues
show
Bug introduced by
The property Oembed does not seem to exist. Did you mean oembed?

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

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

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

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

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

Loading history...
1122
		}
1123
1124
		// Default media
1125
		return FRAMEWORK_DIR . '/images/default_media.png';
1126
	}
1127
1128
	public function getName() {
1129
		if(isset($this->oembed->title)) {
1130
			return $this->oembed->title;
1131
		} else {
1132
			return parent::getName();
1133
		}
1134
	}
1135
1136
	/**
1137
	 * Get OEmbed type
1138
	 *
1139
	 * @return string
1140
	 */
1141
	public function getType() {
1142
		return $this->oembed->type;
0 ignored issues
show
Documentation introduced by
The property $type is declared protected in Oembed_Result. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

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