LOGIC Library

This site is the Logic involvement in sharing expertise and skills acquired in daily work. The goal is to create a solid knowledge base and share best practices in software development and systems management.

More info about us can be found on logicsistemi.it.

Joomla! 2.5: extending users' data with custom fields

In this post we try to explain how to extend Joomla! user's informations by adding additional fields, such as surname, organization or others.

The bad news is that you need to write PHP code and create a plugin. The good news is that in Joomla! 2.5 this task is very easy to be done and following this tutorial you can accomplish even if your coding skils are poor!

 

The easy way

If you simply want to extend user informations without understanding how Joomla! works and stores informations, you can simply follow the tutorial that you find on the Joomla! official documentation site and modify names and fields informations on the base of your needs.

Here we try to explain the code of a user profile plugin, so you will be able to extend functionalities or create components that read stored informations for other purposes.

The wrong way

One of the most common mistake is to go in Joomla! user management code and modify the files to add additional informations. This is a bad approach, because on the next update of Joomla! system all your patches will be overwritten.

Another wrong way to accomplish this task is to modify the code of User - Profile plugin. It's a plugin, but is always a part of Joomla! core, so you can come back to the previous problem. This plugin is intended as an example to create a personalized plugin, or you can use it without modifications if the fields you require are already in the list managed by the plugin.

On the web you can find some tutorial that tell you to modify the Joomla! user table. This was common until Joomla! 1.5, but is not the right way to proceed in the new versions of Joomla!. In the new versions you don't even need additional plugins such as usermeta. Usermeta concepts are now part of Joomla! core.

Folder and files

Let's start by creating, on your pc, a folder to developing our user profile personalized plugin.

Suppose will name it "testprofile". You can create the following folders and files:

plg_user_testprofile
profiles
index.html
profile.xml
en-GB.plg_user_testprofile.ini
en-GB.plg_user_testprofile.sys.ini
index.html
testprofile.php
testprofile.xml

Here a short explanations of the files:

  • testprofile.xml is the manifest file that contains installation instructions
  • testprofile.php is our code. It's here that we implement the required functionalities
  • the 2 .ini files are the translation files. They define conversions between uppercase constants used as labels and translations for different languages (here we have only english translations). The .sys.ini file is used only during install. The other is used by our code. You can find here more informations on how internationalization works.
  • profile.xml contains the informations about required fields. The syntax of this file is the same of the standard form fields files used by Jooma!
  • index.html files are only ther to hide folder contents if called directly by a browser

The manifest file

The content of testprofile.xml is the following:

<?xml version="1.0" encoding="utf-8"?>
<extension version="2.5" type="plugin" group="user">
 <name>plg_user_testprofile</name>
  <author>Edy Incoletti</author>
  <authorEmail>edy.incoletti@logicsistemi.it</authorEmail>
  <authorUrl>http://library.logicsistemi.it</authorUrl>
  <creationDate>May 2012</creationDate>
  <version>1.0.0</version>
 <description>PLG_USER_TEXTPROFILE_XML_DESCRIPTION</description>
 
 <files>
    <filename plugin="testprofile">testprofile.php</filename>
    <filename>index.html</filename>
    <folder>profiles</folder>
 </files>
 
 <languages>
    <language tag="en-GB">en-GB.plg_user_testprofile.ini</language>
    <language tag="en-GB">en-GB.plg_user_testprofile.sys.ini</language>
 </languages>
</extension>

The file starts with the extension tag that specify that the package is a plugin for the 2.5 version of Joomla!. This plugin will be added to the user group of plugins and so used to handle user management events.

After that there are some descriptive fields and then the list of files and folders to copy and the languages to be installed.

Compared to User profile plugin we have removed configuration parameters. This are used to activate or disable fields. Cause this is a personalized plugin I think these requirements can be extablished at design time.

Fields definitions

The file profile.xml define only an additional field. This is only an example and you can add all the required fields using the correct type.

