-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathOembedField.php
More file actions
332 lines (290 loc) · 11.2 KB
/
Copy pathOembedField.php
File metadata and controls
332 lines (290 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
<?php
/**
* oEmbed plugin for Craft CMS 3.x
*
* A simple plugin to extract media information from websites, like youtube videos, twitter statuses or blog articles.
*
* @link https://github.com/wrav
* @copyright Copyright (c) 2017 reganlawton
*/
namespace wrav\oembed\fields;
use Craft;
use craft\base\ElementInterface;
use craft\base\Field;
use craft\elements\MatrixBlock as MatrixBlockElement;
use craft\gql\arguments\elements\MatrixBlock as MatrixBlockArguments;
use craft\gql\resolvers\elements\MatrixBlock as MatrixBlockResolver;
use craft\gql\types\generators\MatrixBlockType as MatrixBlockTypeGenerator;
use craft\gql\types\QueryArgument;
use craft\helpers\ArrayHelper;
use craft\helpers\Gql as GqlHelper;
use craft\helpers\Json;
use craft\helpers\UrlHelper;
use GraphQL\Type\Definition\Type;
use wrav\oembed\gql\OembedFieldTypeGenerator;
use wrav\oembed\Oembed;
use yii\db\Schema;
use wrav\oembed\models\OembedModel;
/**
* OembedField Field
*
* @author reganlawton
* @package Oembed
* @since 1.0.0
*/
class OembedField extends Field
{
// Public Properties
// =========================================================================
/**
* @var string
*/
public $url = '';
/**
* @var mixed|null
*/
protected $value;
// Static Methods
// =========================================================================
/**
* @return string The display name of this class.
*/
public static function displayName(): string
{
return Craft::t('oembed', 'oEmbed');
}
// Public Methods
// =========================================================================
/**
* @return array
*/
public function rules(): array
{
$rules = parent::rules();
return $rules;
}
/**
* @return string The column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
* to convert the give column type to the physical one. For example, `string` will be converted
* as `varchar(255)` and `string(100)` becomes `varchar(100)`. `not null` will automatically be
* appended as well.
* @see \yii\db\QueryBuilder::getColumnType()
*/
public function getContentColumnType(): string
{
return Schema::TYPE_TEXT;
}
/**
* @inheritdoc
* @since 3.3.0
*/
public function getContentGqlType(): \GraphQL\Type\Definition\Type|array
{
$typeArray = OembedFieldTypeGenerator::generateTypes($this);
$handle = $this->handle;
return [
'name' => $handle,
'description' => "Oembed field",
'type' => array_shift($typeArray),
// The `args` array specifies the GraphQL arguments that the `embed` function accepts so we can apply options for the oEmbed service
'args' => [
'options' => [
'name' => 'options',
'type' => Type::string(),
'description' => 'This should be a JSON-encoded string.',
],
'cacheProps' => [
'name' => 'cacheProps',
'type' => Type::string(),
'description' => 'This should be a JSON-encoded string.',
],
],
// Use the `resolve` method to convert the field value into a format that can be used by the oEmbed services embed method
'resolve' => function($source, $arguments) use ($handle) {
try {
$url = $source[$handle]['url'];
} catch (\Exception $e) {
throw new \Exception('The oEmbed field is not set.');
}
if (isset($arguments['options'])) {
$arguments['options'] = Json::decode($arguments['options']);
}
if (isset($arguments['cacheProps'])) {
$arguments['cacheProps'] = Json::decode($arguments['cacheProps']);
}
$embed = Oembed::getInstance()->oembedService->embed($url, $arguments['options'] ?? [], $arguments['cacheProps'] ?? []);
return $embed;
}
];
}
/**
* Detect whether a string contains HTML tags (e.g. an embed code pasted instead of a URL).
*/
private function isHtmlEmbedCode(string $value): bool
{
return trim($value) !== strip_tags(trim($value));
}
/**
* Normalize a URL by adding https:// scheme if missing.
*
* @param string $url The URL to normalize
* @return string The normalized URL
*/
private function normalizeUrl(string $url): string
{
$url = trim($url);
if (empty($url)) {
return $url;
}
// If URL already has a scheme, return as-is
if (preg_match('#^https?://#i', $url)) {
return $url;
}
// If URL starts with //, add https:
if (str_starts_with($url, '//')) {
return 'https:' . $url;
}
// Add https:// to URLs without a scheme
return 'https://' . $url;
}
/**
* @param mixed $value The raw field value
* @param ElementInterface|null $element The element the field is associated with, if there is one
*
* @return mixed The prepared field value
*/
public function normalizeValue(mixed $value, ?craft\base\ElementInterface $element = null): mixed
{
// If null, don't proceed
if ($value === null) {
if ($this->required) {
return null;
}
return new OembedModel(null);
}
// If an instance of `OembedModel` and URL is set, return it
if ($value instanceof OembedModel && $value->url) {
$normalizedUrl = $this->normalizeUrl($value->url);
if ($this->isHtmlEmbedCode($normalizedUrl)) {
return new OembedModel(null);
}
if (UrlHelper::isFullUrl($normalizedUrl)) {
return $this->value = new OembedModel($normalizedUrl);
} else {
// If we get here, something's gone wrong
return new OembedModel(null);
}
}
// If JSON object string, decode it and use that as the value
$value = Json::decodeIfJson($value); // Returns an array
// If array with `url` attribute, that's our url so update the value
// Run `getValue` to avoid https://github.com/wrav/oembed/issues/74
while(is_array($value)) {
$value = ArrayHelper::getValue($value, 'url');
}
// Normalize URL by adding scheme if missing (fixes #177)
if (is_string($value)) {
$value = $this->normalizeUrl($value);
}
// Reject HTML embed codes — catches raw HTML and any already-prefixed bad data (fixes #181)
if (is_string($value) && $this->isHtmlEmbedCode($value)) {
return new OembedModel(null);
}
// If URL string, return an instance of `OembedModel`
if (is_string($value) && UrlHelper::isFullUrl($value)) {
return $this->value = new OembedModel($value);
}
// If we get here, something's gone wrong
return new OembedModel(null);
}
/**
* Modifies an element query.
*
* @param ElementInterface $query The element query
* @param mixed $value The value that was set on this field's corresponding [[ElementCriteriaModel]]
* param, if any.
* @return null|false `false` in the event that the method is sure that no elements are going to be found.
*/
public function serializeValue(mixed $value, ?craft\base\ElementInterface $element = null): mixed
{
return parent::serializeValue($value, $element);
}
/**
* @return string|null
*/
public function getSettingsHtml(): ?string
{
return null;
}
/**
* @inheritdoc
*/
public function getElementValidationRules(): array
{
$rules = parent::getElementValidationRules();
$handle = $this->handle;
$rules[] = [
$handle,
function($attribute, $params) use ($handle) {
$request = Craft::$app->getRequest();
if (!$request instanceof \craft\web\Request || !$request->getIsPost()) {
return;
}
$fields = $request->getBodyParam('fields', []);
$rawValue = $fields[$handle] ?? null;
if (is_string($rawValue) && $rawValue !== strip_tags($rawValue)) {
$this->addError($attribute, Craft::t('oembed', 'Please enter a URL, not an HTML embed code.'));
}
},
];
return $rules;
}
/**
* @param ElementInterface|null $element The element the field is associated with, if there is one
* @param mixed $value The field's value. This will either be the [[normalizeValue() normalized
* value]], raw POST data (i.e. if there was a validation error), or null
* @return string The input HTML.
*/
public function getInputHtml($value, ElementInterface $element = null): string
{
$settings = Oembed::getInstance()->getSettings();
$hidden = $settings['previewHidden'];
$previewIcon = $hidden ? 'expand' : 'collapse';
// Safely extract the URL string and detect HTML embed codes (#181)
$urlString = '';
$isHtmlInput = false;
if ($value instanceof OembedModel) {
$urlString = is_string($value->url) ? $value->url : '';
} elseif (is_string($value)) {
if ($this->isHtmlEmbedCode($value)) {
$isHtmlInput = true;
} else {
$urlString = $value;
}
}
$input = '<input name="'.$this->handle.'" class="text nicetext fullwidth oembed-field" value="'.htmlspecialchars($urlString, ENT_QUOTES, 'UTF-8').'" />';
$preview = '<div class="oembed-header">
<p class="fullwidth"><strong>Preview</strong> <span class="right" data-icon-after="'.$previewIcon.'"></span></p>
</div>';
if ($isHtmlInput) {
$preview .= '<div class="oembed-preview"><p class="error">'.Craft::t('oembed', 'Please enter a URL, not an HTML embed code.').'</p></div>';
} elseif ($value) {
try {
if ($embed = new OembedModel($value)) {
$embed = $embed->embed();
$hiddenClass = $hidden ? 'hidden' : '';
if (!empty($embed)) {
$preview .= '<div class="oembed-preview '.$hiddenClass.'">'.$embed->code.'</div>';
} else {
$preview .= '<div class="oembed-preview '.$hiddenClass.'"><p class="error">Please check your URL.</p></div>';
}
}
} catch (\Exception $exception) {
$preview .= '<div class="oembed-preview"><p class="error">Please check your URL.</p></div>';
}
} else {
$preview .= '<div class="oembed-preview"></div>';
}
return $input.$preview;
}
}