XF 2.3 Custom user fields tied with addon

QuackieMackie

Active member
I wasn't able to find anything related to this, are we able to create custom user fields and have them tied to an addon?

The only thing I could find for this is the manual creation in the admin control panel.
 
Solution
This is what I ended up with :) Thank you @Jeremy P
PHP:
<?php

namespace Sylphian\Library\Install;

use Sylphian\Library\Logger\Logger;
use XF\Entity\UserField;

trait SylInstallHelperTrait
{
    protected const array DEFAULT_USER_FIELD_OPTIONS = [
        'field_type'        => 'textbox',
        'display_order'     => 1,
        'display_group'     => 'personal',
        'required'          => false,
        'user_editable'     => 'yes',
        'moderator_editable'=> true,
        'show_registration' => false,
        'viewable_profile'  => true,
        'viewable_message'  => false,
        'max_length'        => 0,
    ];

    public function createUserField(string $fieldId, string $title, string $description = '', array...
Not in a first class way, but they're ultimately just database entries so you can just insert them using the entity system or directly (just make sure to run any cache rebuild methods if doing the latter).
 
This is what I ended up with :) Thank you @Jeremy P
PHP:
<?php

namespace Sylphian\Library\Install;

use Sylphian\Library\Logger\Logger;
use XF\Entity\UserField;

trait SylInstallHelperTrait
{
    protected const array DEFAULT_USER_FIELD_OPTIONS = [
        'field_type'        => 'textbox',
        'display_order'     => 1,
        'display_group'     => 'personal',
        'required'          => false,
        'user_editable'     => 'yes',
        'moderator_editable'=> true,
        'show_registration' => false,
        'viewable_profile'  => true,
        'viewable_message'  => false,
        'max_length'        => 0,
    ];

    public function createUserField(string $fieldId, string $title, string $description = '', array $fieldOptions = []): bool
    {
        $logger = Logger::withAddonId('Sylphian/Library');

        try {
            $logger->debug("Creating user field: {$fieldId}", [
                'title'   => $title,
                'options' => $fieldOptions
            ]);

            /** @var UserField $field */
            $field = \XF::em()->create('XF:UserField');
            $field->field_id = $fieldId;

            $options = array_merge(self::DEFAULT_USER_FIELD_OPTIONS, $fieldOptions);
            $logger->debug('Setting field options', ['merged_options' => $options]);
            $field->bulkSet($options);

            if (
                in_array($field->field_type, ['select', 'radio', 'checkbox', 'multiselect'], true) &&
                empty($field->field_choices)
            ) {
                $logger->error('Field choices required but not provided', [
                    'field_id'    => $fieldId,
                    'field_type'  => $field->field_type
                ]);
                return false;
            }

            if (!$field->save(false)) {
                $logger->error('Field save failed', [
                    'field_id' => $fieldId,
                    'errors'   => $field->getErrors()
                ]);
                return false;
            }

            $this->saveFieldPhrases($field, $title, $description, $logger);

            $logger->debug('Rebuilding field cache');
            \XF::repository('XF:UserField')->rebuildFieldCache();

            return true;
        } catch (\Exception $e) {
            $logger->error('Exception in createUserField', [
                'field_id'  => $fieldId,
                'exception' => $e->getMessage(),
                'trace'     => $e->getTraceAsString()
            ]);
            return false;
        }
    }

    protected function saveFieldPhrases(UserField $field, string $title, string $description, $logger): void
    {
        // Title phrase
        $titlePhrase = $field->getMasterPhrase(true);
        $titlePhrase->phrase_text = $title;
        $titlePhrase->global_cache = true;
        $titlePhrase->save(false);

        // Description phrase
        if ($description !== '') {
            $descPhrase = $field->getMasterPhrase(false);
            $descPhrase->phrase_text = $description;
            $descPhrase->global_cache = true;
            $descPhrase->save(false);
        }

        $logger->debug('Field phrases saved successfully', [
            'field_id' => $field->field_id,
            'title'    => $title,
            'description' => $description
        ]);
    }

    public function removeUserField(string $fieldId): bool
    {
        try {
            /** @var UserField|null $field */
            $field = \XF::em()->find('XF:UserField', $fieldId);

            if (!$field) {
                return true;
            }

            $success = $field->delete(false);

            if ($success) {
                \XF::repository('XF:UserField')->rebuildFieldCache();
            }

            return $success;
        } catch (\Exception $e) {
            \XF::logException($e, false, 'Error removing user field: ');
            return false;
        }
    }
}

Then in my setup file I just use the new trait and input the data like so:

PHP:
    public function installStep2(): void
    {
        try {
            /** @var UserPetsRepository $repository */
            $repository = $this->app()->repository('Sylphian\UserPets:UserPets');
            $spriteSheets = $repository->getAvailableSpriteSheets();

            $this->createUserField(
                'syl_userpets_spritesheet',
                'Sprite Sheet',
                'The sprite sheet image for your pet.',
                [
                    'field_type' => 'select',
                    'field_choices' => $spriteSheets,
                    'display_group' => 'preferences',
                    'display_order' => 500,
                    'required' => false,
                    'user_editable' => 'yes',
                    'show_registration' => false,
                    'viewable_profile' => false,
                    'viewable_message' => false
                ]
            );

        } catch (\Exception $e) {
            Logger::error('Unexpected error in installStep2', [
                'exception' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
        }
    }

    public function uninstallStep2(): void
    {
        $this->removeUserField('syl_userpets_spritesheet');
    }
 
Solution
Back
Top Bottom