Let the platform do the work

Metadata

Overview

An overview of the legacy MVC metadata framework.

You should note that the MVC architecture is being deprecated and is being replaced with sidecar. Until the framework is fully deprecated, modules set in backward compatibility mode will still use the legacy MVC framework.

Metadata Framework

Background

Metadata is defined as information about data. In Sugar, metadata refers to the framework of using files to abstract the presentation and business logic found in the system. The metadata framework is described in definition files that are processed using PHP. The processing usually includes the use of Smarty templates for rendering the presentation and JavaScript libraries to handle some business logic that affects conditional displays, input validation, and so on.

Application Metadata

All application modules are defined in the modules.php file. It contains several variables that define which modules are active and usable in the application.

The file is located under the '<sugar root>/include' folder. It contains the $moduleList() array variable which contains the reference to the array key to look up the string to be used to display the module in the tabs at the top of the application. The coding standard is for the value to be in the plural of the module name; for example, Contacts, Accounts, Widgets, and so on.

The $beanList() array stores a list of all active beans (modules) in the application. The $beanList entries are stored in a 'name' => 'value' fashion with the 'name' value being in the plural and the 'value' being in the singular of the module name. The 'value' of a $beanList() entry is used to lookup values in our next modules.php variable, the $beanFiles() array.

The $beanFiles variable is also stored in a 'name' => 'value' fashion. The 'name', typically in singular, is a reference to the class name of the object, which is looked up from the $beanList 'value', and the 'value' is a reference to the class file.

The remaining relevant variables in the modules.php file are the $modInvisList variable which makes modules invisible in the regular user interface (i.e., no tab appears for these modules), and the $adminOnlyList which is an extra level of security for modules that are accessible only by administrators through the Admin page.

Module Metadata

The following table lists the metadata definition files found in the modules/[module]/metadata directory, and a brief description of their purpose within the system.

File Description
additionalDetails.php Used to render the popup information displayed when a user hovers the mouse cursor over a row in the List View.
editviewdefs.php Used to render a record's EditView.
detailviewdefs.php Used to render a record's DetailView.
listviewdefs.php Used to render the List View display for a module.
metafiles.php Used to override the location of the metadata definition file to be used. The EditView, DetailView, List View, and Popup code check for the presence of these files.
popupdefs.php Used to render and handle the search form and list view in popups.
searchdefs.php Used to render a module's basic and advanced search form displays.
sidecreateviewdefs.php Used to render a module's quick create form shown in the side shortcut panel.
subpaneldefs.php Used to render a module's subpanels shown when viewing a record's DetailView.

SearchForm Metadata

The search form layout for each module is defined in the module's metadata file searchdefs.php. A sample of the Accounts searchdefs.php appears as:

  <?php

    $searchdefs['Accounts'] = array(
        'templateMeta' => array(
            'maxColumns' => '3',
            'widths' => array(
                'label' => '10', 
                'field' => '30'
            )
        ),
        'layout' => array(
            'basic_search' => array(
                'name',
                'billing_address_city',
                'phone_office',
                array(
                    'name' => 'address_street',
                    'label' => 'LBL_BILLING_ADDRESS',
                    'type' => 'name',
                    'group' => 'billing_address_street'
                ),
                array( 
                    'name' => 'current_user_only',
                    'label' => 'LBL_CURRENT_USER_FILTER',
                    'type'=>'bool'
                ),
            ),
            'advanced_search' => array(
                'name',
                array( 
                    'name' => 'address_street',
                    'label' => 'LBL_ANY_ADDRESS',
                    'type' => 'name'
                ),
                array( 
                    'name' => 'phone',
                    'label' => 'LBL_ANY_PHONE',
                    'type' => 'name'
                ),
                'website',
                array( 
                    'name' => 'address_city',
                    'label' => 'LBL_CITY',
                    'type' => 'name'
                ),
                array( 
                    'name' => 'email',
                    'label' =>'LBL_ANY_EMAIL',
                    'type' => 'name'
                ),
                'annual_revenue',
                array( 
                    'name' => 'address_state',
                    'label' =>'LBL_STATE',
                    'type' => 'name'
                ),
                'employees',
                array( 
                    'name' => 'address_postalcode',
                    'label' =>'LBL_POSTAL_CODE',
                    'type' => 'name'
                ),
                array(
                    'name' => 'billing_address_country',
                    'label' =>'LBL_COUNTRY',
                    'type' => 'name'
                ),
                'ticker_symbol',
                'sic_code',
                'rating',
                'ownership',
                array( 
                    'name' => 'assigned_user_id',
                    'type' => 'enum',
                    'label' => 'LBL_ASSIGNED_TO',
                    'function' => array(
                        'name' =>'get_user_array',
                        'params' => array(false)
                    )
                ),
                'account_type',
                'industry',
            ),
        ),
    );

