XF 2.0 Add new "Display Location" for custom user field

nocte

Well-known member
I want to add a new "Display Location" for custom user fields (beside from ['personal', 'contact', 'preferences'])

I found this thread for XF 1: https://xenforo.com/community/threads/new-display-location-for-custom-user-fields.131696/ - but I cannot find out how to add the 4th display location here:
xf2_display_location.webp

I have added a code event listener with event entity_structure and event hint XF\Entity\UserField.

The method in my Listener class looks like this:

PHP:
    public static function userFieldEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure)
    {
        $structure->columns['display_group']['allowedValues'][] = 'my_new_display_group';
        //print_r($structure);
        //die();
    }

When I print and die, I see that 'my_new_display_group' is added to $structure - so that part works, but the group is not displayed in the AdminCP.

This is the important part of the base_custom_field_edit admin template:

HTML:
            <xf:radiorow name="display_group" value="{$field.display_group}"
                label="{{ phrase('display_location') }}">
                <xf:options source="$displayGroups" />
            </xf:radiorow>

So, I need to change the value of $displayGroups, I suppose..
 
o.k. got it :)

I have added a class extension for XF\Repository\UserField with this method:

PHP:
    public function getDisplayGroups()
    {
        $groups = parent::getDisplayGroups();

        $groups['my_new_display_group'] = \XF::phrase('my_new_display_group');

        return $groups;
    }

I also had to change the database table:

PHP:
    public function installStep1()
    {
        $db = \XF::db();
        $db->query("ALTER TABLE `xf_user_field` CHANGE `display_group` `display_group` ENUM('personal','contact','preferences', 'my_new_display_group')");
    }

    public function uninstallStep1()
    {
        $db = \XF::db();
        $db->query("ALTER TABLE `xf_user_field` CHANGE `display_group` `display_group` ENUM('personal','contact','preferences')");
    }

Is there a better way for changing the database structure in that case? What if another addon adds a display group that way?
 
Is there a better way for changing the database structure in that case? What if another addon adds a display group that way?
There is -- the schema manager. You can see its use through our upgrade scripts, but as a particular example:

Code:
$db->getSchemaManager()->alterTable('xf_option', function (Alter $table)
{
   $table->changeColumn('edit_format')->addValues('username');
});
That's adding a value to the enum. There's a corresponding removeValues() method that you can use on uninstall.

Note that technically, your uninstall script needs to do more than that. You would need to change the display_group value back to one of the "original" values, if any fields remain with the value you added.
 
Thank you so much, @Mike !

Another one: Is there a way to check in a template if any of the fields of my_new_display_group has a value?

Would be nicer than checking for each field in an if-condition like:

PHP:
<xf:if is="$user.Profile.custom_fields.field1 || $user.Profile.custom_fields.field2 || $user.Profile.custom_fields.field3 || $user.Profile.custom_fields.field4">
 
Grouping is only a display helper. The internal set of fields doesn't know anything about it so you need to do that.

That said, look at the "contentcheck" style <xf:if> calls. If you're doing what I think you're doing, it may prevent you from having to duplicate individual if's within that.
 
That said, look at the "contentcheck" style <xf:if> calls.
I have already done it that way (y) Was still curious, if there's a "if display group has value" condition ;)

btw: if anyone's interested in my setup class (for now):
PHP:
    public function installStep1()
    {
        $this->schemaManager()->alterTable('xf_user_field', function(Alter $table)
        {
            $table->changeColumn('display_group')->addValues('my_new_display_group');
        });
    }

    public function uninstallStep1()
    {
        $db = \XF::db();
        $db->query("UPDATE `xf_user_field` SET `display_group` = 'personal' WHERE `display_group` = 'my_new_display_group'");

        $this->schemaManager()->alterTable('xf_user_field', function(Alter $table)
        {
            $table->changeColumn('display_group')->removeValues('my_new_display_group');
        });
    }
 
Sorry for the necro bump - I've been trying to follow this to little avail.
I successfully extended the class, and the new display area shows up when trying to add a custom field - but if I try to save the field with it selected it says:
Oops! We ran into some problems.
Please enter a valid value.
I've updated the database to include the new field as an option, so I'm not sure what else the problem may be!

Is there some sort of cache I need to clear?
 
do you also have a Listener class (see above; 1st post)?
Nope, I don't - so that must be it! Thanks!

I set 'Listen to event' and 'Event hint' as you have above and created a file referenced for the PHP Callback with your function - but I'm unsure how to complete the callback.
Probably there's a class I should be using and extending and then referencing the extension in the callback instead of just putting the name of the function in your post.
Could you let me know what the rest of your listener looked like?
Screen Shot 2018-07-18 at 20.06.02.webp
 
PHP:
<?php

namespace HNZCharInfo;

class FieldListener {
    public static function userFieldEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure) {
        $structure->columns['display_group']['allowedValues'][] = 'character';
    }
}
Like I said, I don't know if I should be using or extending a class here at all and php callbacks are a whole new world to me (I've mostly just been cobbling things together like a script kiddie until Xenforo, now I'm having to learn a system), so I really appreciate your help!
 
Your class is named FieldListener, yet you call a class called ProfileListener. You will need to change one of those.
It's so obvious, thanks a bunch.
I'm also still learning the relationship between file/folder names and class names - so had to fix that, too.
All works well now!
 
Hit a new snag:
I want users to be able to update these fields, so I edited the account_details template to include:
Code:
<xf:macro template="custom_fields_macros" name="custom_fields_edit" arg-type="users" arg-group="character" arg-set="{$xf.visitor.Profile.custom_fields}" />
(Where "character" is the new display location I'd added above.)
This displays the new custom fields fine, the trouble is it doesn't save the user's input on submit.
Do I need to extend another class somewhere to resolve this?
 
Yes! You have to extend XF\Pub\Controller\Account and override the accountDetailsSaveProcess() method.

Something like:

PHP:
protected function accountDetailsSaveProcess(\XF\Entity\User $visitor)
{
    $form = parent::accountDetailsSaveProcess($visitor);

    $userProfile = $visitor->getRelationOrDefault('Profile');

    $this->customFieldsSaveProcess($form, 'character', $userProfile, true);

    return $form;
}
 
Still haven't learned my lesson about namespaces, but once I got that figured out it worked beautifully! Thanks again for all your help, @nocte !
 
Okay, last thing for this (I hope):
What should I be extending to make these fields appear through the custom_fields_display_thread template ?
And how do you know where to look for these classes, is it just extreme familiarity with the software, or something I'm missing?
 
Templates get their data from controllers. Controllers are bound to routes. So the first thing you would do is to nail down which route -> controller is in charge for your template. Once you have your controller, search for your template within an action() method.
Typically looks like this:
return $this->view("XF:TheController", "the_template", $theTemplateVars);
You would extend that method and essentially add your variables to $theTemplateVars and then access them in the template.
Keep in mind that templates are sometimes nested, so you would probably need to find the most upper / parent template.
 
I suppose the template custom_fields_display_thread displays only Thread fields, not Profile fields.
Haha, that makes sense. So I'm looking at the wrong template for displaying user information beside messages.
Just learned how to look for classes in templates through the refine search feature and found the proper template for what I'm doing - message_macros - where the edit I needed to make was imminently obvious.

Thanks for all your help, @S Thomas and @nocte ! This little excursion into the weeds of Xenforo should be complete now. (I even sorted out how to add the new field category to the admin edit users page based on the previous stuff. So I won't be coming back for that, either!)
 
Back
Top Bottom