<?xml version="1.0" encoding="utf-8"?>
<form>
  <fields name="testprofile">
    <fieldset name="testprofile"
     label="PLG_USER_TESTPROFILE_SLIDER_LABEL">
     <field
        name="organization"
        type="text"
        id="organization"
        label="PLG_USER_TESTPROFILE_FIELD_ORGANIZATION_LABEL" />
    </fieldset>
  </fields>
</form>

This field will be showed in a separate fieldset in the user registration form and in the user management application.

Database structure

Joomla! store user profile data in the following table (this table is part of the Joomla! core, so you don't need to create it):

CREATE TABLE #__user_profiles (
  user_id int(11) NOT NULL,
  profile_key varchar(100) NOT NULL,
  profile_value varchar(255) NOT NULL,
  ordering int(11) NOT NULL default '0',
  UNIQUE KEY idx_user_id_profile_key (user_id,profile_key)
);

Where the fields are used for the following purposes:

  • user_id is a foreign key to the id of the user table
  • profile_key is the name of our fields. To avoid conflicts between different user profile plugins, is better to prepend the name of the key with the name of the plugin. In our case the key will be testprofile.organization
  • profile_value is the field that store our value. Note that is a varchar field with a maximum length of 255 characters. This means that you cannot save, with this method, textarea fields that exceed this length
  • ordering is used for ordering purposes

The code

The file testprofile.php contains all the needed code.

<?php 
defined('JPATH_BASE') or die;
 
class plgUserTestprofile extends JPlugin
{
    function onContentPrepareData($context, $data)
    {
        // Check we are manipulating a valid form.
        if (!in_array($context, array('com_users.profile','com_users.registration','com_users.user','com_admin.profile'))){
            return true;
        }
 
        $userId = isset($data->id) ? $data->id : 0;
 
        // Load the profile data from the database.
        $db = &JFactory::getDbo();
        $db->setQuery(
            'SELECT profile_key, profile_value FROM #__user_profiles' .
            ' WHERE user_id = '.(int) $userId .
            ' AND profile_key LIKE \'testprofile.%\'' .
            ' ORDER BY ordering'
        );
        $results = $db->loadRowList();
 
        // Check for a database error.
        if ($db->getErrorNum()) {
            $this->_subject->setError($db->getErrorMsg());
            return false;
        }
 
        // Merge the profile data.
        $data->testprofile = array();
        foreach ($results as $v) {
            $k = str_replace('testprofile.', '', $v[0]);
            $data->testprofile[$k] = $v[1];
        }
 
        return true;
    }
 
    function onContentPrepareForm($form, $data)
    {
        // Load user_profile plugin language
        $lang = JFactory::getLanguage();
        $lang->load('plg_user_testprofile', JPATH_ADMINISTRATOR);
 
        if (!($form instanceof JForm)) {
            $this->_subject->setError('JERROR_NOT_A_FORM');
            return false;
        }
        // Check we are manipulating a valid form.
        if (!in_array($form->getName(), array('com_users.profile', 'com_users.registration','com_users.user','com_admin.profile'))) {
            return true;
        }

        // Add the profile fields to the form.
        JForm::addFormPath(dirname(__FILE__).'/profiles');
        $form->loadFile('profile', false);
    }
 
    function onUserAfterSave($data, $isNew, $result, $error)
    {
        $userId    = JArrayHelper::getValue($data, 'id', 0, 'int');
 
        if ($userId && $result && isset($data['testprofile']) && (count($data['testprofile'])))
        {
            try
            {
                $db = &JFactory::getDbo();
                $db->setQuery('DELETE FROM #__user_profiles WHERE user_id = '.$userId.' AND profile_key LIKE \'testprofile.%\'');
                if (!$db->query()) {
                    throw new Exception($db->getErrorMsg());
                }
 
                $tuples = array();
                $order    = 1;
                foreach ($data['testprofile'] as $k => $v) {
                    $tuples[] = '('.$userId.', '.$db->quote('testprofile.'.$k).', '.$db->quote($v).', '.$order++.')';
                }
 
                $db->setQuery('INSERT INTO #__user_profiles VALUES '.implode(', ', $tuples));
                if (!$db->query()) {
                    throw new Exception($db->getErrorMsg());
                }
            }
            catch (JException $e) {
                $this->_subject->setError($e->getMessage());
                return false;
            }
        }
 
        return true;
    }
 