?>

The searchdefs.php file contains the Array variable $searchDefs with one entry. The key is the name of the module as defined in $moduleList array defined in include/modules.php. The $searchDefsarray is another array that describes the search form layout and fields.

The 'templateMeta' key points to another array that controls the maximum number of columns in each row of the search form ('maxColumns'), as well as layout spacing attributes as defined by 'widths'. In the above example, the generated search form files will allocate 10% of the width spacing to the labels and 30% for each field respectively.

The 'layout' key points to another nested array which defines the fields to display in the basic and advanced search form tabs. Each individual field definition maps to a SugarField widget. See the SugarField widget section for an explanation about SugarField widgets and how they are rendered for the search form, DetailView, and EditView.

The searchdefs.php file is invoked from the MVC framework whenever a module's list view is rendered (see include/MVC/View/views/view.list.php). Within view.list.php, checks are made to see if the module has defined a SearchForm.html file. If this file exists, the MVC will run in classic mode and use the aforementioned include/SearchForm/SearchForm.php file to process the search form. Otherwise, the new search form processing is invoked using include/SearchForm/SearchForm2.php and the searchdefs.php file is scanned for, first under the custom/modules/[module]/metadata directory and then in modules/[module]/metadata.

The processing flow for the search form using the metadata subpaneldefs.php file is similar to that of EdiView and DetailView.

DetailView and EditView Metadata

Metadata files are PHP files that declare nested Array values that contain information about the view, such as buttons, hidden values, field layouts, and more. Following is a visual diagram representing how the Array values declared in the Metadata file are nested:

The following diagram highlights the process of how the application determines which Metadata file is to be used when rendering a request for a view:

The "Classic Mode" on the right hand side of the diagram represents the SugarCRM pre-5.x rendering of a Detail/Editview. This section will focus on the MVC/Metadata mode.

When the view is first requested, the preDisplay method will attempt to find the correct Metadata file to use. Typically, the Metadata file will exist in the [root level]/modules/[module]/metadata directory, but in the event of edits to a layout through the Studio interface, a new Metadata file will be created and placed in the [root level]/custom/modules/[module]/metadata directory. This is done so that changes to layouts may be restored to their original state through Studio, and also to allow changes made to layouts to be upgrade-safe when new patches and upgrades are applied to the application. The metafiles.php file that may be loaded allows for the loading of Metadata files with alternate naming conventions or locations. An example of the metafiles.php contents can be found for the Accounts module (though it is not used by default in the application).

  $metafiles['Accounts'] = array(
    'detailviewdefs' => 'modules/Accounts/metadata/detailviewdefs.php',
    'editviewdefs' => 'modules/Accounts/metadata/editviewdefs.php',
    'ListViewdefs' => 'modules/Accounts/metadata/ListViewdefs.php',
    'searchdefs' => 'modules/Accounts/metadata/searchdefs.php',
    'popupdefs' => 'modules/Accounts/metadata/popupdefs.php',
    'searchfields' => 'modules/Accounts/metadata/SearchFields.php',
);

