0

I'm somewhat new to Craft, and struggling a bit with copying matrix block types. Any help would be greatly appreciated.

So, I have some code that copies a matrix block and its fields, but when I go to delete the newly copied block from the admin, it doesn't go away. I can get it to go away by first deleting all the fields, but not by just deleting the block itself. Am I doing something wrong in the following code that creates the new block?

Thanks in advance for any insight you can give!

private function copyMatrixBlock(string $matrix_handle, string $block_handle){

    //get the matrix field
    $matrix_field = Craft::$app->fields->getFieldByHandle($matrix_handle);

    //look up the block type id from the $block_handle
    $query = new Query;

    $query->select('mbt.id');
    $query->from(TABLE::MATRIXBLOCKTYPES . ' mbt');
    $query->where('mbt.handle = \''.$block_handle.'\'');

    $row = $query->one();

    $block_type_id = $row['id'];

    //get the block types of the matrix field
    $block_types = $matrix_field->getBlockTypes();

    //get the fields that we want to copy
    $the_fields_to_copy = $matrix_field->getBlockTypeFields([$block_type_id]);
    
    $save_these_blocks = [];

    $field_index = 0;
    $block_type_index = 0;

    foreach($block_types as $k => $block_type){

        $save_these_blocks[$block_type->id] = $block_type;

        if($block_type->id == $block_type_id){ //match on the looked up id from the $block_handle
            // copy this block type
            
            $copy_id = StringHelper::randomString(10);
            
            $config['name'] = $block_type->name . '_' . $copy_id;
            $config['handle'] = $block_type->handle . '_' . $copy_id;
            $config['fields'] = [];

            //loop through the fields to copy and add them to the block
            foreach($the_fields_to_copy as $field_to_copy){

                $field_config = [];
                
                $query = new Query;
                $query->select('f.*');
                $query->from(TABLE::FIELDS . ' f');
                $query->where('f.id = \''.$field_to_copy->id.'\''); 

                $row = $query->one();
                foreach($row as $k => $v){ //copy 
                    $field_config[$k] = $v; 
                }
                //replace the name and handle, unset 'uid'
                $field_config['name'] = $row['name'] . '_' . $copy_id;
                $field_config['handle'] = $row['handle'] . '_' . $copy_id;
                $field_config['uid'] = '';
                $field_config['settings'] = '';
                $field_config['typesettings'] = $row['settings'];

                $config['fields']['new' . ++$field_index] = $field_config; //the key needs to be non-numeric
            }

            $new_block = $this->makeBlockType($matrix_field->id, $config, $copy_id);

            $save_these_blocks['new' . ++$block_type_index] = $new_block; //the key needs to be non-numeric

        } else {
            //don't copy this block type
        }
    }

    //save the new blocks to the matrix field
    $matrix_field->setBlockTypes($save_these_blocks);

    //attempt to save
    $fieldsService = Craft::$app->getFields();
    if(!$fieldsService->saveField($matrix_field)){
        die('SOMETHING WENT WRONG');
    } else {
        die('SOMETHING WENT RIGHT');
    }
}

// mostly copied from Matrix::setBlockTypes
private function makeBlockType($matrix_field_id, $config, $copy_id):MatrixBlockType{

    $defaultFieldConfig = [
        'type' => null,
        'name' => null,
        'handle' => null,
        'instructions' => null,
        'required' => false,
        'searchable' => true,
        'translationMethod' => Field::TRANSLATION_METHOD_NONE,
        'translationKeyFormat' => null,
        'typesettings' => null,
    ];

    $blockType = new MatrixBlockType();
    $blockType->fieldId = $matrix_field_id;
    $blockType->name = $config['name'] . '_' . $copy_id;
    $blockType->handle = $config['handle'] . '_' . $copy_id;

    $fieldLayout = $blockType->getFieldLayout();
    if (($fieldLayoutTab = $fieldLayout->getTabs()[0] ?? null) === null) {
        $fieldLayoutTab = new FieldLayoutTab();
        $fieldLayoutTab->name = 'Content';
        $fieldLayoutTab->sortOrder = 1;
        $fieldLayout->setTabs([$fieldLayoutTab]);
    }

    $fields = [];
    $layoutElements = [];

    if (!empty($config['fields'])) {
        foreach ($config['fields'] as $fieldId => $fieldConfig) {
            // If the field doesn't specify a type, then it probably wasn't meant to be submitted
            if (!isset($fieldConfig['type'])) {
                continue;
            }

            $fieldConfig = array_merge($defaultFieldConfig, $fieldConfig);
            $field = $fields[] = Craft::$app->getFields()->createField([
                'type' => $fieldConfig['type'],
                'id' => is_numeric($fieldId) ? $fieldId : null,
                'name' => $fieldConfig['name'],
                'handle' => $fieldConfig['handle'],
                'columnSuffix' => $fieldConfig['columnSuffix'] ?? null,
                'instructions' => $fieldConfig['instructions'],
                'required' => (bool)$fieldConfig['required'],
                'searchable' => (bool)$fieldConfig['searchable'],
                'translationMethod' => $fieldConfig['translationMethod'],
                'translationKeyFormat' => $fieldConfig['translationKeyFormat'],
                'settings' => $fieldConfig['typesettings'],
            ]);

            $layoutElements[] = Craft::createObject([
                'class' => CustomField::class,
                'uid' => $fieldConfig['uid'] ?? null,
                'required' => (bool)$fieldConfig['required'],
                'width' => (int)($fieldConfig['width'] ?? 0) ?: 100,
            ], [$field]);
        }
    }

    $fieldLayoutTab->setElements($layoutElements);
    return $blockType;
}
2
  • Hi. just some ideas to try. ignore create layoutElements[] and setElement() . try creating new blocktype as array without setting fieldId: $newBlockType= []; $newBlockType['handle'] = $handle; $newBlockType['name'] = $name; after your createField function -without passing id param- push field to new block type $newBlockType['fields'][$fieldId] = $field; and return $newBlockType
    – user9975
    Commented Jan 6, 2023 at 15:42
  • Thanks for the suggestions. I think I figured it out. By taking out the die() statements after $fieldsService->saveField(), it appears to work correctly. I think by dying, I wasn't letting Craft do some operations it needed for the changes to take hold. Thinking it's something related to ProjectConfig.
    – Eli
    Commented Jan 7, 2023 at 9:21

1 Answer 1

0

Apparently this was broken because of the die() statements in the above code. I'm still not sure exactly what's going on, but commenting those out fixes the issue. I'm thinking Craft must do some cleanup at the end of a request that sets everything right.

Rather than delete the question, I'll leave it in case anyone happens to run into something similar. Basically, don't die!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.