    function onUserAfterDelete($user, $success, $msg)
    {
        if (!$success) {
            return false;
        }
 
        $userId    = JArrayHelper::getValue($user, 'id', 0, 'int');
 
        if ($userId)
        {
            try
            {
                $db = JFactory::getDbo();
                $db->setQuery(
                    'DELETE FROM #__user_profiles WHERE user_id = '.$userId .
                    " AND profile_key LIKE 'testprofile.%'"
                );
 
                if (!$db->query()) {
                    throw new Exception($db->getErrorMsg());
                }
            }
            catch (JException $e)
            {
                $this->_subject->setError($e->getMessage());
                return false;
            }
        }
 
        return true;
    }
 
}
?>

The onContentPrepareData method is called by the loadFormData method of the profile model in the com_admin component. The onContentPrepareData method symply add a new section to the $data object. This section wil complete the user form informations with the a new set that will be used to fill the fields of the form. To better understand this mechanism you should know how models and forms works in Joomla! and take a look at the com_admin component source code.

The onContentPrepareForm method complete the form definition by adding the informations it founds in the profile.xml file.  The new fields are added in the user profile page in a different frameset and are filled with values retrieved by onContentPrepareData. This method is also responsible to load translation files.

The onUserAfterSave function is called after saving user data during insert or updates. This function delete all record from the #__user_profiles table that store informations managed by our plugin and linked to the selected user. These informations are all the key that start with testprofile. After this operation it saves the new values with INSERT queries.

The onUserAfterDelete function simply removes user profile informations when a user is deleted. This is done by deleting records on the base of user_id field.

All methods have additional code to check errors and improve security.

If you need required fields symply add the following line at the end of oncontentPrepareForm method:

$form->setFieldAttribute('organization', 'required', 'required', 'testprofile');

Language files

This is the content of en-GB.plg_user_testprofile.sys.ini:

PLG_USER_TESTPROFILE="User - Test Profile"
PLG_USER_TESTPROFILE_XML_DESCRIPTION="User Test Profile Plug-in"

There are the strings used in the manifest file. They will be used for the registration of the component.

And this is the content of en-GB.plg_user_testprofile.ini:

PLG_USER_TESTPROFILE="User - Test Profile"
PLG_USER_TESTPROFILE_XML_DESCRIPTION="User Test Profile Plug-in"
PLG_USER_TESTPROFILE_SLIDER_LABEL="User Test Profile"
PLG_USER_TESTPROFILE_FIELD_ORGANIZATION_LABEL="Organization"

We have here the previous translations (this time needed during normal operations) and the label for the fieldset and the added field.

Installing and testing

To create the installation package simply compress the plg_user_testprofile folder as a zip file. You can also use the package that you can find here.

Install the package using the Extension manager and then go to the plugin manager and activate the plugin.

After this operations you will find a new frame in the following pages:

  • The user management in administration
  • The user registration form
  • The user details page

That's all for now.

Comments   

 
#1 Marco 2013-04-11 11:53
hello,
can she help me.

i want to check 2 fields.
variable with another.

for example:
field1 = check with variable1
field2 = check with variable2

if the check is "true" ok
if the check is "false" then error message.
then the user must enter the data again.

thank you
Quote
 
 
#2 Edy Incoletti 2013-04-11 12:20
Quoting Marco:
hello,
can she help me.

i want to check 2 fields.
variable with another.

for example:
field1 = check with variable1
field2 = check with variable2

if the check is "true" ok
if the check is "false" then error message.
then the user must enter the data again.

thank you


The right place to put validation is the onUserBeforeSav e method, so you can add this method in the plugin.

http://docs.joomla.org/Plugin/Events/User#onUserBeforeSav e

