where(['type' => LinkMateField::class]) ->all(); /** @var FieldRecord $record */ foreach ($records as $record) { /** @var LinkMateField $linkMateField */ $linkMateField = Craft::$app->getFields()->createField($record->toArray()); // Map link types and link type settings $linkFieldTypes = []; $linkFieldTypeSettings = []; $linkMateFieldLinkTypes = $linkMateField->getAllowedLinkTypes(); foreach ($linkMateFieldLinkTypes as $linkTypeId => $linkMateLinkType) { $linkMateTypeConfig = $linkMateField->getLinkTypeSettings($linkTypeId, $linkMateLinkType); if (in_array($linkTypeId, ['asset', 'category', 'entry'], true)) { $linkFieldTypes[] = $linkTypeId; $linkFieldTypeSettings[$linkTypeId] = [ 'sources' => $linkMateTypeConfig['sources'] ?? '*', ]; } else if ($linkTypeId === 'email') { $linkFieldTypes[] = 'email'; } else if ($linkTypeId === 'tel') { $linkFieldTypes[] = 'phone'; } else if ($linkTypeId === 'custom' || $linkTypeId === 'url') { $linkFieldTypes[] = 'url'; $linkFieldTypeSettings['url'] = [ 'allowRootRelativeUrls' => $linkTypeId === 'custom', 'allowAnchors' => $linkTypeId === 'custom', 'allowCustomSchemes' => $linkTypeId === 'custom', ]; } else { echo "Invalid link type for LinkMate field \"$linkMateField->handle\": $linkTypeId.\n"; return false; } } $linkFieldTypes = array_unique($linkFieldTypes); // Map Link "advanced" fields $linkFieldAdvancedFields = []; if ($linkMateField->allowTarget) { $linkFieldAdvancedFields[] = 'target'; } if ($linkMateField->autoNoReferrer) { $linkFieldAdvancedFields[] = 'rel'; } if ($linkMateField->enableAriaLabel) { $linkFieldAdvancedFields[] = 'ariaLabel'; } if ($linkMateField->enableTitle) { $linkFieldAdvancedFields[] = 'title'; } if (empty($record->dateDeleted)) { // Re-save the LinkMate field as Link $linkField = new Link([ 'id' => $linkMateField->id, 'handle' => $linkMateField->handle, 'name' => $linkMateField->name, 'uid' => $linkMateField->uid, 'types' => $linkFieldTypes, 'typeSettings' => $linkFieldTypeSettings, 'showLabelField' => $linkMateField->allowCustomText, 'advancedFields' => $linkFieldAdvancedFields, ]); if (!Craft::$app->getFields()->saveField($linkField)) { echo "Failed to convert LinkMate field \"$linkMateField->handle\" to Link.\n"; return false; } } else { $this->update('{{%fields}}', [ 'type' => Link::class, 'settings' => Json::encode([ 'types' => $linkFieldTypes, 'typeSettings' => $linkFieldTypeSettings, 'showLabelField' => $linkMateField->allowCustomText, 'advancedFields' => $linkFieldAdvancedFields, ]), ], [ 'uid' => $linkMateField->uid, ]); } echo "Successfully converted LinkMate \"$linkMateField->handle\" field to Link.\n"; } // Update Link field data /** @var FieldRecord[] $record */ $linkRecords = FieldRecord::findWithTrashed() ->where(['type' => Link::class]) ->all(); foreach ($linkRecords as $linkRecord) { $linkFieldUid = $linkRecord->uid; foreach (Craft::$app->getFields()->getAllLayouts() as $fieldLayout) { foreach ($fieldLayout->getCustomFieldElements() as $customFieldElement) { if ($customFieldElement->getFieldUid() !== $linkFieldUid) { continue; } $customFieldElementUid = $customFieldElement->uid; $contentTableRows = (new Query()) ->select(['id', 'elementId', 'siteId', 'content']) ->from('{{%elements_sites}}') ->where(['like', 'content', '"' . $customFieldElementUid . '"']) ->all(); foreach ($contentTableRows as $contentTableRow) { $json = Json::decodeIfJson($contentTableRow['content']); if (!is_array($json) || empty($json)) { continue; } if (empty($json[$customFieldElementUid])) { continue; } if (!is_array($json[$customFieldElementUid])) { try { $json[$customFieldElementUid] = Json::decode($json[$customFieldElementUid]); } catch (\Throwable $e) { // It was likely never a LinkMate field; converted URL => Link fields can sometimes just be a string (i.e. a URL) if (UrlHelper::isFullUrl($json[$customFieldElementUid])) { $json[$customFieldElementUid] = [ 'type' => 'url', 'value' => $json[$customFieldElementUid], ]; } else { throw $e; } } } $siteId = (int)$contentTableRow['siteId']; $contentData = &$json[$customFieldElementUid]; // Make sure invalid types becomes url $linkType = $contentData['type'] ?? null; if (!in_array($linkType, [ 'entry', 'url', 'asset', 'category', 'email', 'tel', 'sms', ])) { $contentData['type'] = 'url'; } // Rename customText to label? if (array_key_exists('customText', $contentData)) { $contentData['label'] = $contentData['customText']; unset($contentData['customText']); } // Transform numeric value into a valid reference tag "{type:value@siteId:url}" if ( isset($contentData['type'], $contentData['value']) && in_array($contentData['type'], ['asset', 'category', 'entry'], true) ) { $value = $contentData['value']; // Check if numeric (integer OR numeric string) if (is_numeric($value)) { $type = $contentData['type']; $elementId = (int)$value; // Make sure to use a valid site for the element // LinkMate didn't track the site for the linked element, but the Link field does $element = Craft::$app->getElements()->getElementById($elementId, $type, criteria: [ 'siteId' => '*', 'preferSites' => [$siteId], 'unique' => true, ]); if ($element) { $siteId = $element->siteId; } // Build new string $contentData['value'] = sprintf( '{%s:%d@%d:url}', $type, $elementId, $siteId ); } } $this->update( '{{%elements_sites}}', ['content' => $json], ['id' => $contentTableRow['id'], 'siteId' => $siteId], [], false ); } } } } return true; } /** * @inheritdoc */ public function safeDown(): bool { echo "m260313_115451_convert_linkmate_to_link_fields cannot be reverted.\n"; return false; } }