Advanced Data Manager Techniques 
Data managers are very complex internally. They encompass a lot of functionality and options that aren't needed in many cases. This section will discuss some of the more advanced usages of data managers, including discussing how to extend them to include additional functionality.

Using set_info to modify certain behaviors
Each data manager has built in functionality that it provides and default checks that it makes. Some of these data checks do not apply in all cases. Consider a user making a new post very shortly after making another post (so he or she would be stopped by the flood check). The thread/post data manager includes the checks to prevent flooding, so normally it would throw an error here. However, if the user decided to preview the post instead of submitting it, they should see their post instead of an error about the flood check. Calling set_info allows you to set a value to bypass that check, allowing the user to see their post instead of an error.

set_info($fieldname, $value)
setr_info($fieldname, &$value)


The set functions, the only difference between set_info and setr_info is with how the second argument is passed (by-value versus by-reference).
  • $fieldname - the name of the info field you are setting. This is arbitrary and each data manager contains it's own list of info fields it uses.
  • $value - the value that you are setting the field to. No verification is done on the data submitted.
These functions return nothing.

The info fields used by a data manager are unrelated to the data fields used by the set and setr functions. Values set by set_info will not be saved to the database.

Each data manager has a unique list of info fields that it uses. These can be found be looking for references to $this->info within the data manager code. Info fields are generally used in two situations:
  1. As a Boolean value, to control whether a certain section of code is run, such as the preview example discussed above. However, many sections of code do not have options attached to them; they will always be executed.
  2. As an array of extra data that the data manager can use but does not need. A good example occurs in the thread data manager. It accepts an array of info about the forum that the thread is being posted in. If you provide this information, the forum's last post time and other data will be updated; if you do not provide this information, nothing will be updated at the forum level.
Example usage:
$dataman->set_info('preview'true);
$foruminfo fetch_foruminfo(1);
$dataman->set_info('forum'$foruminfo); 


Using error callbacks for advanced behavior if an error occurs
One rarely used data manager feature allows you to call any function you wish just as an error occurs. You can invoke this feature by calling the set_failure_callback method of a data manager. This function takes only one argument, a callback. A callback is either a string that is the name of the function to call, or an array with 2 entries: a reference to an object and a string naming the method to call. Please see the PHP manual for more information on callbacks.

The function or object-method pair passed to set_failure_callback will need to accept the following as its arguments:
  1. &$dm - a reference to the data manager that caused the error. This allows you to read data out of the data manager and do additional processing. Note that the last entry in $dm->errors is the phrased version of the error that triggered this function call.
  2. $errorphrase - this is the name of the phrase describing this error. It has not be changed into the browsing user's language.
Note that the names of the variables received by this function are up to you; these are simply recommendations.

While this functionality is not necessarily required, it can make it easier to perform certain operations. For example, you are doing some additional updates after you've inserted the main data, and these updates may fail but very rarely. If they fail, you want the newly inserted data to be removed. This is possible to do with an error callback.

An example in vBulletin combines the moderator and user data managers. It is possible to use the moderator data manager to tell the user data manager to update a specific user's user group. However, if that use if the last administrator, the user group updates might fail. The code to do this follows.
// this takes place within the moderator data manager

function post_save_each()
{
    
// ...
    
    
$userdata =& datamanager_init('User'$this->registryERRTYPE_CP);
    
    
// ...
    
    
$userdata->set_failure_callback(array(&$this'update_user_failed_insert'));
    
    if (
$update_usergroupid)
    {
        
$userdata->set('usergroupid'$this->info['usergroupid']);
        
$userdata->set('displaygroupid'$this->info['usergroupid']);
    }
    
    
// ...
    
    
$userdata->save();
}