In the documentation there is no return value, but if this method returns false data are not saved. Never tried that!
Quote
 
 
#3 Marco 2013-04-11 12:55
okay. I created a function onUserBeforeSave.
but how do I do it with validate?

here my code.
http://pastebin.com/Bw7R4QWx
Quote
 
 
#4 Edy Incoletti 2013-04-11 13:58
A full example is complex and I will come back to this with a post.
For the moment here is an example (not checked, but should work).

function onUserBeforeSav e($data, $isNew)
{
$result = true;

if ($data['testpro file']['organiz ation'] == '')
{
$result = false;
JError::raiseWa rning(1000, 'Organization not set');
}

return $result;
}
Quote
 
 
#5 Marco 2013-04-11 17:52
thank you
it works perfectly.

however, the data are written anyway to the database.
how can I stop now?
Quote
 
 
#6 Edy Incoletti 2013-04-12 09:18
It shouldn't. Try to debug this file:
/libraries/joomla/user/user.php.

On line 800, if onUserBeforeSav e return false the store command is not executed.

Let me know
Quote
 
 
#7 Marco 2013-04-12 18:13
Error found.
I forgot
$result = false;

Thanks, for your help.
Quote
 
 
#8 Don Lewis 2013-04-24 00:29
My plg_user_testpr ofile breaks when I upgrade to Joomla 3.0. Works on my 2.5.x. Any chance a 3.0 fix is available. Thanks for the plugin and thanks for any help making it work with joomla 3.
Quote
 
 
#9 Marco 2013-04-24 22:56
it's me again :)
how can I hide or delete the fields in the backend.

Regards
Marco
Quote
 
 
#10 Edy Incoletti 2013-04-30 08:25
Quoting Don Lewis:
My plg_user_testprofile breaks when I upgrade to Joomla 3.0. Works on my 2.5.x. Any chance a 3.0 fix is available. Thanks for the plugin and thanks for any help making it work with joomla 3.

No time to do it soon, but it will be one of the next posts.
Quote
 
 
#11 Edy Incoletti 2013-04-30 08:31
Quoting Marco:
it's me again :)
how can I hide or delete the fields in the backend.

Look at the tutorial linked at the beginning. It adds the possibility to display or hide fields.
Quote
 
 
#12 Edy Incoletti 2013-05-16 17:38
Just completed a new tutorial explaining field validation for a user plugin.

http://library.logicsistemi.it/joomla/general-topics/89-joomla-2-5-user-plugin-adding-validation

Enjoy it!
Quote
 
 
#13 StefanA 2013-06-11 15:42
Hi Edy,

I want to have a custom field with a list that is filled from another table in my database. Do you have a hint what I have to do?
Quote
 
 
#14 Edy Incoletti 2013-06-11 16:05
Quoting StefanA:
Hi Edy,

I want to have a custom field with a list that is filled from another table in my database. Do you have a hint what I have to do?

Hi,
You have to create a custom field. Define it in profile.xml using the attribute type (ie type="organizat ion") for your field tag.

Create a field folder in your package root and put into it the field definition in the file organization.ph p using this declaration:

class JFormFieldOrgan ization extends FormFieldList

Look at FormFieldList documentation to understand how to add options to your custom field.

I will soon write a new post about it.
Quote
 
 
#15 StefanA 2013-06-23 22:14
Hi Edy,

I think this code in onContentPrepar eForm() isn't necessary respectively it does nothing:

$fields = array(
'organization',
);
Quote
 
 
#16 Edy Incoletti 2013-06-24 08:15
Quoting StefanA:
Hi Edy,

I think this code in onContentPrepareForm() isn't necessary respectively it does nothing:

$fields = array(
'organization',
);

Absolutely right! I've removed it.
Quote
 
 
#17 StefanA 2013-06-25 12:24
Quoting Edy Incoletti:

Hi,
You have to create a custom field. [...]

I will soon write a new post about it.

I'm stuck. I managed to create a list field that appears in the form and it is filled with the desired values form another table. If I select a value and save the user it is correctly saved in the table #__user_profile s. I can retrieve the correct value in onContentPrepar eData() but if I edit a user, the list field displays the default message ("select a value"). onContentPrepar eData() looks like this:

