View source
<?php
namespace Drupal\crm_core_match\Plugin\crm_core_match\engine;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\crm_core_contact\ContactInterface;
use Drupal\crm_core_match\Plugin\crm_core_match\field\FieldHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class DefaultMatchingEngine extends MatchEngineBase {
const MATCH_CHARS_DEFAULT = 3;
protected $pluginManager;
protected $entityTypeManager;
protected $entityFieldManager;
public function __construct($configuration, $id, $definition, PluginManagerInterface $plugin_manager, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager) {
parent::__construct($configuration, $id, $definition);
$this->pluginManager = $plugin_manager;
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('plugin.manager.crm_core_match.match_field'), $container
->get('entity_type.manager'), $container
->get('entity_field.manager'));
}
public function match(ContactInterface $contact) {
$ids = array();
$fields = $contact
->getFieldDefinitions();
$results = array();
$configuration_rules = $this
->getConfigurationItem('rules') ?: [];
foreach ($configuration_rules as $name => $rules) {
if (isset($fields[$name])) {
$rules['field'] = $fields[$name];
if (!$this->pluginManager
->hasDefinition($rules['field']
->getType())) {
continue;
}
$field_handler = $this->pluginManager
->createInstance($rules['field']
->getType(), $rules);
foreach ($field_handler
->getPropertyNames() as $name) {
$result = $field_handler
->match($contact, $name);
$keys = array_keys($result);
$key = reset($keys);
if (isset($results[$key])) {
if (isset($results[$key][$name])) {
$results[$key][$name] += $result[$key][$name];
}
else {
$results[$key] += $result[$key];
}
}
else {
$results += $result;
}
}
}
}
foreach ($results as $id => $rule_matches) {
$total_score = array_sum($rule_matches);
if ($total_score >= $this
->getConfigurationItem('threshold')) {
$ids[] = $id;
}
}
return $ids;
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['threshold'] = [
'#type' => 'textfield',
'#title' => $this
->t('Threshold'),
'#description' => $this
->t('Defines the score at which a contact is considered a match.'),
'#maxlength' => 28,
'#size' => 28,
'#required' => TRUE,
'#default_value' => $this
->getConfigurationItem('threshold'),
];
$return_description = $this
->t(<<<EOF
If two or more contact records result in matches with identical scores, CRM Core
will give preference to one over the other base on selected option.
EOF
);
$form['return_order'] = [
'#type' => 'select',
'#title' => $this
->t('Return Order'),
'#description' => $return_description,
'#default_value' => $this
->getConfigurationItem('return_order'),
'#options' => [
'created' => $this
->t('Most recently created'),
'updated' => $this
->t('Most recently updated'),
'associated' => $this
->t('Associated with user'),
],
];
$strict_description = $this
->t(<<<EOF
Check this box to return a match for this contact type the first time one is
identified that meets the threshold. Stops redundant processing.
EOF
);
$form['strict'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Strict matching'),
'#description' => $strict_description,
'#default_value' => $this
->getConfigurationItem('strict'),
];
$form['rules'] = [
'#title' => $this
->t('Field Matching'),
'#type' => 'table',
'#tree' => TRUE,
'#header' => $this
->buildHeader(),
'#empty' => $this
->t('There are no fields available.'),
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'weight',
],
],
'#theme_wrappers' => [
'form_element',
],
];
$form['unsupported'] = [
'#type' => 'details',
'#title' => $this
->t('Unsupported Fields'),
'#open' => TRUE,
'#access' => FALSE,
'table' => [
'#theme' => 'table',
'#header' => [
'label' => $this
->t('Name'),
'field_type' => $this
->t('Field type'),
],
'#rows' => [],
'#empty' => $this
->t('There are no unsupported fields.'),
],
'help_text' => [
'#theme_wrappers' => [
'container',
],
'#attributes' => [
'class' => [
'description',
],
],
'#markup' => $this
->t('A given field is unsupported when there is no field match handler for the field type it belongs to. In order to make it supported a handler plugin should be created for it.'),
],
];
$contact_types = $this->entityTypeManager
->getStorage('crm_core_individual_type')
->loadMultiple();
$fields = [];
foreach ($contact_types as $contact_type_id => $value) {
$fields += $this->entityFieldManager
->getFieldDefinitions('crm_core_individual', $contact_type_id);
}
foreach ($fields as $field) {
$rules = $this
->getConfigurationItem('rules');
$config = empty($rules[$field
->getName()]) ? [] : $rules[$field
->getName()];
$config['field'] = $field;
if ($this->pluginManager
->hasDefinition($field
->getType())) {
$match_field_id = $field
->getType();
$match_field = $this->pluginManager
->createInstance($match_field_id, $config);
foreach ($match_field
->getPropertyNames($field) as $name) {
$row = $this
->buildRow($match_field, $name, FALSE);
$form['rules'][$field
->getName() . ':' . $name] = $row;
}
}
else {
$form['unsupported']['table']['#rows'][] = [
'label' => $field
->getLabel(),
'type' => $field
->getType(),
];
$form['unsupported']['#access'] = TRUE;
}
}
return $form;
}
public function buildHeader() {
$header = [];
$header['status'] = $this
->t('Enabled');
$header['label'] = $this
->t('Name');
$header['field_type'] = $this
->t('Field type');
$header['operator'] = $this
->t('Operator');
$header['options'] = $this
->t('Options');
$header['score'] = $this
->t('Score');
$header['weight'] = $this
->t('Weight');
return $header;
}
public function buildRow(FieldHandlerInterface $field, $name, $disabled) {
$row = [];
$row['#attributes']['class'][] = 'draggable';
$row['#weight'] = $field
->getWeight($name);
$row['status'] = [
'#type' => 'checkbox',
'#default_value' => $field
->getStatus($name),
'#disabled' => $disabled,
];
$row['label'] = [
'#markup' => $field
->getLabel($name),
];
$row['type'] = [
'#markup' => $field
->getType(),
];
$row['operator'] = [
'#type' => 'select',
'#default_value' => $field
->getOperator($name),
'#empty_option' => !$disabled ? NULL : $this
->t('- Please Select -'),
'#options' => $field
->getOperators($name),
'#disabled' => $disabled,
];
$row['options'] = [
'#type' => 'textfield',
'#maxlength' => 28,
'#size' => 28,
'#default_value' => $field
->getOptions($name),
'#disabled' => $disabled,
];
$row['score'] = [
'#type' => 'textfield',
'#maxlength' => 4,
'#size' => 3,
'#default_value' => $field
->getScore($name),
'#disabled' => $disabled,
];
$row['weight'] = [
'#type' => 'weight',
'#title' => $this
->t('Weight for @field', [
'@field' => $field
->getLabel(),
]),
'#title_display' => 'invisible',
'#default_value' => $field
->getWeight($name),
'#attributes' => [
'class' => [
'weight',
],
],
];
return $row;
}
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
if (!is_numeric($form_state
->getValue([
'configuration',
'threshold',
]))) {
$form_state
->setErrorByName('configuration[threshold]', $this
->t('Threshold must be a number.'));
}
$rules = $form_state
->getValue([
'configuration',
'rules',
]);
foreach ($rules as $field_name => $config) {
if ($config['status'] && empty($config['operator'])) {
$name = 'rules][' . $field_name . '][operator';
$message = $this
->t('You must select an operator for enabled field.');
$form_state
->setErrorByName($name, $message);
}
if (!is_numeric($config['score'])) {
$name = 'rules][' . $field_name . '][score';
$message = $this
->t('You must enter number in "Score" column.');
$form_state
->setErrorByName($name, $message);
}
}
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$rules = [];
$this->configuration = $form_state
->getValue('configuration');
foreach ($form_state
->getValue([
'configuration',
'rules',
]) as $name => $config) {
if (strpos($name, ':') !== FALSE) {
list($parent, $child) = explode(':', $name, 2);
$rules[$parent][$child] = $config;
}
else {
$rules[$name] = $config;
}
}
$this->configuration['rules'] = $rules;
}
public function defaultConfiguration() {
}
public function calculateDependencies() {
return [];
}
public function getRules() {
$rules = [];
$contact_types = $this->entityTypeManager
->getStorage('crm_core_individual_type')
->loadMultiple();
$field_definitions = [];
foreach ($contact_types as $contact_type_id => $value) {
$field_definitions += $this->entityFieldManager
->getFieldDefinitions('crm_core_individual', $contact_type_id);
}
foreach ($this
->getConfigurationItem('rules') as $field_name => $field_settings) {
if (!isset($field_definitions[$field_name]) || !$this
->isEnabled($field_settings)) {
continue;
}
$field_definition = $field_definitions[$field_name];
$rules[$field_name] = [
'label' => $field_definition
->getLabel(),
'definition' => $field_definition,
];
}
return $rules;
}
protected function isEnabled(array $properties) {
return (bool) array_filter(array_map(function ($property) {
return $property['status'];
}, $properties));
}
}