function 
update_user_failed_insert(&$user)
{
    
$this->condition 'moderatorid = ' $this->fetch_field('moderatorid');
    
$this->delete();
    
$this->condition '';

    
$this->errors array_merge($this->errors$user->errors);

This code undoes the moderator insert if the user can't be updated and then displays an error.


Structure of the $validfields member
The most important aspect of each data manager is the $validfields member. This is an array that controls:
  • What data the data manager accepts
  • How important the data is (is it required? is it be generated automatically?)
  • The data's type (integer, string, etc)
  • How the data is verified
The $validfields array is structured very specifically. The key is the name of the field you are controlling (userid, username, etc). The value is another array, consisting of 2 to 4 values itself:
  1. Data's type - this is the type the data will be cleaned to. This should be one of the constants defined for the input cleaner. Commonly used values are TYPE_STR (string), TYPE_INT (integer), and TYPE_UINT, though there are more types that you can use.
  2. Data required - this controls whether this field must be set for the data to be valid and savable. If you make a field required and the code does not set it before inserting, an error will be thrown. There are 4 possible values:
    REQ_YES - the field is required

    REQ_NO - the field is not required

    REQ_AUTO - the field can be automatically generated. The does not have any effect on code execution at this time. This is appropriate for things like post times that can be reasonably guessed before inserting the new data. (You will still need to write code to generate the appropriate value!)

    REQ_INCR - this field is an AUTO_INCREMENT field in the database, and thus will be automatically generated upon insertion.
  3. Verification method (optional) - this controls how the data is verified as being valid. This value can be used in three ways:
    Not set - if you do not set this value in the array, the no verification will be done on the data.

    The constant VF_METHOD - if you set this value to VF_METHOD, a function will be called to verify the data. The name of that function depends on the fourth value in this array. If you do not specify the fourth value, the function that is called is $this->verify_[fieldname]() (eg, $this->verify_userid()). The fourth value of the array overrides the function name.

    A string which will be evaluated as code - finally, if you set this field to any value other than VF_METHOD it will be treated as PHP code. Before your code completes, it should return true or false. The value being tested for validity is available in $data and the data manager that called the code is available in $dm.
  4. Verification method override (optional) - this applies only if you set the third value to VF_METHOD. You may then use this value to override the name of the function called. The function called will be $this->[value]().
Note that keys in this second array are not explicitly specified.

An example of a $validfields array:
// ... this is within a data manager
    
var $validfields = array(
    
'forumid'           => array(TYPE_UINT,       REQ_INCRVF_METHOD'verify_nonzero'),
    
'title'             => array(TYPE_STR,        REQ_YES,  VF_METHOD),
    
'title_clean'       => array(TYPE_STR,        REQ_YES),
    
'threadcount'       => array(TYPE_UINT,       REQ_NO),
    
'daysprune'         => array(TYPE_INT,        REQ_AUTO'if ($data == 0) { $data = -1; } return true;'),
    
// ...
); 
The following section will describe how you can use hooks to add valid fields to the data manager without modifying the source code directly.


Using hooks to modify and extend existing data managers
One of the most exciting additions to vBulletin in version 3.5 is the idea of a plugin system and hooks. For more information on this system, please see here.
Warning:
Using the plugin system to modify the default vBulletin code can cause significant problems with your board. Any modifications you make via the plugin system cannot be supported. Please turn off the plugin system before requesting support!
Hooks are provided at four distinct places in most data managers. Each location allows you to accomplish specific functionaliy that cannot easily be done by the other locations. The hooks are named in a consistent fashion. They prefixed by a short word representing the data managed, followed by "data_", followed by a suffix tying the hook to a specific location. Two examples of hooks are attachdata_start and userdata_presave.
  • Constructor (*data_start) - this is called when the data manager object is created. If you wish to modify $validfields, this is the hook you'd use. Remember that you should append values onto the array to avoid overwriting the existing values.

    If you need data verification and are only working with hooks, you will not be able to use the VF_METHOD constant. You will need to put all the verification code into a string as the third argument in the array or use a trick like the following code demonstrates. This code would be placed within the appropriate *data_start hook.
    // adding a field without data verification
    $this->validfields['myfield1'] = array(TYPE_STRREQ_NO);

    // adding a field with all data verification inline
    $this->validfields['myfield2'] = array(
        
    TYPE_INT,
        
    REQ_YES,
        
    'if ($data % 2 == 0)
        {
            $dm->error("myfield2_is_even");
            return false;
        }
        else
        {
            return true;
        }'
    );

    // adding a field with data verification outside
    // note: instead of defining the function here,
    //     you could put it in an outside file and include it
    if (!function_exists('dm_verify_myfield3'))
    {
        function 
    dm_verify_myfield3(&$data, &$dm)
        {
            
    // remove anything but a-z
            
    $data preg_replace('#[^a-z]#i'''$data);
            
            if (
    strtolower($data{0}) != 'z')
            {
                
    $dm->error('myfield3_does_not_begin_with_z');
                return 
    false;
            }
            else
            {
                
    // note that when this field gets saved,
                // it will be saved with everything but
                // a-z already removed
                
    return true;
            }
        }
    }
    $this->validfields['myfield3'] = array(TYPE_STRREQ_NO'return dm_verify_myfield3($data, $dm);'); 
  • Pre-save (*data_presave) - this is called just before the data is saved, in the pre_save method. This allows you to do any last minute data checks (which is useful when you have two pieces of data which interact) or for generating fields which have not been but can be reasonably guessed.

    Since the data has yet to be confirmed as valid, you should not save any changes into the database here.
  • Post-save (*data_postsave) - this is called just after the data is saved, in the post_save_each method. This allows you to update any additional data based on the changes made.

    For example, you may have created an additional table in the database which relies on the user name being correct. You could check if the username had changed and update your table appropriately:
    if (!$this->condition)
    {
        
    // inserting a new user, so insert into your table
    }
    else if (
    $this->user['username'])
    {
        
    // changing the username, so update your table

  • Delete (*data_delete) - this is called just after the data is deleted, in the post_delete method. This is similar to the hook called post-save, except it is called while deleting a record with the data manager. Continuing the example from post-save, when a user is deleted, you should remove the associated record from your table.
While most data managers have just these four hooks, some vary slightly. For example, some do not have the delete hook because they do not support deletion operations. Others are more complex and have additional hooks. Specific discussions of these variations are beyond the scope of this document.
Nathan Bertram 22nd Dec 2010, 04:00am
If you're looking some more info about take a peak at vB_DataManager_* classes at http://members.vbulletin.com/api/