domain_access.module 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. <?php
  2. /**
  3. * @file
  4. * Domain-based access control for content.
  5. */
  6. use Drupal\Core\Field\FieldDefinitionInterface;
  7. use Drupal\Core\Field\FieldItemListInterface;
  8. use Drupal\node\NodeInterface;
  9. use Drupal\Core\Access\AccessResult;
  10. use Drupal\Core\Entity\EntityInterface;
  11. use Drupal\Core\Session\AccountInterface;
  12. use Drupal\Core\Form\FormState;
  13. /**
  14. * The name of the node access control field.
  15. */
  16. const DOMAIN_ACCESS_FIELD = 'field_domain_access';
  17. /**
  18. * The name of the all affiliates field.
  19. */
  20. const DOMAIN_ACCESS_ALL_FIELD = 'field_domain_all_affiliates';
  21. /**
  22. * Implements hook_node_grants().
  23. */
  24. function domain_access_node_grants(AccountInterface $account, $op) {
  25. $grants = [];
  26. /** @var \Drupal\domain\Entity\Domain $active */
  27. $active = \Drupal::service('domain.negotiator')->getActiveDomain();
  28. if (empty($active)) {
  29. $active = \Drupal::entityTypeManager()->getStorage('domain')->loadDefaultDomain();
  30. }
  31. // No domains means no permissions.
  32. if (empty($active)) {
  33. return $grants;
  34. }
  35. $id = $active->getDomainId();
  36. // Advanced grants for edit/delete require permissions.
  37. /** @var \Drupal\user\UserInterface $user */
  38. $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id());
  39. $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($user);
  40. // Grants for view are simple. Use the active domain and all affiliates.
  41. // Note that "X to any domain" is a global permission designed for admins.
  42. if ($op == 'view') {
  43. $grants['domain_id'][] = $id;
  44. $grants['domain_site'][] = 0;
  45. if ($user->hasPermission('view unpublished domain content')) {
  46. if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) {
  47. $grants['domain_unpublished'][] = $id;
  48. }
  49. }
  50. }
  51. elseif ($op == 'update' && $user->hasPermission('edit domain content')) {
  52. if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) {
  53. $grants['domain_id'][] = $id;
  54. }
  55. }
  56. elseif ($op == 'delete' && $user->hasPermission('delete domain content')) {
  57. if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) {
  58. $grants['domain_id'][] = $id;
  59. }
  60. }
  61. return $grants;
  62. }
  63. /**
  64. * Implements hook_node_access_records().
  65. */
  66. function domain_access_node_access_records(NodeInterface $node) {
  67. $grants = [];
  68. // Create grants for each translation of the node. See the report at
  69. // https://www.drupal.org/node/2825419 for the logic here. Note that right
  70. // now, grants may not be the same for all languages.
  71. $translations = $node->getTranslationLanguages();
  72. foreach ($translations as $langcode => $language) {
  73. $translation = $node->getTranslation($langcode);
  74. // If there are no domains set, use the current one.
  75. $domains = \Drupal::service('domain_access.manager')->getAccessValues($translation);
  76. /** @var \Drupal\domain\DomainInterface $active */
  77. if (empty($domains) && $active = \Drupal::service('domain.negotiator')->getActiveDomain()) {
  78. $domains[$active->id()] = $active->getDomainId();
  79. }
  80. foreach ($domains as $id => $domainId) {
  81. /** @var \Drupal\domain\DomainInterface $domain */
  82. if ($domain = \Drupal::entityTypeManager()->getStorage('domain')->load($id)) {
  83. $grants[] = [
  84. 'realm' => ($translation->isPublished()) ? 'domain_id' : 'domain_unpublished',
  85. 'gid' => $domain->getDomainId(),
  86. 'grant_view' => 1,
  87. 'grant_update' => 1,
  88. 'grant_delete' => 1,
  89. 'langcode' => $langcode,
  90. ];
  91. }
  92. }
  93. // Set the domain_site grant.
  94. if (!empty($translation->get(DOMAIN_ACCESS_ALL_FIELD)->value) && $translation->isPublished()) {
  95. $grants[] = [
  96. 'realm' => 'domain_site',
  97. 'gid' => 0,
  98. 'grant_view' => 1,
  99. 'grant_update' => 0,
  100. 'grant_delete' => 0,
  101. 'langcode' => $langcode,
  102. ];
  103. }
  104. // Because of language translation, we must save a record for each language.
  105. // even if that record adds no permissions, as this one does.
  106. else {
  107. $grants[] = [
  108. 'realm' => 'domain_site',
  109. 'gid' => 1,
  110. 'grant_view' => 0,
  111. 'grant_update' => 0,
  112. 'grant_delete' => 0,
  113. 'langcode' => $langcode,
  114. ];
  115. }
  116. }
  117. return $grants;
  118. }
  119. /**
  120. * Implements hook_ENTITY_TYPE_presave().
  121. *
  122. * Fires only if Devel Generate module is present, to assign test nodes to
  123. * domains.
  124. */
  125. function domain_access_node_presave(EntityInterface $node) {
  126. domain_access_presave_generate($node);
  127. }
  128. /**
  129. * Implements hook_ENTITY_TYPE_presave().
  130. *
  131. * Fires only if Devel Generate module is present, to assign test nodes to
  132. * domains.
  133. */
  134. function domain_access_user_presave(EntityInterface $account) {
  135. domain_access_presave_generate($account);
  136. }
  137. /**
  138. * Handles presave operations for devel generate.
  139. */
  140. function domain_access_presave_generate(EntityInterface $entity) {
  141. // There is a core bug https://www.drupal.org/node/2609252 that causes a
  142. // fatal database errors if the boolean DOMAIN_ACCESS_ALL_FIELD is set when
  143. // a user cannot access the field. See domain_access_entity_field_access().
  144. // To overcome this issue, we cast the boolean to integer, which prevents the
  145. // failure.
  146. $value = (int) $entity->get(DOMAIN_ACCESS_ALL_FIELD)->value;
  147. $entity->set(DOMAIN_ACCESS_ALL_FIELD, $value);
  148. // Handle devel module settings.
  149. $exists = \Drupal::moduleHandler()->moduleExists('devel_generate');
  150. $values = [];
  151. if ($exists && isset($entity->devel_generate)) {
  152. // If set by the form.
  153. if (isset($entity->devel_generate['domain_access'])) {
  154. $selection = array_filter($entity->devel_generate['domain_access']);
  155. if (isset($selection['random-selection'])) {
  156. $domains = \Drupal::entityTypeManager()->getStorage('domain')->loadMultiple();
  157. $values[DOMAIN_ACCESS_FIELD] = array_rand($domains, ceil(rand(1, count($domains))));
  158. }
  159. else {
  160. $values[DOMAIN_ACCESS_FIELD] = array_keys($selection);
  161. }
  162. }
  163. if (isset($entity->devel_generate['domain_all'])) {
  164. $selection = $entity->devel_generate['domain_all'];
  165. if ($selection == 'random-selection') {
  166. $values[DOMAIN_ACCESS_ALL_FIELD] = rand(0, 1);
  167. }
  168. else {
  169. $values[DOMAIN_ACCESS_ALL_FIELD] = ($selection = 'yes' ? 1 : 0);
  170. }
  171. }
  172. foreach ($values as $name => $value) {
  173. $entity->set($name, $value);
  174. }
  175. }
  176. }
  177. /**
  178. * Implements hook_form_FORM_ID_alter().
  179. *
  180. * Add options for domains when using Devel Generate.
  181. */
  182. function domain_access_form_devel_generate_form_content_alter(&$form, &$form_state, $form_id) {
  183. // Add our element to the Devel generate form.
  184. $form['submit']['#weight'] = 10;
  185. $list = ['random-selection' => t('Random selection')];
  186. $list += \Drupal::entityTypeManager()->getStorage('domain')->loadOptionsList();
  187. $form['domain_access'] = [
  188. '#title' => t('Domains'),
  189. '#type' => 'checkboxes',
  190. '#options' => $list,
  191. '#weight' => 2,
  192. '#multiple' => TRUE,
  193. '#size' => count($list) > 5 ? 5 : count($list),
  194. '#default_value' => ['random-selection'],
  195. '#description' => t('Sets the domains for created nodes. Random selection overrides other choices.'),
  196. ];
  197. $form['domain_all'] = [
  198. '#title' => t('Send to all affiliates'),
  199. '#type' => 'radios',
  200. '#options' => [
  201. 'random-selection' => t('Random selection'),
  202. 'yes' => t('Yes'),
  203. 'no' => t('No'),
  204. ],
  205. '#default_value' => 'random-selection',
  206. '#weight' => 3,
  207. '#description' => t('Sets visibility across all affiliates.'),
  208. ];
  209. }
  210. /**
  211. * Implements hook_form_FORM_ID_alter().
  212. *
  213. * Add options for domains when using Devel Generate.
  214. */
  215. function domain_access_form_devel_generate_form_user_alter(&$form, &$form_state, $form_id) {
  216. domain_access_form_devel_generate_form_content_alter($form, $form_state, $form_id);
  217. }
  218. /**
  219. * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
  220. *
  221. * Move Domain Access fields to an advanced tab like other node settings.
  222. */
  223. function domain_access_form_node_form_alter(&$form, FormState $form_state, $form_id) {
  224. $move_enabled = \Drupal::config('domain_access.settings')->get('node_advanced_tab');
  225. if (
  226. $move_enabled && isset($form[DOMAIN_ACCESS_FIELD]) &&
  227. isset($form[DOMAIN_ACCESS_ALL_FIELD]) &&
  228. empty($form[DOMAIN_ACCESS_FIELD]['#group']) &&
  229. empty($form[DOMAIN_ACCESS_ALL_FIELD]['#group'])
  230. ) {
  231. // Move to the tabs on the entity form.
  232. $form[DOMAIN_ACCESS_FIELD]['#group'] = 'domain';
  233. $form[DOMAIN_ACCESS_ALL_FIELD]['#group'] = 'domain';
  234. $form['domain'] = [
  235. '#type' => 'details',
  236. '#open' => (bool) \Drupal::config('domain_access.settings')->get('node_advanced_tab_open'),
  237. '#title' => t('Domain settings'),
  238. '#group' => 'advanced',
  239. '#attributes' => [
  240. 'class' => ['node-form-options'],
  241. ],
  242. '#attached' => [
  243. 'library' => ['node/drupal.node'],
  244. ],
  245. '#weight' => 100,
  246. '#optional' => TRUE,
  247. ];
  248. }
  249. // Add the options hidden from the user silently to the form.
  250. $manager = \Drupal::service('domain.element_manager');
  251. $form = $manager->setFormOptions($form, $form_state, DOMAIN_ACCESS_FIELD);
  252. }
  253. /**
  254. * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\user\UserForm.
  255. *
  256. * Handle settings that the user cannot access.
  257. */
  258. function domain_access_form_user_form_alter(&$form, &$form_state, $form_id) {
  259. // Add the options hidden from the user silently to the form.
  260. $manager = \Drupal::service('domain.element_manager');
  261. $form = $manager->setFormOptions($form, $form_state, DOMAIN_ACCESS_FIELD);
  262. }
  263. /**
  264. * Implements hook_domain_references_alter().
  265. */
  266. function domain_access_domain_references_alter($query, $account, $context) {
  267. // Restrict domains by editorial assignment.
  268. if ($context['field_type'] != 'editor') {
  269. return;
  270. }
  271. switch ($context['entity_type']) {
  272. case 'node':
  273. if ($account->hasPermission('publish to any domain')) {
  274. break;
  275. }
  276. elseif ($account->hasPermission('publish to any assigned domain')) {
  277. if (!empty($account->get(DOMAIN_ACCESS_ALL_FIELD)->value)) {
  278. break;
  279. }
  280. $allowed = \Drupal::service('domain_access.manager')->getAccessValues($account);
  281. $query->condition('id', array_keys($allowed), 'IN');
  282. }
  283. else {
  284. // Remove all options.
  285. $query->condition('id', '-no-possible-match-');
  286. }
  287. break;
  288. case 'user':
  289. if ($account->hasPermission('assign editors to any domain')) {
  290. // Do nothing.
  291. }
  292. elseif ($account->hasPermission('assign domain editors')) {
  293. if (!empty($account->get(DOMAIN_ACCESS_ALL_FIELD)->value)) {
  294. break;
  295. }
  296. $allowed = \Drupal::service('domain_access.manager')->getAccessValues($account);
  297. $query->condition('id', array_keys($allowed), 'IN');
  298. }
  299. else {
  300. // Remove all options.
  301. $query->condition('id', '-no-possible-match-');
  302. }
  303. break;
  304. default:
  305. // No action taken.
  306. break;
  307. }
  308. }
  309. /**
  310. * Implements hook_node_access().
  311. */
  312. function domain_access_node_access(NodeInterface $node, $op, AccountInterface $account) {
  313. static $active_domain;
  314. if (!isset($active_domain)) {
  315. // Ensure that the loader has run. In some tests, the kernel event has not.
  316. $active = \Drupal::service('domain.negotiator')->getActiveDomain();
  317. if (empty($active)) {
  318. $active = \Drupal::service('domain.negotiator')->getActiveDomain(TRUE);
  319. }
  320. $active_domain = $active;
  321. }
  322. // Check to see that we have a valid active domain.
  323. // Without one, we cannot assert an opinion about access.
  324. if (!$active_domain || empty($active_domain->getDomainId())) {
  325. return AccessResult::neutral();
  326. }
  327. $type = $node->bundle();
  328. $manager = \Drupal::service('domain_access.manager');
  329. $allowed = FALSE;
  330. // In order to access update or delete, the user must be able to View.
  331. if ($op == 'view' && $manager->checkEntityAccess($node, $account)) {
  332. /** @var \Drupal\user\UserInterface $user */
  333. if ($node->isPublished()) {
  334. $allowed = TRUE;
  335. }
  336. elseif ($account->hasPermission('view unpublished domain content')) {
  337. $allowed = TRUE;
  338. }
  339. }
  340. if ($op == 'update') {
  341. if ($account->hasPermission('update ' . $type . ' content on assigned domains') && $manager->checkEntityAccess($node, $account)) {
  342. $allowed = TRUE;
  343. }
  344. elseif ($account->hasPermission('edit domain content') && $manager->checkEntityAccess($node, $account)) {
  345. $allowed = TRUE;
  346. }
  347. }
  348. if ($op == 'delete') {
  349. if ($account->hasPermission('delete ' . $type . ' content on assigned domains') && $manager->checkEntityAccess($node, $account)) {
  350. $allowed = TRUE;
  351. }
  352. elseif ($account->hasPermission('delete domain content') && $manager->checkEntityAccess($node, $account)) {
  353. $allowed = TRUE;
  354. }
  355. }
  356. if ($allowed) {
  357. return AccessResult::allowed()
  358. ->cachePerPermissions()
  359. ->cachePerUser()
  360. ->addCacheableDependency($node);
  361. }
  362. // No opinion.
  363. return AccessResult::neutral();
  364. }
  365. /**
  366. * Implements hook_node_create_access().
  367. *
  368. * @link https://www.drupal.org/node/2348203
  369. */
  370. function domain_access_node_create_access(AccountInterface $account, $context, $entity_bundle) {
  371. // Check to see that we have a valid active domain.
  372. // Without one, we cannot assert an opinion about access.
  373. /** @var \Drupal\domain\DomainInterface $active */
  374. if ($active = \Drupal::service('domain.negotiator')->getActiveDomain()) {
  375. $id = $active->getDomainId();
  376. }
  377. else {
  378. return AccessResult::neutral();
  379. }
  380. // Load the full user record.
  381. $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id());
  382. $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($user);
  383. if (($account->hasPermission('create ' . $entity_bundle . ' content on assigned domains')
  384. || $account->hasPermission('create domain content'))
  385. && in_array($id, $user_domains)) {
  386. // Note the cache context here!
  387. return AccessResult::allowed()->addCacheContexts(['user.permissions', 'url.site']);
  388. }
  389. // No opinion.
  390. return AccessResult::neutral();
  391. }
  392. /**
  393. * Implements hook_entity_field_access().
  394. */
  395. function domain_access_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
  396. // Hide the domain access fields from the entity add/edit forms
  397. // when the user cannot access them.
  398. if ($operation != 'edit') {
  399. return AccessResult::neutral();
  400. }
  401. // The entity the field is attached to.
  402. $entity = $items->getEntity();
  403. if ($field_definition->getName() == DOMAIN_ACCESS_FIELD) {
  404. if ($entity instanceof AccountInterface) {
  405. $access = AccessResult::allowedIfHasPermissions($account, [
  406. 'assign domain editors',
  407. 'assign editors to any domain',
  408. ], 'OR');
  409. }
  410. elseif ($entity instanceof NodeInterface) {
  411. // Treat any other entity as content.
  412. $access = AccessResult::allowedIfHasPermissions($account, [
  413. 'publish to any domain',
  414. 'publish to any assigned domain',
  415. ], 'OR');
  416. }
  417. // allowedIfHasPermissions returns allowed() or neutral().
  418. // In this case, we want it to be forbidden,
  419. // if user doesn't have the permissions above.
  420. if (isset($access) && !$access->isAllowed()) {
  421. return AccessResult::forbidden();
  422. }
  423. }
  424. // Check permissions on the All Affiliates field.
  425. elseif ($field_definition->getName() == DOMAIN_ACCESS_ALL_FIELD) {
  426. if ($entity instanceof AccountInterface) {
  427. return AccessResult::forbiddenIf(!$account->hasPermission('assign editors to any domain'));
  428. }
  429. elseif ($entity instanceof NodeInterface) {
  430. // Treat any other entity as content.
  431. return AccessResult::forbiddenIf(!$account->hasPermission('publish to any domain'));
  432. }
  433. }
  434. return AccessResult::neutral();
  435. }
  436. /**
  437. * Implements hook_ENTITY_TYPE_insert().
  438. *
  439. * Creates our fields when new node types are created.
  440. */
  441. function domain_access_node_type_insert(EntityInterface $entity) {
  442. /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
  443. if (!$entity->isSyncing()) {
  444. // Do not fire hook when config sync in progress.
  445. domain_access_confirm_fields('node', $entity->id());
  446. }
  447. }
  448. /**
  449. * Implements hook_ENTITY_TYPE_insert().
  450. *
  451. * In some cases, form display modes are not set when the node type is created.
  452. * be sure to update our field definitions on creation of form_display for
  453. * node types.
  454. */
  455. function domain_access_entity_form_display_insert(EntityInterface $entity) {
  456. if (!$entity->isSyncing() && $entity->getTargetEntityTypeId() == 'node' && $bundle = $entity->getTargetBundle()) {
  457. domain_access_confirm_fields('node', $bundle);
  458. }
  459. }
  460. /**
  461. * Creates our fields for an entity bundle.
  462. *
  463. * @param string $entity_type
  464. * The entity type being created. Node and user are supported.
  465. * @param string $bundle
  466. * The bundle being created.
  467. * @param array $text
  468. * The text to use for the field. Keys are:
  469. * 'name' -- the lower-case, human-readable name of the entity.
  470. * 'label' -- the form label for the all affiliates field.
  471. * 'description' -- the help text for the all affiliates field.
  472. *
  473. * If calling this function for entities other than user or node, it is the
  474. * caller's responsibility to provide this text.
  475. *
  476. * This function is here for convenience during installation. It is not really
  477. * an API function. Modules wishing to add fields to non-node entities must
  478. * provide their own field storage. See the field storage YML sample in
  479. * tests/modules/domain_access_test for an example of field storage
  480. * definitions.
  481. *
  482. * @see domain_access_node_type_insert()
  483. * @see domain_access_install()
  484. */
  485. function domain_access_confirm_fields($entity_type, $bundle, array $text = []) {
  486. // We have reports that importing config causes this function to fail.
  487. try {
  488. $text['node'] = [
  489. 'name' => 'content',
  490. 'label' => 'Send to all affiliates',
  491. 'description' => 'Make this content available on all domains.',
  492. ];
  493. $text['user'] = [
  494. 'name' => 'user',
  495. 'label' => 'Editor for all affiliates',
  496. 'description' => 'Make this user an editor on all domains.',
  497. ];
  498. $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_FIELD;
  499. $field_storage = \Drupal::entityTypeManager()->getStorage('field_config');
  500. if (!$field = $field_storage->load($id)) {
  501. $field = [
  502. 'field_name' => DOMAIN_ACCESS_FIELD,
  503. 'entity_type' => $entity_type,
  504. 'label' => 'Domain Access',
  505. 'bundle' => $bundle,
  506. // Users should not be required to be a domain editor.
  507. 'required' => $entity_type !== 'user',
  508. 'description' => 'Select the affiliate domain(s) for this ' . $text[$entity_type]['name'],
  509. 'default_value_callback' => 'Drupal\domain_access\DomainAccessManager::getDefaultValue',
  510. 'settings' => [
  511. 'handler' => 'default:domain',
  512. // Handler_settings are deprecated but seem to be necessary here.
  513. 'handler_settings' => [
  514. 'target_bundles' => NULL,
  515. 'sort' => ['field' => 'weight', 'direction' => 'ASC'],
  516. ],
  517. 'target_bundles' => NULL,
  518. 'sort' => ['field' => 'weight', 'direction' => 'ASC'],
  519. ],
  520. ];
  521. $field_config = $field_storage->create($field);
  522. $field_config->save();
  523. }
  524. // Assign the all affiliates field.
  525. $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_ALL_FIELD;
  526. if (!$field = $field_storage->load($id)) {
  527. $field = [
  528. 'field_name' => DOMAIN_ACCESS_ALL_FIELD,
  529. 'entity_type' => $entity_type,
  530. 'label' => $text[$entity_type]['label'],
  531. 'bundle' => $bundle,
  532. 'required' => FALSE,
  533. 'description' => $text[$entity_type]['description'],
  534. ];
  535. $field_config = $field_storage->create($field);
  536. $field_config->save();
  537. }
  538. // Tell the form system how to behave. Default to radio buttons.
  539. if ($display = \Drupal::entityTypeManager()->getStorage('entity_form_display')->load($entity_type . '.' . $bundle . '.default')) {
  540. $display->setComponent(DOMAIN_ACCESS_FIELD, [
  541. 'type' => 'options_buttons',
  542. 'weight' => 40,
  543. ])->setComponent(DOMAIN_ACCESS_ALL_FIELD, [
  544. 'type' => 'boolean_checkbox',
  545. 'settings' => ['display_label' => 1],
  546. 'weight' => 41,
  547. ])->save();
  548. }
  549. }
  550. catch (Exception $e) {
  551. \Drupal::logger('domain_access')->notice('Field installation failed.');
  552. }
  553. }
  554. /**
  555. * Implements hook_views_data_alter().
  556. */
  557. function domain_access_views_data_alter(array &$data) {
  558. $table = 'node__' . DOMAIN_ACCESS_FIELD;
  559. $data[$table][DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field';
  560. $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter';
  561. $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument';
  562. // Current domain filter.
  563. $data[$table]['current_all'] = [
  564. 'title' => t('Current domain'),
  565. 'group' => t('Domain'),
  566. 'filter' => [
  567. 'field' => DOMAIN_ACCESS_FIELD . '_target_id',
  568. 'id' => 'domain_access_current_all_filter',
  569. 'title' => t('Available on current domain'),
  570. 'help' => t('Filters out nodes not available on current domain (published to current domain or all affiliates).'),
  571. ],
  572. ];
  573. // Since domains are not stored in the database, relationships cannot be used.
  574. unset($data[$table][DOMAIN_ACCESS_FIELD]['relationship']);
  575. // Set the user data.
  576. $table = 'user__' . DOMAIN_ACCESS_FIELD;
  577. $data[$table][DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field';
  578. $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter';
  579. $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument';
  580. // Since domains are not stored in the database, relationships cannot be used.
  581. unset($data[$table][DOMAIN_ACCESS_FIELD]['relationship']);
  582. }
  583. /**
  584. * Implements hook_ENTITY_TYPE_insert().
  585. */
  586. function domain_access_domain_insert($entity) {
  587. /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
  588. if ($entity->isSyncing()) {
  589. // Do not fire hook when config sync in progress.
  590. return;
  591. }
  592. $id = 'domain_access_add_action.' . $entity->id();
  593. $controller = \Drupal::entityTypeManager()->getStorage('action');
  594. if (!$controller->load($id)) {
  595. /** @var \Drupal\system\Entity\Action $action */
  596. $action = $controller->create([
  597. 'id' => $id,
  598. 'type' => 'node',
  599. 'label' => t('Add selected content to the @label domain', ['@label' => $entity->label()]),
  600. 'configuration' => [
  601. 'domain_id' => $entity->id(),
  602. ],
  603. 'plugin' => 'domain_access_add_action',
  604. ]);
  605. $action->trustData()->save();
  606. }
  607. $remove_id = 'domain_access_remove_action.' . $entity->id();
  608. if (!$controller->load($remove_id)) {
  609. /** @var \Drupal\system\Entity\Action $action */
  610. $action = $controller->create([
  611. 'id' => $remove_id,
  612. 'type' => 'node',
  613. 'label' => t('Remove selected content from the @label domain', ['@label' => $entity->label()]),
  614. 'configuration' => [
  615. 'domain_id' => $entity->id(),
  616. ],
  617. 'plugin' => 'domain_access_remove_action',
  618. ]);
  619. $action->trustData()->save();
  620. }
  621. $id = 'domain_access_add_editor_action.' . $entity->id();
  622. if (!$controller->load($id)) {
  623. /** @var \Drupal\system\Entity\Action $action */
  624. $action = $controller->create([
  625. 'id' => $id,
  626. 'type' => 'user',
  627. 'label' => t('Add editors to the @label domain', ['@label' => $entity->label()]),
  628. 'configuration' => [
  629. 'domain_id' => $entity->id(),
  630. ],
  631. 'plugin' => 'domain_access_add_editor_action',
  632. ]);
  633. $action->trustData()->save();
  634. }
  635. $remove_id = 'domain_access_remove_editor_action.' . $entity->id();
  636. if (!$controller->load($remove_id)) {
  637. /** @var \Drupal\system\Entity\Action $action */
  638. $action = $controller->create([
  639. 'id' => $remove_id,
  640. 'type' => 'user',
  641. 'label' => t('Remove editors from the @label domain', ['@label' => $entity->label()]),
  642. 'configuration' => [
  643. 'domain_id' => $entity->id(),
  644. ],
  645. 'plugin' => 'domain_access_remove_editor_action',
  646. ]);
  647. $action->trustData()->save();
  648. }
  649. }
  650. /**
  651. * Implements hook_ENTITY_TYPE_delete().
  652. */
  653. function domain_access_domain_delete(EntityInterface $entity) {
  654. $controller = \Drupal::entityTypeManager()->getStorage('action');
  655. $actions = $controller->loadMultiple([
  656. 'domain_access_add_action.' . $entity->id(),
  657. 'domain_access_remove_action.' . $entity->id(),
  658. 'domain_access_add_editor_action.' . $entity->id(),
  659. 'domain_access_remove_editor_action.' . $entity->id(),
  660. ]);
  661. foreach ($actions as $action) {
  662. $action->delete();
  663. }
  664. }
  665. /**
  666. * Implements hook_form_alter().
  667. *
  668. * Find forms that contain the domain access field and allow those to handle
  669. * default values properly. Note that here we just care if the form saves an
  670. * entity. We then pass that entity to a helper function.
  671. *
  672. * @see domain_access_default_form_values()
  673. */
  674. function domain_access_form_alter(&$form, &$form_state, $form_id) {
  675. if ($object = $form_state->getFormObject() && !empty($object) && is_callable([$object, 'getEntity']) && $entity = $object->getEntity()) {
  676. domain_access_default_form_values($form, $form_state, $entity);
  677. }
  678. }
  679. /**
  680. * Defines default values for domain access field.
  681. *
  682. * This function is a workaround for a core bug. When the domain access field
  683. * is not accessible to some users, the existing values are not preserved.
  684. *
  685. * @see domain_access_entity_field_access()
  686. */
  687. function domain_access_default_form_values(&$form, &$form_state, $entity) {
  688. // Set domain access default value when the user does not have access
  689. // to edit the field. This seems to work fine for all affiliates, which
  690. // suggests a core bug in entity reference handling.
  691. if (!$entity->isNew() &&
  692. isset($form['field_domain_access']) &&
  693. !$form['field_domain_access']['#access'] &&
  694. empty($form['field_domain_access']['widget']['#default_value'])
  695. ) {
  696. // Set the default values correctly.
  697. $values = \Drupal::service('domain_access.manager')->getAccessValues($entity);
  698. $form['field_domain_access']['widget']['#default_value'] = array_keys($values);
  699. }
  700. }