function onContentPrepar eData($context, $data)
{
...

// Merge the profile data.
$data->nwkvuser = array();
foreach ($results as $v) {
$k = str_replace('nw kvuser.', '', $v[0]);
$data->nwkv_user[$k] = $v[1];
}

return true;
}

Is this correct? What value must be in $k?
Quote
 
 
#18 Edy Incoletti 2013-06-25 14:16
Quoting StefanA:

// Merge the profile data.
$data->nwkvuser = array();
foreach ($results as $v) {
$k = str_replace('nwkvuser.', '', $v[0]);
$data->nwkv_user[$k] = $v[1];
}

It seems right. Pay only attention to this:
$data->nwkv_user
should be
$data->nwkvuser

Regards
Quote
 
 
#19 StefanA 2013-06-25 20:11
Life could be so easy if one would read his code thoroughly...
Quote
 
 
#20 Atiee 2013-07-30 12:56
Hi Edy!
With your tutorial I was able to extend the profile form with fields for my phonebook component. But I've a problem. On the User Manager screen custom fields are always closed, and the Basic Settings are opened. My component is used by some kind old ladys, who have nothing to do with the Basic Settings. I've set the Basic's display attribute to none with JavaScript in the onContentPrepar eForm, set the phonebook h3 and div tag classes to title pane-toggler-do wn, and pane-slider content pane-down. With this, the Basic now invisible, but the Phonebook is still closed. The arrow at the title shows that is open, and I can see, that it's actually opened for 1px, but when I check the element, I see, that height is 0px for my custom div, and heigth is auto for the invisible Basic div. I've tried to change it with JavaScript, but something is overrides it always...

Have you any solutions to make the custom fieldset always opened?

Atiee
Quote
 
 
#21 Edy Incoletti 2013-08-12 08:50
Quoting Atiee:
Hi Edy!
With your tutorial I was able to extend the profile form with fields for my phonebook component. But I've a problem. On the User Manager screen custom fields are always closed, and the Basic Settings are opened. My component is used by some kind old ladys, who have nothing to do with the Basic Settings. I've set the Basic's display attribute to none with JavaScript in the onContentPrepar eForm, set the phonebook h3 and div tag classes to title pane-toggler-down, and pane-slider content pane-down. With this, the Basic now invisible, but the Phonebook is still closed. The arrow at the title shows that is open, and I can see, that it's actually opened for 1px, but when I check the element, I see, that height is 0px for my custom div, and heigth is auto for the invisible Basic div. I've tried to change it with JavaScript, but something is overrides it always...

Have you any solutions to make the custom fieldset always opened?

Atiee

Try to add JS code in the onContentPrepar e method. Your code should trigger a click on the h3 tag with the right id.
Quote
 
 
#22 Atiee 2013-08-12 09:58
Quoting Edy Incoletti:

Try to add JS code in the onContentPrepare method. Your code should trigger a click on the h3 tag with the right id.

Thanks, that works!
Quote
 
 
#23 Pam 2013-08-19 04:18
HOW do I get these extra fields to show up on the User Manger panel? I need to see the extra field either in the registration notification email I receive OR on the User Manager so that I can know who is who. What's the point of gathering someone's organization (for example) at registration, if I cannot see that on the notification email or in the User Manager. It's a real job to have to go into each person's record just to find the field. Hope that makes sense?
Quote
 
 
#24 Edy Incoletti 2013-08-19 07:32
Quoting Pam:
HOW do I get these extra fields to show up on the User Manger panel? I need to see the extra field either in the registration notification email I receive OR on the User Manager so that I can know who is who. What's the point of gathering someone's organization (for example) at registration, if I cannot see that on the notification email or in the User Manager. It's a real job to have to go into each person's record just to find the field. Hope that makes sense?

