Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
125 / 125
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
RelationshipController
100.00% covered (success)
100.00%
125 / 125
100.00% covered (success)
100.00%
8 / 8
19
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 build
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
1
 buildHeader
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getActiveRelationships
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getInactiveRelationships
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 doRows
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
4
 addPage
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\crm\Controller;
6
7use Drupal\Core\Controller\ControllerBase;
8use Drupal\Core\Entity\EntityTypeManagerInterface;
9use Drupal\Core\Link;
10use Drupal\crm\Entity\ContactInterface;
11use Drupal\crm\Service\RelationshipService;
12use Symfony\Component\DependencyInjection\ContainerInterface;
13
14/**
15 * Returns responses for Crm routes.
16 */
17class RelationshipController extends ControllerBase {
18
19  /**
20   * The entity type manager.
21   *
22   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
23   */
24  protected $entityTypeManager;
25
26  /**
27   * The relationship service.
28   *
29   * @var \Drupal\crm\Service\RelationshipService
30   */
31  protected $relationshipService;
32
33  /**
34   * The controller constructor.
35   *
36   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
37   *   The entity type manager.
38   * @param \Drupal\crm\Service\RelationshipService $relationship_service
39   *   The relationship service.
40   */
41  public function __construct(EntityTypeManagerInterface $entity_type_manager, RelationshipService $relationship_service) {
42    $this->entityTypeManager = $entity_type_manager;
43    $this->relationshipService = $relationship_service;
44  }
45
46  /**
47   * {@inheritdoc}
48   */
49  public static function create(ContainerInterface $container) {
50    return new self(
51      $container->get('entity_type.manager'),
52      $container->get('crm.relationship')
53    );
54  }
55
56  /**
57   * Builds the response.
58   *
59   * @param \Drupal\crm\ContactInterface $crm_contact
60   *   The contact entity.
61   *
62   * @return array
63   *   A render array for the relationship list.
64   */
65  public function build(ContactInterface $crm_contact): array {
66    $contact_id = $crm_contact->id();
67
68    $build['active'] = [
69      '#type' => 'table',
70      '#header' => $this->buildHeader(),
71      '#title' => $this->t('Active Relationships'),
72      '#rows' => $this->getActiveRelationships($contact_id),
73      '#empty' => $this->t('There are no active relationships.'),
74      '#cache' => [],
75    ];
76
77    $build['inactive'] = [
78      '#type' => 'table',
79      '#header' => $this->buildHeader(),
80      '#title' => $this->t('Inactive Relationships'),
81      '#rows' => $this->getInactiveRelationships($contact_id),
82      '#empty' => $this->t('There are no inactive relationships.'),
83      '#cache' => [],
84    ];
85
86    $build['#cache']['max-age'] = 0;
87
88    // Add contextual links.
89    $build['#contextual_links']['crm_contact_relationship'] = [
90      'route_parameters' => ['crm_contact' => $contact_id],
91    ];
92
93    return $build;
94  }
95
96  /**
97   * Builds the header.
98   *
99   * @return array
100   *   An array of header cells.
101   */
102  protected function buildHeader() {
103    $header['type'] = $this->t('Relationship');
104    $header['contact'] = $this->t('Contact');
105    $header['start_date'] = $this->t('Start Date');
106    $header['end_date'] = $this->t('End Date');
107    $header['address'] = $this->t('Address');
108    $header['email'] = $this->t('Email');
109    $header['phone'] = $this->t('Phone');
110
111    $header['operations'] = $this->t('Operations');
112    return $header;
113  }
114
115  /**
116   * Gets the active relationships.
117   *
118   * @param int $crm_contact
119   *   The CRM contact ID.
120   *
121   * @return array
122   *   An array of active relationships.
123   */
124  protected function getActiveRelationships($crm_contact) {
125
126    $storage = $this->entityTypeManager->getStorage('crm_relationship');
127    $query = $storage->getQuery();
128    $query->condition('status', 1)
129      ->sort('start_date', 'DESC');
130    $or = $query->orConditionGroup()
131      ->condition('contacts', $crm_contact);
132    $query->condition($or);
133    $result = $query->accessCheck(FALSE)->execute();
134    $relationships = $storage->loadMultiple($result);
135
136    return $this->doRows($relationships, $crm_contact);
137  }
138
139  /**
140   * Gets the inactive relationships.
141   *
142   * @param int $crm_contact
143   *   The CRM contact ID.
144   *
145   * @return array
146   *   An array of inactive relationships.
147   */
148  protected function getInactiveRelationships($crm_contact) {
149
150    $storage = $this->entityTypeManager->getStorage('crm_relationship');
151    $query = $storage->getQuery();
152    $query->condition('status', 0)
153      ->sort('end_date', 'DESC');
154    $or = $query->orConditionGroup()
155      ->condition('contacts', $crm_contact);
156    $query->condition($or);
157    $result = $query->accessCheck(FALSE)->execute();
158    $relationships = $storage->loadMultiple($result);
159
160    return $this->doRows($relationships, $crm_contact);
161  }
162
163  /**
164   * Builds the rows.
165   *
166   * @param array $relationships
167   *   An array of relationships.
168   * @param int $crm_contact
169   *   The CRM contact ID.
170   *
171   * @return array
172   *   An array of rows.
173   */
174  protected function doRows($relationships, $crm_contact) {
175    $rows = [];
176
177    foreach ($relationships as $relationship) {
178      $contacts = $relationship->get('contacts')->referencedEntities();
179      $is_a = $contacts[0]->id() == $crm_contact;
180      $contact = $is_a ? $contacts[1] : $contacts[0];
181      $contact_label = $contact->toLink()->toString();
182      $relationship_label = $is_a ? $relationship->bundle->entity->get('label_b') : $relationship->bundle->entity->get('label_a');
183      $rows[] = [
184        'type' => $relationship_label,
185        'contact' => $contact_label,
186        'start_date' => $relationship->get('start_date')->value,
187        'end_date' => $relationship->get('end_date')->value,
188        'address' => $contact->get('addresses')->primary()?->entity->label(),
189        'email' => $contact->get('emails')->primary()?->entity->label(),
190        'phone' => $contact->get('telephones')->primary()?->entity->label(),
191        'operations' => [
192          'data' => [
193            '#type' => 'operations',
194            '#links' => [
195              'edit' => [
196                'title' => $this->t('Edit'),
197                'url' => $relationship->toUrl('edit-form'),
198              ],
199              'delete' => [
200                'title' => $this->t('Delete'),
201                'url' => $relationship->toUrl('delete-form'),
202              ],
203            ],
204          ],
205        ],
206      ];
207    }
208    return $rows;
209  }
210
211  /**
212   * Displays a page with eligible relationship types for a contact.
213   *
214   * @param \Drupal\crm\ContactInterface $crm_contact
215   *   The contact entity.
216   *
217   * @return array
218   *   A render array for the add relationship page.
219   */
220  public function addPage(ContactInterface $crm_contact): array {
221    $eligible_types = $this->relationshipService->getEligibleRelationshipTypesForContact($crm_contact);
222
223    if (empty($eligible_types)) {
224      return [
225        '#markup' => $this->t('There are no relationship types available for this contact.'),
226      ];
227    }
228
229    $build = [
230      '#theme' => 'entity_add_list',
231      '#bundles' => [],
232      '#add_bundle_message' => $this->t('There are no relationship types available for this contact.'),
233      '#cache' => [
234        'tags' => ['config:crm_relationship_type_list'],
235      ],
236    ];
237
238    foreach ($eligible_types as $type_id => $info) {
239      /** @var \Drupal\crm\RelationshipTypeInterface $type */
240      $type = $info['type'];
241      $positions = $info['positions'];
242      $is_asymmetric = (bool) $type->get('asymmetric');
243
244      // For symmetric relationships, only show one entry (use first position).
245      // For asymmetric relationships, show an entry for each eligible position.
246      $positions_to_show = $is_asymmetric ? $positions : [reset($positions)];
247
248      foreach ($positions_to_show as $position) {
249        $label = $position === 'a' ? $type->get('label_b') : $type->get('label_a');
250        if (empty($label)) {
251          $label = $type->label();
252        }
253
254        // Use a unique key for each position to allow both A and B entries.
255        $bundle_key = $is_asymmetric ? $type_id . '_' . $position : $type_id;
256
257        // Use contact_a or contact_b query parameter based on position.
258        $query_param = $position === 'a' ? 'contact_a' : 'contact_b';
259
260        $build['#bundles'][$bundle_key] = [
261          'label' => $label,
262          'description' => $type->getDescription(),
263          'add_link' => Link::createFromRoute($label, 'entity.crm_relationship.add_form', [
264            'crm_relationship_type' => $type_id,
265          ], [
266            'query' => [
267              $query_param => $crm_contact->id(),
268            ],
269          ]),
270        ];
271      }
272    }
273
274    return $build;
275  }
276
277}