After the Metadata file is loaded, the preDisplay method also creates an EditView object and checks if a Smarty template file needs to be built for the given Metadata file. The EditView object does the bulk of the processing for a given Metadata file (creating the template, setting values, setting field level ACL controls if applicable, etc.). Please see the EditView process diagram for more detailed information about these steps.

After the preDisplay method is called in the view code, the display method is called, resulting in a call to the EditView object's process method, as well as the EditView object's display method.

The EditView class is responsible for the bulk of the Metadata file processing and creation of the resulting display. The EditView class also checks to see if the resulting Smarty template is already created. It also applies the field level ACL controls for Sugar Sell, Serve, Ultimate, Enterprise, Corporate, and Professional.

The classes responsible for displaying the Detail View and SearchForm also extend and use the EditView class. The ViewEdit, ViewDetail and ViewSidequickcreate classes use the EditView class to process and display their contents. Even the file that renders the quick create form display (SubpanelQuickCreate.php) uses the EditView class. DetailView (in DetailView2.php) and SearchForm (in SearchForm2.php) extend the EditView class while SubpanelQuickCreate.php uses an instance of the EditView class. The following diagram highlights these relationships.

The following diagram highlights the EditView class's main responsibilities and their relationships with other classes in the system. We will use the example of a DetailView request although the sequence will be similar for other views that use the EditView class.

One thing to note is the EditView class's interaction with the TemplateHandler class. The TemplateHandler class is responsible for generating a Smarty template in the cache/modules/<module> directory. For example, for the Accounts module, the TemplateHandler will create the Smarty file, cache/modules/Accounts/DetailView.tpl, based on the Metadata file definition and other supplementary information from the EditView class. The TemplateHandler class actually uses Smarty itself to generate the resulting template that is placed in the aforementioned cache directory.

Some of the modules that are available in the SugarCRM application also extend the ViewDetail class. One example of this is the DetailView for the Projects module. As mentioned in the MVC section, it is possible to extend the view classes by placing a file in the modules/<module>/views directory. In this case, a view.detail.php file exists in the modules/Projects/views folder. This may serve as a useful example in studying how to extend a view and apply additional field/layout settings not provided by the EditView class.

The following diagram shows the files involved with the DetailView example in more detail:

A high level processing summary of the components for DetailViews follows:

The MVC framework receives a request to process the DetaiView.php (A) action for a module. For example, a record is selected from the list view shown on the browser with URL:

index.php?action=DetailView&module=Opportunities&record=46af9843-ccdf-f489-8833

At this point the new MVC framework checks to see if there is a DetailView.php (A2) file in the modules/Opportunity directory that will override the default DetailView.php implementation. The presence of a DetailView.php file will trigger the "classic" MVC view. If there is no DetailView.php (A2) file in the directory, the MVC will also check if you have defined a custom view to handle the DetailView rendering in MVC (that is, checks if there is a file modules/Opportunity/views/view.detail.php). See the documentation for the MVC architecture for more information. Finally, if neither the DetailView.php (A2) nor the view.detail.php exists, then the MVC will invoke include/DetailView/DetailView.php (A).

The MVC framework (see views.detail.php in include/MVC/View/views folder) creates an instance of the generic DetailView (A)

  // Call DetailView2 constructor
$dv = new DetailView2();

// Assign by reference the Sugar_Smarty object created from MVC
// We have to explicitly assign by reference to back support PHP 4.x
$dv->ss =& $this->ss;

// Call the setup function
$dv->setup($this->module, $this->bean, $metadataFile, 'include/DetailView/DetailView.tpl');

// Process this view
$dv->process();

// Return contents to the buffer
echo $dv->display();