HI Pam,
This is a tutorial, not a complete component you can download.
If you want to show the extra fields in the User Manager you have to create an override of the corresponding view. Start by copying the view /administrator/ components/com_ users/views/use rs/tmpl/default .php into /administrator/ templates/blues tork/html/com_u sers/default.ph p and modify it to meet your requirements.
Quote
 
 
#25 axiom 2013-08-20 09:34
hi,
Thanks for the tutorial.
My joomla version is 3,if it is possible put the 3rd version tutorial too!
Thank you
Quote
 
 
#26 Edy Incoletti 2013-08-21 08:39
Quoting axiom:
hi,
Thanks for the tutorial.
My joomla version is 3,if it is possible put the 3rd version tutorial too!
Thank you

The Joomla! 3 version will soon be there.
Quote
 
 
#27 Edy Incoletti 2013-08-28 12:33
Quoting Don Lewis:
My plg_user_testprofile breaks when I upgrade to Joomla 3.0. Works on my 2.5.x. Any chance a 3.0 fix is available. Thanks for the plugin and thanks for any help making it work with joomla 3.

I've set up a Joomla! 3.1 website and installed the plugin. This version http://library.logicsistemi.it/images/joomla/plg_user_testprofile_1.2.0.zip works perfectly nad you can find here my considerations http://library.logicsistemi.it/en/joomla/general-topics/93-joomla-3-custom-fields-for-user-s-profile.
Quote
 
 
#28 shahukl 2013-10-24 12:17
this error showing when i try to insatll testprofile "JFolder :: create: Could not create directory" Please Help me
Quote
 
 
#29 Edy Incoletti 2013-10-25 08:28
Quoting shahukl:
this error showing when i try to insatll testprofile "JFolder :: create: Could not create directory" Please Help me

Usually this is caused by wrong permissions on your filesystem.
The user under which runs the web server must have write permissions on several folders.
Check Joomla! docs.
Quote
 
 
#30 Elmer Balbin 2013-11-15 09:35
Followed the tutorial and works perfectly! How about if I want to add, dropdown, checkboxes or radio buttons?
Quote
 
 
#31 Victor 2013-12-03 20:37
saved my day ! thanks man !!!!
Quote
 
 
#32 anup 2013-12-20 11:36
add custom fields in user registration from
Quote
 
 
#33 Joey 2014-05-07 19:59
I'm adding my own script that makes a .ini with the form data, how do i catch the form data ?
$inidata = $_GET['testprofile']['organization'];
doesnt work :(
Quote
 
 
#34 Peorthyr 2014-07-14 14:16
Good day.
I have a question, let's suppose I have different type of user on my J3 installation, user of group A and users of group B. let's say that group A has a new field called "organization" (just to follow your "let me say really clear" tutorial), while Group B has a field called "society".
is it possible to make... well, I don't know, I guess two different plugins and to assign plugin A to the group A and plugin B to the group B? or is better to handle this kind of things directly in the plugin with some if..else?

thanks in advance.
Quote
 
 
#35 Carmine 2015-03-16 07:24
Hello,

I need to add a drop down list of countries to the user's profile and I think I can handle that using an sql field type, but I also need to add the possibility for the user to upload a picture. How can I achieve that ? How can I handle the upload of the file from the plugin and make sure that the name of the file is saved to the database ?

Thanks so much !!!
Quote
 
 
#36 sajib88 2016-04-23 13:46
I want to Upload file image+file when user registration, Can any one help me ?

database #__user add new Filed - upload
Trying this code but where i place ?

$file = JRequest::getVa r('upload', null, 'files', 'array');
jimport('joomla .filesystem.fil e');
$filename = JFile::makeSafe ($file['name']) ;
$src = $file['tmp_name'];
//$ext = JFile::getExt($filename);
$dest = JPATH_SITE . DS . 'images' . DS . 'upload' . DS .$filename;
JFile::upload($src, $dest);
Quote
 
 
#37 john 2016-10-27 16:20
Witch files i should edit to add 2 extra feeds?
Quote
 
 
#38 Edy Incoletti 2016-10-28 12:44
Quoting john:
Witch files i should edit to add 2 extra feeds?

simply add it as additional field tag in the profile.xml file
Quote