'includeCustom' => TRUE,
'loadOptions' => ['id', 'label'],
'action' => 'create',
- 'select' => ['name', 'label', 'input_type', 'input_attrs', 'required', 'options', 'help_pre', 'help_post', 'serialize', 'data_type'],
+ 'select' => ['name', 'label', 'input_type', 'input_attrs', 'required', 'options', 'help_pre', 'help_post', 'serialize', 'data_type', 'fk_entity'],
'where' => [['input_type', 'IS NOT NULL']],
];
if (in_array($entityName, ['Individual', 'Household', 'Organization'])) {
.service('afGui', function(crmApi4, $parse, $q) {
// Parse strings of javascript that php couldn't interpret
+ // TODO: Figure out which attributes actually need to be evaluated, as a whitelist would be less error-prone than a blacklist
+ var doNotEval = ['filters'];
function evaluate(collection) {
_.each(collection, function(item) {
if (_.isPlainObject(item)) {
evaluate(item['#children']);
- _.each(item, function(node, idx) {
- if (_.isString(node)) {
- var str = _.trim(node);
+ _.each(item, function(prop, key) {
+ if (_.isString(prop) && !_.includes(doNotEval, key)) {
+ var str = _.trim(prop);
if (str[0] === '{' || str[0] === '[' || str.slice(0, 3) === 'ts(') {
- item[idx] = $parse(str)({ts: CRM.ts('afform')});
+ item[key] = $parse(str)({ts: CRM.ts('afform')});
}
}
});
else if (editor.getFormType() === 'search') {
editor.layout['#children'] = afGui.findRecursive($scope.afform.layout, {'af-fieldset': ''})[0]['#children'];
+ editor.searchDisplay = afGui.findRecursive(editor.layout['#children'], function(item) {
+ return item['#tag'] && item['#tag'].indexOf('crm-search-display-') === 0;
+ })[0];
+ editor.searchFilters = getSearchFilterOptions();
}
// Set changesSaved to true on initial load, false thereafter whenever changes are made to the model
return $scope.afform;
};
+ this.toggleContactSummary = function() {
+ if ($scope.afform.contact_summary) {
+ $scope.afform.contact_summary = false;
+ if ($scope.afform.type === 'search') {
+ delete editor.searchDisplay.filters;
+ }
+ } else {
+ $scope.afform.contact_summary = 'block';
+ if ($scope.afform.type === 'search') {
+ editor.searchDisplay.filters = editor.searchFilters[0].key;
+ }
+ }
+ };
+
+ function getSearchFilterOptions() {
+ var searchDisplay = editor.meta.searchDisplays[editor.searchDisplay['search-name'] + '.' + editor.searchDisplay['display-name']],
+ entityCount = {},
+ options = [];
+
+ addFields(searchDisplay['saved_search.api_entity'], '');
+
+ _.each(searchDisplay['saved_search.api_params'].join, function(join) {
+ var joinInfo = join[0].split(' AS ');
+ addFields(joinInfo[0], joinInfo[1] + '.');
+ });
+
+ function addFields(entityName, prefix) {
+ var entity = afGui.getEntity(entityName);
+ entityCount[entity.entity] = (entityCount[entity.entity] || 0) + 1;
+ var count = (entityCount[entity.entity] > 1 ? ' ' + entityCount[entity.entity] : '');
+ if (entityName === 'Contact') {
+ options.push({
+ key: "{'" + prefix + "id': options.contact_id}",
+ label: entity.label + count
+ });
+ } else {
+ _.each(entity.fields, function(field) {
+ if (field.fk_entity === 'Contact') {
+ options.push({
+ key: "{'" + prefix + field.name + "': options.contact_id}",
+ label: entity.label + count + ' ' + field.label
+ });
+ }
+ });
+ }
+ }
+ return options;
+ }
+
// Validates that a drag-n-drop action is allowed
this.onDrop = function(event, ui) {
var sort = ui.item.sortable;
</label>
<p class="help-block">{{:: ts('Allow CiviCRM users to add the form to their home dashboard.') }}</p>
</div>
+
+ <div class="form-group">
+ <div class="form-inline">
+ <label>
+ <input type="checkbox" ng-checked="afform.contact_summary" ng-click="editor.toggleContactSummary()">
+ {{:: ts('Add to contact summary page') }}
+ </label>
+ <select class="form-control" ng-model="afform.contact_summary" ng-if="afform.contact_summary">
+ <option value="block">{{:: ts('As Block') }}</option>
+ <option value="tab">{{:: ts('As Tab') }}</option>
+ </select>
+ </div>
+ <p class="help-block">{{:: ts('Placement can be configured using the Contact Layout Editor.') }}</p>
+ </div>
+ <div class="form-group" ng-if="afform.contact_summary && editor.searchDisplay && editor.searchFilters.length > 1">
+ <div class="form-inline">
+ <label for="af_config_form_search_filters">
+ {{:: ts('Filter on:') }}
+ </label>
+ <select class="form-control" id="af_config_form_search_filters" ng-model="editor.searchDisplay.filters">
+ <option ng-repeat="option in editor.searchFilters" value="{{ option.key }}">{{ option.label }}</option>
+ </select>
+ </div>
+ <p class="help-block">{{:: ts('Choose which contact from the search should match the contact being viewed.') }}</p>
+ </div>
</fieldset>
</ng-form>
'name' => 'is_token',
'data_type' => 'Boolean',
],
+ [
+ 'name' => 'contact_summary',
+ 'data_type' => 'String',
+ ],
[
'name' => 'repeat',
'data_type' => 'Mixed',
'data_type' => 'Array',
],
];
-
+ // Calculated fields returned by get action
if ($self->getAction() === 'get') {
$fields[] = [
'name' => 'module_name',
}
/**
- * Implements hook_civicrm_caseTypes().
+ * Implements hook_civicrm_tabset().
*
- * Generate a list of case-types.
+ * Adds afforms as contact summary tabs.
+ */
+function afform_civicrm_tabset($tabsetName, &$tabs, $context) {
+ if ($tabsetName !== 'civicrm/contact/view') {
+ return;
+ }
+ $scanner = \Civi::service('afform_scanner');
+ $weight = 111;
+ foreach ($scanner->getMetas() as $afform) {
+ if (!empty($afform['contact_summary']) && $afform['contact_summary'] === 'tab') {
+ $module = _afform_angular_module_name($afform['name']);
+ $tabs[] = [
+ 'id' => $afform['name'],
+ 'title' => $afform['title'],
+ 'weight' => $weight++,
+ 'icon' => 'crm-i fa-list-alt',
+ 'is_active' => TRUE,
+ 'template' => 'afform/contactSummary/AfformTab.tpl',
+ 'module' => $module,
+ 'directive' => _afform_angular_module_name($afform['name'], 'dash'),
+ ];
+ // If this is the real contact summary page (and not a callback from ContactLayoutEditor), load module.
+ if (empty($context['caller'])) {
+ Civi::service('angularjs.loader')->addModules($module);
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_civicrm_pageRun().
*
- * Note: This hook only runs in CiviCRM 4.4+.
+ * Adds afforms as contact summary blocks.
+ */
+function afform_civicrm_pageRun(&$page) {
+ if (get_class($page) !== 'CRM_Contact_Page_View_Summary') {
+ return;
+ }
+ $scanner = \Civi::service('afform_scanner');
+ $cid = $page->get('cid');
+ $side = 'left';
+ foreach ($scanner->getMetas() as $afform) {
+ if (!empty($afform['contact_summary']) && $afform['contact_summary'] === 'block') {
+ $module = _afform_angular_module_name($afform['name']);
+ $block = [
+ 'module' => $module,
+ 'directive' => _afform_angular_module_name($afform['name'], 'dash'),
+ ];
+ $content = CRM_Core_Smarty::singleton()->fetchWith('afform/contactSummary/AfformBlock.tpl', ['contactId' => $cid, 'block' => $block]);
+ CRM_Core_Region::instance("contact-basic-info-$side")->add([
+ 'markup' => '<div class="crm-summary-block">' . $content . '</div>',
+ 'weight' => 1,
+ ]);
+ Civi::service('angularjs.loader')->addModules($module);
+ $side = $side === 'left' ? 'right' : 'left';
+ }
+ }
+}
+
+/**
+ * Implements hook_civicrm_contactSummaryBlocks().
*
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_caseTypes
+ * @link https://github.com/civicrm/org.civicrm.contactlayout
*/
-function afform_civicrm_caseTypes(&$caseTypes) {
- _afform_civix_civicrm_caseTypes($caseTypes);
+function afform_civicrm_contactSummaryBlocks(&$blocks) {
+ $scanner = \Civi::service('afform_scanner');
+ foreach ($scanner->getMetas() as $afform) {
+ if (!empty($afform['contact_summary']) && $afform['contact_summary'] === 'block') {
+ // Provide our own group for this block to visually distinguish it on the contact summary editor palette.
+ $blocks += [
+ 'afform' => [
+ 'title' => ts('Form Builder'),
+ 'icon' => 'fa-list-alt',
+ 'blocks' => [],
+ ],
+ ];
+ $blocks['afform']['blocks'][$afform['name']] = [
+ 'title' => $afform['title'],
+ 'tpl_file' => 'afform/contactSummary/AfformBlock.tpl',
+ 'module' => _afform_angular_module_name($afform['name']),
+ 'directive' => _afform_angular_module_name($afform['name'], 'dash'),
+ 'sample' => [],
+ 'edit' => 'civicrm/admin/afform#/edit/' . $afform['name'],
+ ];
+ }
+ }
}
/**
--- /dev/null
+<crm-angular-js modules="{$block.module}">
+ <div id="bootstrap-theme">
+ <{$block.directive} options="{ldelim}contact_id: {$contactId}{rdelim}"></{$block.directive}>
+ </div>
+</crm-angular-js>
--- /dev/null
+<crm-angular-js modules="{$tabValue.module}">
+ <div id="bootstrap-theme">
+ <{$tabValue.directive} options="{ldelim}contact_id: {$contactId}{rdelim}"></{$tabValue.directive}>
+ </div>
+</crm-angular-js>
}
if (is_string($this->display) && !empty($this->savedSearch['id'])) {
$this->display = SearchDisplay::get(FALSE)
+ ->setSelect(['*', 'type:name'])
->addWhere('name', '=', $this->display)
->addWhere('saved_search_id', '=', $this->savedSearch['id'])
->execute()->first();
*/
private function getAfformFilters() {
$afform = $this->loadAfform();
- return array_column(\CRM_Utils_Array::findAll(
+ if (!$afform) {
+ return [];
+ }
+ // Get afform field filters
+ $filters = array_column(\CRM_Utils_Array::findAll(
$afform['layout'] ?? [],
['#tag' => 'af-field']
), 'name');
+ // Get filters passed into search display directive
+ $filterAttr = $afform['searchDisplay']['filters'] ?? NULL;
+ if ($filterAttr && is_string($filterAttr) && $filterAttr[0] === '{') {
+ $filters = array_unique(array_merge($filters, array_keys(\CRM_Utils_JS::getRawProps($filterAttr))));
+ }
+ return $filters;
}
/**
->setLayoutFormat('shallow')
->execute()->first();
// Validate that the afform contains this search display
- if (\CRM_Utils_Array::findAll(
+ $afform['searchDisplay'] = \CRM_Utils_Array::findAll(
$afform['layout'] ?? [],
- ['#tag' => "crm-search-display-{$this->display['type']}", 'display-name' => $this->display['name']])
- ) {
+ ['#tag' => "{$this->display['type:name']}", 'display-name' => $this->display['name']]
+ )[0] ?? NULL;
+ if ($afform['searchDisplay']) {
$this->_afform = $afform;
}
}