Table of Contents

Events Multi-Reg Architecture

https://docs.google.com/document/d/1Y7ZwBsaklV1CUrMe_4EuAtDeSRnaRjCEAWl2-ewv3uA/edit?usp=sharing

This document outlines the technical approach for implementing FIS’s multi-registrant event workflow.

org.israelscouts.multireg

Org.israelscouts.multireg is a rights-reserved CiviCRM extension which will contain only the business logic for FIS’s specific use cases. To a large extent its job is to tie together utilities from the FOSS generic extensions in this document.

Not yet addressed below:

  • Settings form
  • Thank you screen
  • ???

Components

  • CiviCRM setting multireg_reltype for selecting which relationship types are used to suggest contacts for registration.

  • Angular basepage. For this extension fine-grained control over permissions is needed. Therefore we will create a new menu entry /civicrm/fis for Civi\Angular\Page\Main which doesn’t require “Access CiviCRM.” We will invoke CiviCRM hook angularModules to remove all undesired modules (e.g., mail) from the page (but not from /civicrm/a), and we will add our own modules here.

  • Angular module multireg. To be fleshed out. This module will implement the APIs/utilities supplied by org.civicrm.fieldmetadata to render the form. It will also be responsible for preparing submissions to be handed off to this extension’s custom APIs (see below). It is responsible to an extent not yet determined for data validation -- could happen client side, or it could use the new generic validate API action, should it be finished in time, or it could simply catch API exceptions and handle them gracefully.

  • CiviCRM pageRun hooks to:

    • hijack the public-facing “Register Now” button as needed. See FIS-26.
    • display an error message to anonymous users who try to access /civicrm/fis.
  • Custom CiviCRM API to accept parameters we determine are necessary (see FIS-29) to create one or more event registrations and process the payment. (See FIS-28 for ensuring that the transaction is associated with the event registration.) Pending technical discovery. This may have potential to be genericized, but for the purposes of scope and budget, we’re writing specifically for this use case first.

org.civicrm.fieldmetadata

Lexicon

Field: Field can mean many things depending on context. In the context of this extension, a field is a property of a CiviCRM entity. “First name” is a field on a Contact. A field may be represented by multiple HTML inputs or database rows/columns.

Field collection: A field collection is a collection of fields as defined above. In CiviCRM, Profiles and Price Sets are obvious examples of field collections. A less obvious example might be the result of api.Relationship.getfields.

This is an open source extension that adds a new API to CiviCRM which standardizes metadata about fields or field collections which can be used to build CiviCRM interfaces. Its primary function will be to facilitate building forms, but there may be other applications we haven’t thought of yet.

The initial use case is to get instructions for building a form from a field collection; we will provide a public interface for this.

In particular, because of FIS’s project goals, we are interested in standardizing metadata about the following field collections and their constituent fields:

  • Profiles
  • Price sets

Components

API: Fieldmetadata.get

Fieldmetadata.get(array(
  ‘entity’ => ‘API name’,
  // an array that can be used to lookup the entity; keys determined
  // by fetcher class
  ‘entity_params’ => $params,
));

The above API is responsible for:

  • Calling the delegated fetcher function
  • Passing along errors from the fetch
  • Calling the encapsulated transform function
  • Passing along error messages from the transformer
  • Returns a standard API result with a values array as detailed below

Proposed return format

{
  "is_error": 0,
  "version": 3,
  "count": 1,
  "values": [ /* field collection interface */
    {
      collectionType: ‘UFGroup’, /* i.e., what kind of field collection is this */
      label: ‘foo collection’,
      name: ‘foo_collection’,
      preText: ‘Fill this out!’,
      postTest: ‘Thanks!’,
      fields: [
        {
          collectionType: ‘UFField’, /* e.g., UFField, CustomField, getFields */
          entity: ‘Contact’, /* i.e., what API entity would be used to persist this field */
          label: ‘foo’,
          name: ‘custom_96’,
          defaultValue: ‘brains’,
          widget: ‘crmUiRichtext’,
          order: 1,
          options: {},
          required: 0
        }
      ],
    },
    {
      collectionType: ‘UFField’, /* e.g., UFField, CustomField, getFields */
      entity: ‘Activity, /* i.e., what API entity would be used to persist this field */
      label: ‘Favorite CRM’,
      name: ‘custom_99’,
      defaultValue: ‘civi’,
      widget: ‘select’,
      order: 2,
      options: {‘civi’: ‘CiviCRM’, ‘csv’: ‘Excel or other spreadsheets’},
      required: 1,
    }
  ]
}

CRM_Fieldmetadata_NormalizerFactory

This is a factory class to which the API delegates for determining what kind of transformation we’re doing. It has a method ‘create’ which takes (at least) the requested entity and which returns a new instance of the appropriate CRM_Fieldmetadata_Normalizer subclass. Here is a possible implementation of this method:

function create ($entity) {
  // key: Entity => value: PHP class
  $normalizerClasses = array();
  CRM_Fieldmetadata_Hook::registerNormalizer(&$normalizerClasses);
  $class = CRM_Utils_Array::value($entity, $normalizerClasses);
  if (!$class) {
​    // throw exception indicating no normalizer
​    // has been registered for this entity
  }
  $normalizer = new $class;
  if (!is_a($normalizer, CRM_Fieldmetadata_Normalizer)) {
​    // throw exception indicating the provided class
​    // does not extend the required base class
  }
  return $normalizer;
}

CRM_Fieldmetadata_Normalizer

A base class for all normalizers to extend. It will have default methods and/or abstract methods for its children to implement. These interfaces have not been designed yet.

CRM_Fieldmetadata_Normalizer_UFGroup, CRM_Fieldmetadata_Normalizer_PriceSet

Our implementations for these entities will ship with the extension but will implement hook_civicrm_metadata_registerNormalizer like anyone else would.

CRM_Fieldmetadata_FetcherFactory

This is a factory class to which the API delegates for determining how to fetch the metadata. This factory is analogous to CRM_Fieldmetadata_NormalizerFactory; see above for details.

CRM_Fieldmetadata_Fetcher

A base class for all fetchers to extend. It will have default methods and/or abstract methods for its children to implement. These interfaces have not been designed yet. The family of fetcher classes is responsible for retrieving metadata from CiviCRM and returning it in the format of a standard API result.

CRM_Fieldmetadata_Fetcher_UFGroup, CRM_Fieldmetadata_Fetcher_PriceSet

Our implementations for these entities will ship with the extension but will implement hook_civicrm_metadata_registerFetcher like anyone else would.

Angular Components

crmRenderFieldCollection: Directive

This will be an encapsulating directive that will take collection metadata (the return from the crmFieldMetadataFetch service) and renders the relevant metadata eg, pre-form help, post-form help, title, etc.

Example Usage

<crm-render-field-collection=”#nameOfCollectionVariable#”

​ type=”[div|fieldset]”>

Example Output

<fieldset|div

​ crm-render-field-collection=”#nameOfCollectionVariable#”>

​ <legend|h1></legend|h1>

   

Pre

   <div class=”crm-section” ng-each={nameOfCollectionVariable.fields

​ as |field|}>

   

Post

</fieldset|div>

crmRenderField: Directive

Directive to completely render a field. This includes pre-field and post-field help, label, and the widget itself. This directive wraps around crmField, crmLabel and crmRenderWidget

crmRenderWidget: Directive

Directive to render an individual field’s input component or widget. It is this directive’s responsibility to mark required fields as such, and no more. This directive does not care about pre or post field help, or label, its only job is to render an input element/widget.

crmFieldMetadataFetch: Service

Service that takes params and does the API fetch and error checking and returns an ordered list of fields to be rendered.

After some discussion we decided this service should be no more than a thin wrapper around the API. We thought this service might have other jobs to do -- to assign models to all of the fields that are about to be rendered, or to facilitate building a form specification (i.e., collections of field collections) -- but this can be handled easily by the implementer. See pseudocode:

var formSpec = [];

formSpec.push(fetch(‘UFGroup’, ‘get’, $params));

formSpec.push(fetch(‘UFGroup’, ‘get’, $differentParams));

formSpec._each(...)

<crm-render-field-collection formSpec[0] crm-model=”contact1”>