flipbox /
meta
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * @copyright Copyright (c) Flipbox Digital Limited |
||
| 5 | * @license https://flipboxfactory.com/software/meta/license |
||
| 6 | * @link https://www.flipboxfactory.com/software/meta/ |
||
| 7 | */ |
||
| 8 | |||
| 9 | namespace flipbox\meta\fields; |
||
| 10 | |||
| 11 | use Craft; |
||
| 12 | use craft\base\EagerLoadingFieldInterface; |
||
| 13 | use craft\base\Element; |
||
| 14 | use craft\base\ElementInterface; |
||
| 15 | use craft\base\Field; |
||
| 16 | use craft\base\FieldInterface; |
||
| 17 | use craft\behaviors\FieldLayoutBehavior; |
||
| 18 | use craft\db\Query; |
||
| 19 | use craft\elements\db\ElementQuery; |
||
| 20 | use craft\elements\db\ElementQueryInterface; |
||
| 21 | use craft\fields\Matrix; |
||
| 22 | use craft\fields\MissingField; |
||
| 23 | use craft\fields\PlainText; |
||
| 24 | use craft\helpers\Json; |
||
| 25 | use craft\helpers\StringHelper; |
||
| 26 | use craft\models\FieldLayout; |
||
| 27 | use craft\validators\ArrayValidator; |
||
| 28 | use flipbox\meta\elements\db\Meta as MetaQuery; |
||
| 29 | use flipbox\meta\elements\Meta as MetaElement; |
||
| 30 | use flipbox\meta\helpers\Field as FieldHelper; |
||
| 31 | use flipbox\meta\Meta as MetaPlugin; |
||
| 32 | use flipbox\meta\records\Meta as MetaRecord; |
||
| 33 | use flipbox\meta\web\assets\input\Input as MetaInputAsset; |
||
| 34 | use flipbox\meta\web\assets\settings\Settings as MetaSettingsAsset; |
||
| 35 | |||
| 36 | /** |
||
| 37 | * @author Flipbox Factory <[email protected]> |
||
| 38 | * @since 1.0.0 |
||
| 39 | * |
||
| 40 | * @method setFieldLayout(FieldLayout $fieldLayout) |
||
| 41 | * @method FieldLayout getFieldLayout() |
||
| 42 | */ |
||
| 43 | class Meta extends Field implements EagerLoadingFieldInterface |
||
| 44 | { |
||
| 45 | |||
| 46 | const DEFAULT_TEMPLATE = FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'layout'; |
||
| 47 | |||
| 48 | /** |
||
| 49 | * @var int|null |
||
| 50 | */ |
||
| 51 | public $max; |
||
| 52 | |||
| 53 | /** |
||
| 54 | * @var int|null |
||
| 55 | */ |
||
| 56 | public $min; |
||
| 57 | |||
| 58 | /** |
||
| 59 | * @var string |
||
| 60 | */ |
||
| 61 | public $selectionLabel = "Add meta"; |
||
| 62 | |||
| 63 | /** |
||
| 64 | * @var int |
||
| 65 | */ |
||
| 66 | public $localize = false; |
||
| 67 | |||
| 68 | /** |
||
| 69 | * @var int|null |
||
| 70 | */ |
||
| 71 | public $fieldLayoutId; |
||
| 72 | |||
| 73 | /** |
||
| 74 | * @var bool |
||
| 75 | */ |
||
| 76 | public $templateOverride = false; |
||
| 77 | |||
| 78 | /** |
||
| 79 | * @var string |
||
| 80 | */ |
||
| 81 | protected $template = self::DEFAULT_TEMPLATE; |
||
| 82 | |||
| 83 | /** |
||
| 84 | * @var bool |
||
| 85 | */ |
||
| 86 | public $hasFieldErrors = false; |
||
| 87 | |||
| 88 | /** |
||
| 89 | * @inheritdoc |
||
| 90 | */ |
||
| 91 | public static function displayName(): string |
||
| 92 | { |
||
| 93 | return Craft::t('meta', 'Meta'); |
||
| 94 | } |
||
| 95 | |||
| 96 | /** |
||
| 97 | * @inheritdoc |
||
| 98 | */ |
||
| 99 | public static function supportedTranslationMethods(): array |
||
| 100 | { |
||
| 101 | // Don't ever automatically propagate values to other sites. |
||
| 102 | return [ |
||
| 103 | self::TRANSLATION_METHOD_SITE, |
||
| 104 | ]; |
||
| 105 | } |
||
| 106 | |||
| 107 | /** |
||
| 108 | * @inheritdoc |
||
| 109 | */ |
||
| 110 | public static function hasContentColumn(): bool |
||
| 111 | { |
||
| 112 | return false; |
||
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * @inheritdoc |
||
| 117 | */ |
||
| 118 | public function settingsAttributes(): array |
||
| 119 | { |
||
| 120 | return [ |
||
| 121 | 'max', |
||
| 122 | 'min', |
||
| 123 | 'selectionLabel', |
||
| 124 | 'fieldLayoutId', |
||
| 125 | 'templateOverride', |
||
| 126 | 'template' |
||
| 127 | ]; |
||
| 128 | } |
||
| 129 | |||
| 130 | /** |
||
| 131 | * @inheritdoc |
||
| 132 | */ |
||
| 133 | public function behaviors() |
||
| 134 | { |
||
| 135 | return [ |
||
| 136 | 'fieldLayout' => [ |
||
| 137 | 'class' => FieldLayoutBehavior::class, |
||
| 138 | 'elementType' => self::class |
||
| 139 | ], |
||
| 140 | ]; |
||
| 141 | } |
||
| 142 | |||
| 143 | /** |
||
| 144 | * @inheritdoc |
||
| 145 | */ |
||
| 146 | public function rules() |
||
| 147 | { |
||
| 148 | return array_merge( |
||
| 149 | parent::rules(), |
||
| 150 | [ |
||
| 151 | [ |
||
| 152 | [ |
||
| 153 | 'min', |
||
| 154 | 'max' |
||
| 155 | ], |
||
| 156 | 'integer', |
||
| 157 | 'min' => 0 |
||
| 158 | ] |
||
| 159 | ] |
||
| 160 | ); |
||
| 161 | } |
||
| 162 | |||
| 163 | /** |
||
| 164 | * @inheritdoc |
||
| 165 | */ |
||
| 166 | public function validate($attributeNames = null, $clearErrors = true): bool |
||
| 167 | { |
||
| 168 | // Run basic model validation first |
||
| 169 | $validates = parent::validate($attributeNames, $clearErrors); |
||
| 170 | |||
| 171 | // Run field validation as well |
||
| 172 | if (!MetaPlugin::getInstance()->getConfiguration()->validate($this)) { |
||
| 173 | $validates = false; |
||
| 174 | } |
||
| 175 | |||
| 176 | return $validates; |
||
| 177 | } |
||
| 178 | |||
| 179 | /** |
||
| 180 | * @inheritdoc |
||
| 181 | */ |
||
| 182 | public function getSettingsHtml() |
||
| 183 | { |
||
| 184 | // Get the available field types data |
||
| 185 | $fieldTypeInfo = $this->getFieldOptionsForConfiguration(); |
||
| 186 | |||
| 187 | $view = Craft::$app->getView(); |
||
| 188 | |||
| 189 | $view->registerAssetBundle(MetaSettingsAsset::class); |
||
| 190 | $view->registerJs( |
||
| 191 | 'new Craft.MetaConfiguration(' . |
||
| 192 | Json::encode($fieldTypeInfo, JSON_UNESCAPED_UNICODE) . ', ' . |
||
| 193 | Json::encode(Craft::$app->getView()->getNamespace(), JSON_UNESCAPED_UNICODE) . |
||
| 194 | ');' |
||
| 195 | ); |
||
| 196 | |||
| 197 | $view->registerTranslations('meta', [ |
||
| 198 | 'New field' |
||
| 199 | ]); |
||
| 200 | |||
| 201 | $fieldTypeOptions = []; |
||
| 202 | |||
| 203 | /** @var Field|string $class */ |
||
| 204 | foreach (Craft::$app->getFields()->getAllFieldTypes() as $class) { |
||
| 205 | $fieldTypeOptions[] = [ |
||
| 206 | 'value' => $class, |
||
| 207 | 'label' => $class::displayName() |
||
| 208 | ]; |
||
| 209 | } |
||
| 210 | |||
| 211 | // Handle missing fields |
||
| 212 | $fields = $this->getFields(); |
||
| 213 | foreach ($fields as $i => $field) { |
||
| 214 | if ($field instanceof MissingField) { |
||
| 215 | $fields[$i] = $field->createFallback(PlainText::class); |
||
| 216 | $fields[$i]->addError('type', Craft::t('app', 'The field type “{type}” could not be found.', [ |
||
| 217 | 'type' => $field->expectedType |
||
| 218 | ])); |
||
| 219 | $this->hasFieldErrors = true; |
||
| 220 | } |
||
| 221 | } |
||
| 222 | $this->setFields($fields); |
||
|
0 ignored issues
–
show
|
|||
| 223 | |||
| 224 | return Craft::$app->getView()->renderTemplate( |
||
| 225 | FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'settings', |
||
| 226 | [ |
||
| 227 | 'field' => $this, |
||
| 228 | 'fieldTypes' => $fieldTypeOptions |
||
| 229 | ] |
||
| 230 | ); |
||
| 231 | } |
||
| 232 | |||
| 233 | /** |
||
| 234 | * @inheritdoc |
||
| 235 | */ |
||
| 236 | public function normalizeValue($value, ElementInterface $element = null) |
||
| 237 | { |
||
| 238 | /** @var Element|null $element */ |
||
| 239 | |||
| 240 | if ($value instanceof MetaQuery) { |
||
| 241 | return $value; |
||
| 242 | } |
||
| 243 | |||
| 244 | // New element query |
||
| 245 | $query = MetaElement::find(); |
||
| 246 | |||
| 247 | // Existing element? |
||
| 248 | if ($element && $element->id) { |
||
|
0 ignored issues
–
show
The expression
$element->id 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 For 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...
|
|||
| 249 | $query->ownerId($element->id); |
||
| 250 | } else { |
||
| 251 | $query->id(false); |
||
| 252 | } |
||
| 253 | |||
| 254 | // Set our field and site to the query |
||
| 255 | $query |
||
| 256 | ->fieldId($this->id) |
||
| 257 | ->siteId($element->siteId); |
||
| 258 | |||
| 259 | // Set the initially matched elements if $value is already set, which is the case if there was a validation |
||
| 260 | // error or we're loading an entry revision. |
||
| 261 | if (is_array($value) || $value === '') { |
||
| 262 | $query->status = null; |
||
| 263 | $query->enabledForSite = false; |
||
| 264 | $query->limit = null; |
||
| 265 | $query->setCachedResult( |
||
| 266 | $this->createElementsFromSerializedData($value, $element) |
||
| 267 | ); |
||
| 268 | } |
||
| 269 | |||
| 270 | return $query; |
||
| 271 | } |
||
| 272 | |||
| 273 | /** |
||
| 274 | * @inheritdoc |
||
| 275 | */ |
||
| 276 | public function modifyElementsQuery(ElementQueryInterface $query, $value) |
||
| 277 | { |
||
| 278 | /** @var ElementQuery $query */ |
||
| 279 | if ($value === 'not :empty:') { |
||
| 280 | $value = ':notempty:'; |
||
| 281 | } |
||
| 282 | |||
| 283 | if ($value === ':notempty:' || $value === ':empty:') { |
||
| 284 | $alias = MetaRecord::tableAlias() . '_' . $this->handle; |
||
| 285 | $operator = ($value === ':notempty:' ? '!=' : '='); |
||
| 286 | |||
| 287 | $query->subQuery->andWhere( |
||
| 288 | "(select count([[{$alias}.id]]) from " . |
||
| 289 | MetaRecord::tableName() . |
||
| 290 | " {{{$alias}}} where [[{$alias}.ownerId]] = [[elements.id]]" . |
||
| 291 | " and [[{$alias}.fieldId]] = :fieldId) {$operator} 0", |
||
| 292 | [':fieldId' => $this->id] |
||
| 293 | ); |
||
| 294 | } elseif ($value !== null) { |
||
| 295 | return false; |
||
| 296 | } |
||
| 297 | |||
| 298 | return null; |
||
| 299 | } |
||
| 300 | |||
| 301 | /** |
||
| 302 | * @return string |
||
| 303 | */ |
||
| 304 | public function getDefaultTemplate(): string |
||
| 305 | { |
||
| 306 | return $this->templateOverride ? $this->template : self::DEFAULT_TEMPLATE; |
||
| 307 | } |
||
| 308 | |||
| 309 | /** |
||
| 310 | * @return string |
||
| 311 | */ |
||
| 312 | public function getTemplate(): string |
||
| 313 | { |
||
| 314 | return $this->templateOverride ? $this->template : self::DEFAULT_TEMPLATE; |
||
| 315 | } |
||
| 316 | |||
| 317 | /** |
||
| 318 | * @param $template |
||
| 319 | * @return $this |
||
| 320 | */ |
||
| 321 | public function setTemplate($template) |
||
| 322 | { |
||
| 323 | if (!$this->templateOverride) { |
||
| 324 | $template = null; |
||
| 325 | } |
||
| 326 | $this->template = $template; |
||
| 327 | return $this; |
||
| 328 | } |
||
| 329 | |||
| 330 | /** |
||
| 331 | * @inheritdoc |
||
| 332 | */ |
||
| 333 | public function getInputHtml($value, ElementInterface $element = null): string |
||
| 334 | { |
||
| 335 | $id = Craft::$app->getView()->formatInputId($this->handle); |
||
| 336 | |||
| 337 | // Get the field data |
||
| 338 | $fieldInfo = $this->getFieldInfoForInput(); |
||
| 339 | |||
| 340 | Craft::$app->getView()->registerAssetBundle(MetaInputAsset::class); |
||
| 341 | |||
| 342 | Craft::$app->getView()->registerJs( |
||
| 343 | 'new Craft.MetaInput(' . |
||
| 344 | '"' . Craft::$app->getView()->namespaceInputId($id) . '", ' . |
||
| 345 | Json::encode($fieldInfo, JSON_UNESCAPED_UNICODE) . ', ' . |
||
| 346 | '"' . Craft::$app->getView()->namespaceInputName($this->handle) . '", ' . |
||
| 347 | ($this->min ?: 'null') . ', ' . |
||
| 348 | ($this->max ?: 'null') . |
||
| 349 | ');' |
||
| 350 | ); |
||
| 351 | |||
| 352 | Craft::$app->getView()->registerTranslations('meta', [ |
||
| 353 | 'Add new', |
||
| 354 | 'Add new above' |
||
| 355 | ]); |
||
| 356 | |||
| 357 | if ($value instanceof MetaQuery) { |
||
| 358 | $value |
||
| 359 | ->limit(null) |
||
| 360 | ->status(null) |
||
| 361 | ->enabledForSite(false); |
||
| 362 | } |
||
| 363 | |||
| 364 | return Craft::$app->getView()->renderTemplate( |
||
| 365 | FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'input', |
||
| 366 | [ |
||
| 367 | 'id' => $id, |
||
| 368 | 'name' => $this->handle, |
||
| 369 | 'field' => $this, |
||
| 370 | 'elements' => $value, |
||
| 371 | 'static' => false, |
||
| 372 | 'template' => self::DEFAULT_TEMPLATE |
||
| 373 | ] |
||
| 374 | ); |
||
| 375 | } |
||
| 376 | |||
| 377 | /** |
||
| 378 | * @inheritdoc |
||
| 379 | */ |
||
| 380 | public function getElementValidationRules(): array |
||
| 381 | { |
||
| 382 | return [ |
||
| 383 | 'validateMeta', |
||
| 384 | [ |
||
| 385 | ArrayValidator::class, |
||
| 386 | 'max' => $this->max ?: null, |
||
| 387 | 'tooMany' => Craft::t( |
||
| 388 | 'app', |
||
| 389 | '{attribute} should contain at most {max, number} {max, plural, one{record} other{records}}.' |
||
| 390 | ), |
||
| 391 | ], |
||
| 392 | ]; |
||
| 393 | } |
||
| 394 | |||
| 395 | /** |
||
| 396 | * Validates an owner element’s Meta. |
||
| 397 | * |
||
| 398 | * @param ElementInterface $element |
||
| 399 | * |
||
| 400 | * @return void |
||
| 401 | */ |
||
| 402 | public function validateMeta(ElementInterface $element) |
||
| 403 | { |
||
| 404 | /** @var Element $element */ |
||
| 405 | /** @var MetaQuery $value */ |
||
| 406 | $value = $element->getFieldValue($this->handle); |
||
| 407 | $validate = true; |
||
| 408 | |||
| 409 | foreach ($value->all() as $meta) { |
||
| 410 | /** @var MetaElement $meta */ |
||
| 411 | if (!$meta->validate()) { |
||
| 412 | $validate = false; |
||
| 413 | } |
||
| 414 | } |
||
| 415 | |||
| 416 | if (!$validate) { |
||
| 417 | $element->addError($this->handle, Craft::t('app', 'Correct the errors listed above.')); |
||
| 418 | } |
||
| 419 | } |
||
| 420 | |||
| 421 | /** |
||
| 422 | * @param mixed $value |
||
| 423 | * @param Element|ElementInterface $element |
||
| 424 | * @return string |
||
| 425 | */ |
||
| 426 | public function getSearchKeywords($value, ElementInterface $element): string |
||
| 427 | { |
||
| 428 | /** @var MetaQuery $value */ |
||
| 429 | |||
| 430 | $keywords = []; |
||
| 431 | $contentService = Craft::$app->getContent(); |
||
| 432 | |||
| 433 | /** @var MetaElement $meta */ |
||
| 434 | foreach ($value->all() as $meta) { |
||
| 435 | $originalContentTable = $contentService->contentTable; |
||
| 436 | $originalFieldContext = $contentService->fieldContext; |
||
| 437 | |||
| 438 | $contentService->contentTable = $meta->getContentTable(); |
||
| 439 | $contentService->fieldContext = $meta->getFieldContext(); |
||
| 440 | |||
| 441 | /** @var Field $field */ |
||
| 442 | foreach (Craft::$app->getFields()->getAllFields() as $field) { |
||
| 443 | $fieldValue = $meta->getFieldValue($field->handle); |
||
| 444 | $keywords[] = $field->getSearchKeywords($fieldValue, $element); |
||
| 445 | } |
||
| 446 | |||
| 447 | $contentService->contentTable = $originalContentTable; |
||
| 448 | $contentService->fieldContext = $originalFieldContext; |
||
| 449 | } |
||
| 450 | |||
| 451 | return parent::getSearchKeywords($keywords, $element); |
||
| 452 | } |
||
| 453 | |||
| 454 | /** |
||
| 455 | * todo - review this |
||
| 456 | * |
||
| 457 | * @inheritdoc |
||
| 458 | */ |
||
| 459 | public function getStaticHtml($value, ElementInterface $element): string |
||
| 460 | { |
||
| 461 | if ($value) { |
||
| 462 | $id = StringHelper::randomString(); |
||
| 463 | return Craft::$app->getView()->renderTemplate( |
||
| 464 | FieldHelper::TEMPLATE_PATH . DIRECTORY_SEPARATOR . 'input', |
||
| 465 | [ |
||
| 466 | 'id' => $id, |
||
| 467 | 'name' => $this->handle, |
||
| 468 | 'elements' => $value, |
||
| 469 | 'static' => true |
||
| 470 | ] |
||
| 471 | ); |
||
| 472 | } else { |
||
| 473 | Craft::$app->getView()->registerTranslations('meta', [ |
||
| 474 | 'No meta' |
||
| 475 | ]); |
||
| 476 | |||
| 477 | return '<p class="light">' . Craft::t('meta', 'No meta') . '</p>'; |
||
| 478 | } |
||
| 479 | } |
||
| 480 | |||
| 481 | /** |
||
| 482 | * @inheritdoc |
||
| 483 | */ |
||
| 484 | public function getEagerLoadingMap(array $sourceElements) |
||
| 485 | { |
||
| 486 | // Get the source element IDs |
||
| 487 | $sourceElementIds = []; |
||
| 488 | |||
| 489 | foreach ($sourceElements as $sourceElement) { |
||
| 490 | $sourceElementIds[] = $sourceElement->id; |
||
| 491 | } |
||
| 492 | |||
| 493 | // Return any relation data on these elements, defined with this field |
||
| 494 | $map = (new Query()) |
||
| 495 | ->select(['ownerId as source', 'id as target']) |
||
| 496 | ->from([MetaRecord::tableName()]) |
||
| 497 | ->where([ |
||
| 498 | 'fieldId' => $this->id, |
||
| 499 | 'ownerId' => $sourceElementIds, |
||
| 500 | ]) |
||
| 501 | ->orderBy(['sortOrder' => SORT_ASC]) |
||
| 502 | ->all(); |
||
| 503 | |||
| 504 | return [ |
||
| 505 | 'elementType' => MetaElement::class, |
||
| 506 | 'map' => $map, |
||
| 507 | 'criteria' => ['fieldId' => $this->id] |
||
| 508 | ]; |
||
| 509 | } |
||
| 510 | |||
| 511 | /** |
||
| 512 | * @inheritdoc |
||
| 513 | */ |
||
| 514 | public function beforeSave(bool $isNew): bool |
||
| 515 | { |
||
| 516 | // Save field settings (and field content) |
||
| 517 | if (!MetaPlugin::getInstance()->getConfiguration()->beforeSave($this)) { |
||
| 518 | return false; |
||
| 519 | } |
||
| 520 | |||
| 521 | // Trigger an 'afterSave' event |
||
| 522 | return parent::beforeSave($isNew); |
||
| 523 | } |
||
| 524 | |||
| 525 | /** |
||
| 526 | * @inheritdoc |
||
| 527 | */ |
||
| 528 | public function afterSave(bool $isNew) |
||
| 529 | { |
||
| 530 | // Save field settings (and field content) |
||
| 531 | MetaPlugin::getInstance()->getConfiguration()->afterSave($this); |
||
| 532 | |||
| 533 | // Trigger an 'afterSave' event |
||
| 534 | parent::afterSave($isNew); |
||
| 535 | } |
||
| 536 | |||
| 537 | /** |
||
| 538 | * @inheritdoc |
||
| 539 | */ |
||
| 540 | public function beforeDelete(): bool |
||
| 541 | { |
||
| 542 | // Delete field content table |
||
| 543 | MetaPlugin::getInstance()->getConfiguration()->beforeDelete($this); |
||
| 544 | |||
| 545 | // Trigger a 'beforeDelete' event |
||
| 546 | return parent::beforeDelete(); |
||
| 547 | } |
||
| 548 | |||
| 549 | /** |
||
| 550 | * @inheritdoc |
||
| 551 | */ |
||
| 552 | public function afterElementSave(ElementInterface $element, bool $isNew) |
||
| 553 | { |
||
| 554 | // Save meta element |
||
| 555 | MetaPlugin::getInstance()->getField()->afterElementSave($this, $element); |
||
| 556 | |||
| 557 | // Trigger an 'afterElementSave' event |
||
| 558 | parent::afterElementSave($element, $isNew); |
||
| 559 | } |
||
| 560 | |||
| 561 | /** |
||
| 562 | * @inheritdoc |
||
| 563 | */ |
||
| 564 | public function beforeElementDelete(ElementInterface $element): bool |
||
| 565 | { |
||
| 566 | // Delete meta elements |
||
| 567 | if (!MetaPlugin::getInstance()->getField()->beforeElementDelete($this, $element)) { |
||
| 568 | return false; |
||
| 569 | } |
||
| 570 | |||
| 571 | return parent::beforeElementDelete($element); |
||
| 572 | } |
||
| 573 | |||
| 574 | /** |
||
| 575 | * @inheritdoc |
||
| 576 | */ |
||
| 577 | protected function isValueEmpty($value, ElementInterface $element): bool |
||
|
0 ignored issues
–
show
|
|||
| 578 | { |
||
| 579 | /** @var MetaQuery $value */ |
||
| 580 | return $value->count() === 0; |
||
| 581 | } |
||
| 582 | |||
| 583 | /** |
||
| 584 | * |
||
| 585 | * Returns info about each field type for the configurator. |
||
| 586 | * |
||
| 587 | * @return array |
||
| 588 | */ |
||
| 589 | private function getFieldOptionsForConfiguration() |
||
| 590 | { |
||
| 591 | $disallowedFields = [ |
||
| 592 | self::class, |
||
| 593 | Matrix::class |
||
| 594 | ]; |
||
| 595 | |||
| 596 | $fieldTypes = []; |
||
| 597 | |||
| 598 | // Set a temporary namespace for these |
||
| 599 | $originalNamespace = Craft::$app->getView()->getNamespace(); |
||
| 600 | $namespace = Craft::$app->getView()->namespaceInputName('fields[__META_FIELD__][settings]', $originalNamespace); |
||
| 601 | Craft::$app->getView()->setNamespace($namespace); |
||
| 602 | |||
| 603 | /** @var Field|string $class */ |
||
| 604 | foreach (Craft::$app->getFields()->getAllFieldTypes() as $class) { |
||
| 605 | // Ignore disallowed fields |
||
| 606 | if (in_array($class, $disallowedFields)) { |
||
| 607 | continue; |
||
| 608 | } |
||
| 609 | |||
| 610 | Craft::$app->getView()->startJsBuffer(); |
||
| 611 | |||
| 612 | /** @var FieldInterface $field */ |
||
| 613 | $field = new $class(); |
||
| 614 | |||
| 615 | if ($settingsHtml = (string)$field->getSettingsHtml()) { |
||
| 616 | $settingsHtml = Craft::$app->getView()->namespaceInputs($settingsHtml); |
||
| 617 | } |
||
| 618 | |||
| 619 | $settingsBodyHtml = $settingsHtml; |
||
| 620 | $settingsFootHtml = Craft::$app->getView()->clearJsBuffer(); |
||
| 621 | |||
| 622 | $fieldTypes[] = [ |
||
| 623 | 'type' => $class, |
||
| 624 | 'name' => $class::displayName(), |
||
| 625 | 'settingsBodyHtml' => $settingsBodyHtml, |
||
| 626 | 'settingsFootHtml' => $settingsFootHtml, |
||
| 627 | ]; |
||
| 628 | } |
||
| 629 | |||
| 630 | Craft::$app->getView()->setNamespace($originalNamespace); |
||
| 631 | |||
| 632 | return $fieldTypes; |
||
| 633 | } |
||
| 634 | |||
| 635 | /** |
||
| 636 | * Returns html for all associated field types for the Meta field input. |
||
| 637 | * |
||
| 638 | * @return array |
||
| 639 | */ |
||
| 640 | private function getFieldInfoForInput(): array |
||
| 641 | { |
||
| 642 | // Set a temporary namespace for these |
||
| 643 | $originalNamespace = Craft::$app->getView()->getNamespace(); |
||
| 644 | $namespace = Craft::$app->getView()->namespaceInputName( |
||
| 645 | $this->handle . '[__META__][fields]', |
||
| 646 | $originalNamespace |
||
| 647 | ); |
||
| 648 | Craft::$app->getView()->setNamespace($namespace); |
||
| 649 | |||
| 650 | $fieldLayoutFields = $this->getFields(); |
||
| 651 | |||
| 652 | // Set $_isFresh's |
||
| 653 | foreach ($fieldLayoutFields as $field) { |
||
| 654 | $field->setIsFresh(true); |
||
| 655 | } |
||
| 656 | |||
| 657 | Craft::$app->getView()->startJsBuffer(); |
||
| 658 | |||
| 659 | $bodyHtml = Craft::$app->getView()->namespaceInputs( |
||
| 660 | Craft::$app->getView()->renderTemplate( |
||
| 661 | '_includes/fields', |
||
| 662 | [ |
||
| 663 | 'namespace' => null, |
||
| 664 | 'fields' => $fieldLayoutFields |
||
| 665 | ] |
||
| 666 | ) |
||
| 667 | ); |
||
| 668 | |||
| 669 | // Reset $_isFresh's |
||
| 670 | foreach ($fieldLayoutFields as $field) { |
||
| 671 | $field->setIsFresh(null); |
||
| 672 | } |
||
| 673 | |||
| 674 | $footHtml = Craft::$app->getView()->clearJsBuffer(); |
||
| 675 | |||
| 676 | $fields = [ |
||
| 677 | 'bodyHtml' => $bodyHtml, |
||
| 678 | 'footHtml' => $footHtml, |
||
| 679 | ]; |
||
| 680 | |||
| 681 | // Revert namespace |
||
| 682 | Craft::$app->getView()->setNamespace($originalNamespace); |
||
| 683 | |||
| 684 | return $fields; |
||
| 685 | } |
||
| 686 | |||
| 687 | /** |
||
| 688 | * Creates an array of elements based on the given serialized data. |
||
| 689 | * |
||
| 690 | * @param array|string $value The raw field value |
||
| 691 | * @param ElementInterface|null $element The element the field is associated with, if there is one |
||
| 692 | * |
||
| 693 | * @return MetaElement[] |
||
| 694 | */ |
||
| 695 | private function createElementsFromSerializedData($value, ElementInterface $element = null): array |
||
| 696 | { |
||
| 697 | /** @var Element $element */ |
||
| 698 | |||
| 699 | if (!is_array($value)) { |
||
| 700 | return []; |
||
| 701 | } |
||
| 702 | |||
| 703 | $oldElementsById = []; |
||
| 704 | |||
| 705 | // Get the old elements that are still around |
||
| 706 | if (!empty($element->id)) { |
||
| 707 | $ownerId = $element->id; |
||
| 708 | |||
| 709 | $ids = []; |
||
| 710 | |||
| 711 | foreach ($value as $metaId => &$meta) { |
||
| 712 | if (is_numeric($metaId) && $metaId !== 0) { |
||
| 713 | $ids[] = $metaId; |
||
| 714 | } |
||
| 715 | } |
||
| 716 | unset($meta); |
||
| 717 | |||
| 718 | if (!empty($ids)) { |
||
| 719 | $oldMetaQuery = MetaElement::find(); |
||
| 720 | $oldMetaQuery->fieldId($this->id); |
||
| 721 | $oldMetaQuery->ownerId($ownerId); |
||
| 722 | $oldMetaQuery->id($ids); |
||
| 723 | $oldMetaQuery->limit(null); |
||
| 724 | $oldMetaQuery->status(null); |
||
| 725 | $oldMetaQuery->enabledForSite(false); |
||
| 726 | $oldMetaQuery->siteId($element->siteId); |
||
| 727 | $oldMetaQuery->indexBy('id'); |
||
| 728 | $oldElementsById = $oldMetaQuery->all(); |
||
| 729 | } |
||
| 730 | } else { |
||
| 731 | $ownerId = null; |
||
| 732 | } |
||
| 733 | |||
| 734 | $elements = []; |
||
| 735 | $sortOrder = 0; |
||
| 736 | $prevElement = null; |
||
| 737 | |||
| 738 | foreach ($value as $metaId => $metaData) { |
||
| 739 | // Is this new? (Or has it been deleted?) |
||
| 740 | if (strpos($metaId, 'new') === 0 || !isset($oldElementsById[$metaId])) { |
||
| 741 | $meta = new MetaElement(); |
||
| 742 | $meta->fieldId = $this->id; |
||
|
0 ignored issues
–
show
It seems like
$this->id can also be of type string. However, the property $fieldId is declared as type integer|null. 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 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...
|
|||
| 743 | $meta->ownerId = $ownerId; |
||
| 744 | $meta->siteId = $element->siteId; |
||
| 745 | } else { |
||
| 746 | $meta = $oldElementsById[$metaId]; |
||
| 747 | } |
||
| 748 | |||
| 749 | $meta->setOwner($element); |
||
| 750 | $meta->enabled = (isset($metaData['enabled']) ? (bool)$metaData['enabled'] : true); |
||
| 751 | |||
| 752 | // Set the content post location on the element if we can |
||
| 753 | $fieldNamespace = $element->getFieldParamNamespace(); |
||
| 754 | |||
| 755 | if ($fieldNamespace !== null) { |
||
| 756 | $metaFieldNamespace = ($fieldNamespace ? $fieldNamespace . '.' : '') . |
||
| 757 | '.' . $this->handle . |
||
| 758 | '.' . $metaId . |
||
| 759 | '.fields'; |
||
| 760 | $meta->setFieldParamNamespace($metaFieldNamespace); |
||
| 761 | } |
||
| 762 | |||
| 763 | if (isset($metaData['fields'])) { |
||
| 764 | $meta->setFieldValues($metaData['fields']); |
||
| 765 | } |
||
| 766 | |||
| 767 | $sortOrder++; |
||
| 768 | $meta->sortOrder = $sortOrder; |
||
| 769 | |||
| 770 | // Set the prev/next elements |
||
| 771 | if ($prevElement) { |
||
| 772 | /** @var ElementInterface $prevElement */ |
||
| 773 | $prevElement->setNext($meta); |
||
| 774 | /** @var ElementInterface $meta */ |
||
| 775 | $meta->setPrev($prevElement); |
||
| 776 | } |
||
| 777 | $prevElement = $meta; |
||
| 778 | |||
| 779 | $elements[] = $meta; |
||
| 780 | } |
||
| 781 | |||
| 782 | return $elements; |
||
| 783 | } |
||
| 784 | |||
| 785 | /** |
||
| 786 | * Returns the fields associated with this element. |
||
| 787 | * |
||
| 788 | * @return FieldInterface[] |
||
| 789 | */ |
||
| 790 | public function getFields(): array |
||
| 791 | { |
||
| 792 | return $this->getFieldLayout()->getFields(); |
||
| 793 | } |
||
| 794 | |||
| 795 | /** |
||
| 796 | * Sets the fields associated with this element. |
||
| 797 | * |
||
| 798 | * @param FieldInterface[] $fields |
||
| 799 | * |
||
| 800 | * @return void |
||
| 801 | */ |
||
| 802 | public function setFields(array $fields) |
||
| 803 | { |
||
| 804 | $defaultFieldConfig = [ |
||
| 805 | 'type' => null, |
||
| 806 | 'name' => null, |
||
| 807 | 'handle' => null, |
||
| 808 | 'instructions' => null, |
||
| 809 | 'required' => false, |
||
| 810 | 'translationMethod' => Field::TRANSLATION_METHOD_NONE, |
||
| 811 | 'translationKeyFormat' => null, |
||
| 812 | 'settings' => null, |
||
| 813 | ]; |
||
| 814 | |||
| 815 | foreach ($fields as $fieldId => $fieldConfig) { |
||
| 816 | if (!$fieldConfig instanceof FieldInterface) { |
||
| 817 | |||
| 818 | /** @noinspection SlowArrayOperationsInLoopInspection */ |
||
| 819 | $fieldConfig = array_merge($defaultFieldConfig, $fieldConfig); |
||
| 820 | |||
| 821 | $fields[$fieldId] = Craft::$app->getFields()->createField([ |
||
| 822 | 'type' => $fieldConfig['type'], |
||
| 823 | 'id' => $fieldId, |
||
| 824 | 'name' => $fieldConfig['name'], |
||
| 825 | 'handle' => $fieldConfig['handle'], |
||
| 826 | 'instructions' => $fieldConfig['instructions'], |
||
| 827 | 'required' => (bool)$fieldConfig['required'], |
||
| 828 | 'translationMethod' => $fieldConfig['translationMethod'], |
||
| 829 | 'translationKeyFormat' => $fieldConfig['translationKeyFormat'], |
||
| 830 | 'settings' => $fieldConfig['settings'], |
||
| 831 | ]); |
||
| 832 | } |
||
| 833 | } |
||
| 834 | |||
| 835 | $this->getFieldLayout()->setFields($fields); |
||
| 836 | } |
||
| 837 | } |
||
| 838 |
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: