Last active
December 2, 2025 14:26
-
-
Save mmikkel/0b0662742f6f0db264572050b4fdea09 to your computer and use it in GitHub Desktop.
Migrate LinkMate til native Link field
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace craft\contentmigrations; | |
| use Craft; | |
| use craft\db\Migration; | |
| use craft\db\Query; | |
| use craft\fields\Link; | |
| use craft\helpers\Json; | |
| use craft\records\Field as FieldRecord; | |
| use vaersaagod\linkmate\fields\LinkField as LinkMateField; | |
| /** | |
| * m251202_115451_convert_linkmate_to_link_fields migration. | |
| */ | |
| class m251202_115451_convert_linkmate_to_link_fields extends Migration | |
| { | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function safeUp(): bool | |
| { | |
| // Get all LinkMate fields (including trashed) | |
| /** @var FieldRecord[] $record */ | |
| $records = FieldRecord::findWithTrashed() | |
| ->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; | |
| } | |
| } | |
| // 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])) { | |
| $json[$customFieldElementUid] = Json::decode($json[$customFieldElementUid]); | |
| } | |
| $siteId = (int)$contentTableRow['siteId']; | |
| $contentData = &$json[$customFieldElementUid]; | |
| // 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 "m251202_115451_convert_linkmate_to_link_fields cannot be reverted.\n"; | |
| return false; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment