Last Updated: Jul. 14, 2022 at 09:00pm UTC

File Organization

Code Sections

There are three main sections of the Ninja Forms CiviCRM plugin, src, lib\Shared, and lib\CiviCrmSdk. These sections isolate code for ease of management, development, and reuse. Most of the code connecting these sections are programmed to interfaces, meaning that there is an interface that defines all the allowed requests that can be made, and any class honoring that interface can be used.

lib\Shared

The Shared section contains code that deals with Ninja Forms and Wordpress, but has NO knowledge of CiviCRM nor our integration with CiviCRM. As such this entire section, or any class within it, can be reused in any other Ninja Forms plugin. For the NF CiviCRM project, this shared section uses a 'CiviCrm' namespace to keep it separated, at least for now. For us to develop a truly shared package, the code here should be tested thoroughly, and possibly refactored to ensure we standardize on what we determine to be the 'best current practice'.

lib\CiviCrmSdk

The CiviCrmSdk section is a mini 'software development kit' for integrating with CiviCRM. Civi's API currently has two active versions - v3 and v4, and not all functionality is currently available in the more recent v4 version, so I wrote the SDK to standardize our requests and keep the plugin code relatively immune to whatever changes are happening in Civi. Much of the functionality is delivered through a main CiviCrm factory to help isolate the aforementioned changes. For example, when the plugin wishes to create a new contact, it can request the CiviCrm factory to produce a contact object and then call ->create() on that object. No matter what happens to CiviCrm's API development, those two calls remain the same and only the code within the CiviCrmSdk adapts to the change.

src

The src section is the primary source code for the Ninja Forms CiviCRM plugin. It is the only section that is aware of Ninja Forms, WordPress, and CiviCRM. As with the other sections, it is programmed primarily to interfaces and the implementation is delivered by factories. The code in this section licenses the plugin, registers the actions and plugin sections, outputs submission metaboxes. It contains a sub-sections of DataAccessObjects and 'Processors' to handle specialized functionality. I recognized this functionality as being similar to that of the other CRMs we currently offer; by isolating this functionality, my hope is that eventually this code structure can be shared with existing plugins and also speed up the development and reliability of new integrating plugins.

Class Types

Within each code section, the classes are categorized to provide a structure that encourages intended coding practices to reduce interdependencies.

Contracts

Contracts are interfaces that define the required functionality that a receiving class requires of an injected dependency. The receiving class specifies only the contract required, not a specific class, such that any class honoring the contract (by implementing the interface) can be injected into the receiving class.

Entities

Entities define data structure to be passed among classes. It is a class that robustly defines an associative array, ensuring that all required values are set and that each value has the correct data type.

Factories

Because of the added choices in instantiating classes, much of the instantiating is done using factories - classes that have the job of instantiating and return objects. The dependent classes can now be injected with a factory class and then request the desired class. An added benefit of using factories is that required objects are only instantiated when required, not when the dependent class is instantiated. Thus classes handling functionality like form processing can be registered and ready, but the actual processing classes are not instantiated unless triggered by a form submission.

Code Flow

The factories and contracts, while making the code easier to maintain and develop, makes it more difficult to trace and self-teach as one must bounce across multiple classes/files to eventually get to an instantiation. This documentation enables one to trace the process more effectively.

Initialization

ninja-forms-civicrm

This main plugin file resides in the root directory of the plugin (above the src, lib, and vendor directories ). It registers the plugin with WordPress, handles the Ninja Forms licensing, and checks for required installation parameters like PHP version. It then calls the bootstrap.php file at plugins_loaded priority '0', the earliest position available once all plugins are loaded.

bootstrap

The boostrap file, also in the root directory controls the loading and registering of all functionality at the appropriate times. First, it loads the autoloader to ensure all namespaced classes within its control can be automatically loaded. It then hooks its main function into a dedicated NF CiviCrm action hook; the purpose of this action hook is to extend access to the plugin's features through WordPress' add_action functionality. It then creates the Master class that controls the plugin's functionality and passes that class into the action hook. Its main bootstrap function, previously added to the primary action, hooks two classes into WordPress' flow. One is hooked at plugins_loaded priority 5 and the other is hooked at init priority 15. This gives us an 'early' and a 'late' hook that enables one to time added functionality by calling it from the appropriate class.

Master

The Master class is the main class controlling the plugin. It extends a 'Container' class, which enables it to register dependencies and instantiate and deliver them on demand (using registerServices() method). It implements a shared interface requiring it to register advanced settings (shown in the advanced tab of the form builder) and register plugin settings (shown on the NF settings page.) Because much functionality between plugins can be shared, the master class extends a shared Abstract Master class that implements the shared functionality. I may revisit the master interface and break it into smaller interfaces because not all plugins will require all the currently specified methods

PluginsLoadedEarly

This class registers the plugin actions, and also instantiates the submission diagnostics metabox that displays submission results. Because this class is instantiated before CiviCRM is fully functional, no Civi funtionality can occur here.

InitLate

The plugin settings is instantiated here because it requires the Civi plugin to be active for it to function. If Civi is not active, the plugin settings will still load with a substitute class so that one can continue with a limited form build/integration and view any settings that do not depend on an active CiviCRM installation.

Settings

Plugin Settings

The plugin settings are registered using a shared PluginsRegistrar class. This shared registrar requires only the file name of a configuration file to register the plugin settings. The configuration file is an associative array structured to honor the PluginSettings entity definition. By doing this, the registrar knows exactly how to get the data it needs to register the plugin.

As part of the registration, the registrar passes the plugin settings at run time through a filter, automatically enabling run time adjustments to the configured settings. To modify those settings,a FilterSettingsOutput class extends a shared AbstractFilterSettingsOutput class. Only one method is required - modifyConfiguredSettings. This gives the class the opportunity to modify any of the configured settings; this method is only called immediately before the output of settings on the NF settings page, which means all WordPress functionality for the logged in user and all other plugins are fully active and available.

To add AJAX to the plugin settings, extend the shared Abstract AjaxHandler class by defining the properties and adding the abstracted methods and a jQuery file, then construct a new shared AjaxRegistrar object with your AjaxHandler. The registrar will enqueue the required scripts and you can easily pass data to your registered jQuery file, receive the user interaction into your handler, process it, and return the response to your jQuery file automatically.

Form Action

The form action and its settings are registered using a shared ActionRegistrar class. The ActionRegistar registers a shared FormAction adapter class that is constructed from a plugin's FormActionProcessor. This structure simplifies the action creation on only a few elements - an action configuration array, a required processSubmissionData() method and an optional finalizeActionSettings() method.

Action Configuration

The action configuration array specifies the primary action settings like name timing priority and can also include action settings. Any action settings values that require run time construction can be modified using the finalizeActionSettings() method, which is called at the time of form building. Also, custom CSS for the action can be specified as a CSS filename relative to root and the shared files will enqueue the CSS only on form builder pages.

Processing

Form processing is handled by a class extending the FormActionProcessor abstract class. The extending class implements the processSubmissionData() method, which is triggered by form submission. The extending class has access to submission data as specified by the action settings with which it performs its intended functionality. Optionally, the method can modify the form process $data array to store or pass data to subsequent actions.

Shared Library

Data Model Provider

The Data Model Provider contract/handler provides standardized data structures for known uses.

columnsFromCollection()

Data requests such a record retrieval or field definitions often comes in as a collection of objects in JSON format, which can be decoded into an indexed array of associative arrays. This method takes the full response to extract only the data from specific keys, thus reducing the data to only the columns which concern us.
While originally designed for external data requests, this method has an added benefit of easily paring down collections for internal functionality like creating label-value pairs for select lists.

keyValuePairsFromCollection()

Given a collection of objects decoded into an indexed array of associative arrays and string names for two specific keys in the associative array, this method constructs a single associative array of key-value pairs. For example, a record retrieval request provides the full collection of all CRM account users and the form designer needs to field map a certain user as owner of a task. This method is given the full collection along with the field keys for the user Id and the display name and returns an array of userId=>DisplayName. This data can be output on the screen or passed to the label-value pairs method that creates a select list array.

labelValuePairsFromCollection()

Given a collection of objects decoded into an indexed array of associative arrays and string names for two specific keys in the associative array, this method constructs an indexed array of associative arrays containing 'label'=> and 'value'=> keys. This is the construct used for NF options. As such this method enables the developer to pass in record or field definition responses to easily construct drop down lists.