When the setup method is invoked, a TemplateHandler instance (D) is created. A check is performed to determine which detailviewdefs.php metadata file to used in creating the resulting DetailView. The first check is performed to see if a metadata file was passed in as a parameter. The second check is performed against the custom/studio/modules/[Module] directory to see if a metadata file exists. For the final option, the DetailView constructor will use the module's default detailviewdefs.php metadata file located under the modules/[Module]/metadata directory. If there is no detailviewdefs.php file in the modules/[Module]/metadata directory, but a DetailView.html exists, then a "best guess" version is created using the metadata parser file in include/SugarFields/Parsers/DetailViewMetaParser.php (not shown in diagram).

The TemplateHandler also handles creating the quick search (Ajax code to do look ahead typing) as well as generating the JavaScript validation rules for the module. Both the quick search and JavaScript code should remain static based on the definitions of the current definition of the metadata file. When fields are added or removed from the file through Studio, this template and the resulting updated quick search and JavaScript code will be rebuilt.

It should be noted that the generic DetailView (A) defaults to using the generic DetailView.tpl smarty template file (F). This may also be overridden through the constructor parameters. The generic DetailView (A) constructor also retrieves the record according to the record id and populates the $focus bean variable.

The process() method is invoked on the generic DetailView.php instance:

  function process()
{
    //Format fields first
    if($this->formatFields) 
    {
        $this->focus->format_all_fields();
    }

    parent::process();
}

This, in turn, calls the EditView->process() method since DetailView extends from EditView. The EditView->process() method will eventually call the EditView->render() method to calculate the width spacing for the DetailView labels and values. The number of columns and the percentage of width to allocate to each column may be defined in the metadata file. The actual values are rounded as a total percentage of 100%. For example, given the templateMeta section's maxColumns and widths values:

  'templateMeta' => array(
    'maxColumns' => '2',
    'widths' => array(
        array(
            'label' => '10', 
            'field' => '30'
        ),
        array(
            'label' => '10', 
            'field' => '30'
        )
    ),
),

We can see that the labels and fields are mapped as a 1-to-3 ratio. The sum of the widths only equals a total of 80 (10 + 30 x 2) so the actual resulting values written to the Smarty template will be at a percentage ratio of 12.5-to-37.5. The resulting fields defined in the metadata file will be rendered as a table with the column widths as defined:

The actual metadata layout will allow for variable column lengths throughout the displayed table. For example, the metadata portion defined as:

  'panels' => array(
    'default' => array(
        array(
            'name',
            array(
                'name' => 'amount',
                'label' => '{$MOD.LBL_AMOUNT} ({$CURRENCY})',
            ),
        ),
        array(
            'account_name',
        ),
        array(
            '',
            'opportunity_type',
        )
    )
)

This specifies a default panel under the panels section with three rows. The first row has two fields (name and amount). The amount field has some special formatting using the label override option. The second row contains the account_name field and the third row contains the opportunity_type column.


Next, the process() method populates the $fieldDefs array variable with the vardefs.php file (G) definition and the $focus bean's value. This is done by calling the toArray () method on the $focus bean instance and combining these values with the field definition specified in the vardefs.php file (G).

The display() method is then invoked on the generic DetailView instance for the final step.
When the display() method is invoked, variables to the DetailView.tpl Smarty template are assigned and the module's HTML code is sent to the output buffer.

Before HTML code is sent back, the TemplateHandler (D) first performs a check to see if an existing DetailView template already exists in the cache respository (H). In this case, it will look for file cache/modules/Opportunity/DetailView.tpl. The operation of creating the Smarty template is expensive so this operation ensures that the work will not have to be redone. As a side note, edits made to the DetailView or EditView through the Studio application will clear the cache file and force the template to be rewritten so that the new changes are reflected.

If the cache file does not exist, the TemplateHandler (D) will create the template file and store it in the cache directory. When the fetch() method is invoked on the Sugar_Smarty class (E) to create the template, the DetailView.tpl file is parsed.