domain_access.module 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  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. use Drupal\Core\Form\FormStateInterface;
  14. /**
  15. * The name of the node access control field.
  16. */
  17. const DOMAIN_ACCESS_FIELD = 'field_domain_access';
  18. /**
  19. * The name of the all affiliates field.
  20. */
  21. const DOMAIN_ACCESS_ALL_FIELD = 'field_domain_all_affiliates';
  22. /**
  23. * Implements hook_node_grants().
  24. */
  25. function domain_access_node_grants(AccountInterface $account, $op) {
  26. $grants = array();
  27. /** @var \Drupal\domain\Entity\Domain $active */
  28. $active = \Drupal::service('domain.negotiator')->getActiveDomain();
  29. if (empty($active)) {
  30. $active = \Drupal::service('entity_type.manager')->getStorage('domain')->loadDefaultDomain();
  31. }
  32. // No domains means no permissions.
  33. if (empty($active)) {
  34. return $grants;
  35. }
  36. $id = $active->getDomainId();
  37. // Advanced grants for edit/delete require permissions.
  38. /** @var \Drupal\user\UserInterface $user */
  39. $user = \Drupal::entityTypeManager()->getStorage('user')->load($account->id());
  40. $user_domains = \Drupal::service('domain_access.manager')->getAccessValues($user);
  41. // Grants for view are simple. Use the active domain and all affiliates.
  42. // Note that "X to any domain" is a global permission designed for admins.
  43. if ($op == 'view') {
  44. $grants['domain_id'][] = $id;
  45. $grants['domain_site'][] = 0;
  46. if ($user->hasPermission('view unpublished domain content')) {
  47. if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) {
  48. $grants['domain_unpublished'][] = $id;
  49. }
  50. }
  51. }
  52. elseif ($op == 'update' && $user->hasPermission('edit domain content')) {
  53. if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) {
  54. $grants['domain_id'][] = $id;
  55. }
  56. }
  57. elseif ($op == 'delete' && $user->hasPermission('delete domain content')) {
  58. if ($user->hasPermission('publish to any domain') || in_array($id, $user_domains) || !empty($user->get(DOMAIN_ACCESS_ALL_FIELD)->value)) {
  59. $grants['domain_id'][] = $id;
  60. }
  61. }
  62. return $grants;
  63. }
  64. /**
  65. * Implements hook_node_access_records().
  66. */
  67. function domain_access_node_access_records(NodeInterface $node) {
  68. $grants = array();
  69. // Create grants for each translation of the node. See the report at
  70. // https://www.drupal.org/node/2825419 for the logic here. Note that right
  71. // now, grants may not be the same for all languages.
  72. $translations = $node->getTranslationLanguages();
  73. foreach ($translations as $langcode => $language) {
  74. $translation = $node->getTranslation($langcode);
  75. // If there are no domains set, use the current one.
  76. $domains = \Drupal::service('domain_access.manager')->getAccessValues($translation);
  77. /** @var \Drupal\domain\DomainInterface $active */
  78. if (empty($domains) && $active = \Drupal::service('domain.negotiator')->getActiveDomain()) {
  79. $domains[$active->id()] = $active->getDomainId();
  80. }
  81. foreach ($domains as $id => $domainId) {
  82. /** @var \Drupal\domain\DomainInterface $domain */
  83. if ($domain = \Drupal::service('entity_type.manager')->getStorage('domain')->load($id)) {
  84. $grants[] = array(
  85. 'realm' => ($translation->isPublished()) ? 'domain_id' : 'domain_unpublished',
  86. 'gid' => $domain->getDomainId(),
  87. 'grant_view' => 1,
  88. 'grant_update' => 1,
  89. 'grant_delete' => 1,
  90. 'langcode' => $langcode,
  91. );
  92. }
  93. }
  94. // Set the domain_site grant.
  95. if (!empty($translation->get(DOMAIN_ACCESS_ALL_FIELD)->value) && $translation->isPublished()) {
  96. $grants[] = array(
  97. 'realm' => 'domain_site',
  98. 'gid' => 0,
  99. 'grant_view' => 1,
  100. 'grant_update' => 0,
  101. 'grant_delete' => 0,
  102. 'langcode' => $langcode,
  103. );
  104. }
  105. // Because of language translation, we must save a record for each language.
  106. // even if that record adds no permissions, as this one does.
  107. else {
  108. $grants[] = array(
  109. 'realm' => 'domain_site',
  110. 'gid' => 1,
  111. 'grant_view' => 0,
  112. 'grant_update' => 0,
  113. 'grant_delete' => 0,
  114. 'langcode' => $langcode,
  115. );
  116. }
  117. }
  118. return $grants;
  119. }
  120. /**
  121. * Implements hook_ENTITY_TYPE_presave().
  122. *
  123. * Fires only if Devel Generate module is present, to assign test nodes to
  124. * domains.
  125. */
  126. function domain_access_node_presave(EntityInterface $node) {
  127. domain_access_presave_generate($node);
  128. }
  129. /**
  130. * Implements hook_ENTITY_TYPE_presave().
  131. *
  132. * Fires only if Devel Generate module is present, to assign test nodes to
  133. * domains.
  134. */
  135. function domain_access_user_presave(EntityInterface $account) {
  136. domain_access_presave_generate($account);
  137. }
  138. /**
  139. * Handles presave operations for devel generate.
  140. */
  141. function domain_access_presave_generate(EntityInterface $entity) {
  142. // There is a core bug https://www.drupal.org/node/2609252 that causes a
  143. // fatal database errors if the boolean DOMAIN_ACCESS_ALL_FIELD is set when
  144. // a user cannot access the field. See domain_access_entity_field_access().
  145. // To overcome this issue, we cast the boolean to integer, which prevents the
  146. // failure.
  147. $value = (int) $entity->get(DOMAIN_ACCESS_ALL_FIELD)->value;
  148. $entity->set(DOMAIN_ACCESS_ALL_FIELD, $value);
  149. // Handle devel module settings.
  150. $exists = \Drupal::moduleHandler()->moduleExists('devel_generate');
  151. $values = [];
  152. if ($exists && isset($entity->devel_generate)) {
  153. // If set by the form.
  154. if (isset($entity->devel_generate['domain_access'])) {
  155. $selection = array_filter($entity->devel_generate['domain_access']);
  156. if (isset($selection['random-selection'])) {
  157. $domains = \Drupal::service('entity_type.manager')->getStorage('domain')->loadMultiple();
  158. $values[DOMAIN_ACCESS_FIELD] = array_rand($domains, ceil(rand(1, count($domains))));
  159. }
  160. else {
  161. $values[DOMAIN_ACCESS_FIELD] = array_keys($selection);
  162. }
  163. }
  164. if (isset($entity->devel_generate['domain_all'])) {
  165. $selection = $entity->devel_generate['domain_all'];
  166. if ($selection == 'random-selection') {
  167. $values[DOMAIN_ACCESS_ALL_FIELD] = rand(0, 1);
  168. }
  169. else {
  170. $values[DOMAIN_ACCESS_ALL_FIELD] = ($selection = 'yes' ? 1 : 0);
  171. }
  172. }
  173. foreach ($values as $name => $value) {
  174. $entity->set($name, $value);
  175. }
  176. }
  177. }
  178. /**
  179. * Implements hook_form_FORM_ID_alter().
  180. *
  181. * Add options for domains when using Devel Generate.
  182. */
  183. function domain_access_form_devel_generate_form_content_alter(&$form, &$form_state, $form_id) {
  184. // Add our element to the Devel generate form.
  185. $form['submit']['#weight'] = 10;
  186. $list = ['random-selection' => t('Random selection')];
  187. $list += \Drupal::service('entity_type.manager')->getStorage('domain')->loadOptionsList();
  188. $form['domain_access'] = array(
  189. '#title' => t('Domains'),
  190. '#type' => 'checkboxes',
  191. '#options' => $list,
  192. '#weight' => 2,
  193. '#multiple' => TRUE,
  194. '#size' => count($list) > 5 ? 5 : count($list),
  195. '#default_value' => ['random-selection'],
  196. '#description' => t('Sets the domains for created nodes. Random selection overrides other choices.'),
  197. );
  198. $form['domain_all'] = array(
  199. '#title' => t('Send to all affiliates'),
  200. '#type' => 'radios',
  201. '#options' => [
  202. 'random-selection' => t('Random selection'),
  203. 'yes' => t('Yes'),
  204. 'no' => t('No'),
  205. ],
  206. '#default_value' => 'random-selection',
  207. '#weight' => 3,
  208. '#description' => t('Sets visibility across all affiliates.'),
  209. );
  210. }
  211. /**
  212. * Implements hook_form_FORM_ID_alter().
  213. *
  214. * Add options for domains when using Devel Generate.
  215. */
  216. function domain_access_form_devel_generate_form_user_alter(&$form, &$form_state, $form_id) {
  217. domain_access_form_devel_generate_form_content_alter($form, $form_state, $form_id);
  218. }
  219. /**
  220. * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
  221. *
  222. * Move Domain Access fields to an advanced tab like other node settings.
  223. */
  224. function domain_access_form_node_form_alter(&$form, FormState $form_state, $form_id) {
  225. $move_enabled = \Drupal::config('domain_access.settings')->get('node_advanced_tab');
  226. if (
  227. $move_enabled && isset($form[DOMAIN_ACCESS_FIELD]) &&
  228. isset($form[DOMAIN_ACCESS_ALL_FIELD]) &&
  229. empty($form[DOMAIN_ACCESS_FIELD]['#group']) &&
  230. empty($form[DOMAIN_ACCESS_ALL_FIELD]['#group'])
  231. ) {
  232. // Move to the tabs on the entity form
  233. $form[DOMAIN_ACCESS_FIELD]['#group'] = 'domain';
  234. $form[DOMAIN_ACCESS_ALL_FIELD]['#group'] = 'domain';
  235. $form['domain'] = [
  236. '#type' => 'details',
  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. * Creates our fields for an entity bundle.
  450. *
  451. * @param string $entity_type
  452. * The entity type being created. Node and user are supported.
  453. * @param string $bundle
  454. * The bundle being created.
  455. * @param array $text
  456. * The text to use for the field. Keys are:
  457. * 'name' -- the lower-case, human-readable name of the entity.
  458. * 'label' -- the form label for the all affiliates field.
  459. * 'description' -- the help text for the all affiliates field.
  460. *
  461. * If calling this function for entities other than user or node, it is the
  462. * caller's responsibility to provide this text.
  463. *
  464. * This function is here for convenience during installation. It is not really
  465. * an API function. Modules wishing to add fields to non-node entities must
  466. * provide their own field storage. See the field storage YML sample in
  467. * tests/modules/domain_access_test for an example of field storage definitions.
  468. *
  469. * @see domain_access_node_type_insert()
  470. * @see domain_access_install()
  471. */
  472. function domain_access_confirm_fields($entity_type, $bundle, $text = array()) {
  473. // We have reports that importing config causes this function to fail.
  474. try {
  475. $text['node'] = [
  476. 'name' => 'content',
  477. 'label' => 'Send to all affiliates',
  478. 'description' => 'Make this content available on all domains.',
  479. ];
  480. $text['user'] = [
  481. 'name' => 'user',
  482. 'label' => 'Editor for all affiliates',
  483. 'description' => 'Make this user an editor on all domains.',
  484. ];
  485. $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_FIELD;
  486. $field_storage = \Drupal::entityTypeManager()->getStorage('field_config');
  487. if (!$field = $field_storage->load($id)) {
  488. $field = array(
  489. 'field_name' => DOMAIN_ACCESS_FIELD,
  490. 'entity_type' => $entity_type,
  491. 'label' => 'Domain Access',
  492. 'bundle' => $bundle,
  493. // Users should not be required to be a domain editor.
  494. 'required' => $entity_type !== 'user',
  495. 'description' => 'Select the affiliate domain(s) for this ' . $text[$entity_type]['name'],
  496. 'default_value_callback' => 'Drupal\domain_access\DomainAccessManager::getDefaultValue',
  497. 'settings' => array(
  498. 'handler' => 'default:domain',
  499. // Handler_settings are deprecated but seem to be necessary here.
  500. 'handler_settings' => [
  501. 'target_bundles' => NULL,
  502. 'sort' => ['field' => 'weight', 'direction' => 'ASC'],
  503. ],
  504. 'target_bundles' => NULL,
  505. 'sort' => ['field' => 'weight', 'direction' => 'ASC'],
  506. ),
  507. );
  508. $field_config = $field_storage->create($field);
  509. $field_config->save();
  510. }
  511. // Assign the all affiliates field.
  512. $id = $entity_type . '.' . $bundle . '.' . DOMAIN_ACCESS_ALL_FIELD;
  513. if (!$field = $field_storage->load($id)) {
  514. $field = array(
  515. 'field_name' => DOMAIN_ACCESS_ALL_FIELD,
  516. 'entity_type' => $entity_type,
  517. 'label' => $text[$entity_type]['label'],
  518. 'bundle' => $bundle,
  519. 'required' => FALSE,
  520. 'description' => $text[$entity_type]['description'],
  521. );
  522. $field_config = $field_storage->create($field);
  523. $field_config->save();
  524. }
  525. // Tell the form system how to behave. Default to radio buttons.
  526. if ($display = \Drupal::entityTypeManager()->getStorage('entity_form_display')->load($entity_type . '.' . $bundle . '.default')) {
  527. $display->setComponent(DOMAIN_ACCESS_FIELD, [
  528. 'type' => 'options_buttons',
  529. 'weight' => 40,
  530. ])->setComponent(DOMAIN_ACCESS_ALL_FIELD, [
  531. 'type' => 'boolean_checkbox',
  532. 'settings' => ['display_label' => 1],
  533. 'weight' => 41,
  534. ])->save();
  535. }
  536. }
  537. catch (Exception $e) {
  538. \Drupal::logger('domain_access')->notice('Field installation failed.');
  539. }
  540. }
  541. /**
  542. * Implements hook_views_data_alter().
  543. */
  544. function domain_access_views_data_alter(array &$data) {
  545. $table = 'node__' . DOMAIN_ACCESS_FIELD;
  546. $data[$table][DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field';
  547. $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter';
  548. $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument';
  549. // Current domain filter.
  550. $data[$table]['current_all'] = array(
  551. 'title' => t('Current domain'),
  552. 'group' => t('Domain'),
  553. 'filter' => array(
  554. 'field' => DOMAIN_ACCESS_FIELD . '_target_id',
  555. 'id' => 'domain_access_current_all_filter',
  556. 'title' => t('Available on current domain'),
  557. 'help' => t('Filters out nodes not available on current domain (published to current domain or all affiliates).'),
  558. ),
  559. );
  560. // Since domains are not stored in the database, relationships cannot be used.
  561. unset($data[$table][DOMAIN_ACCESS_FIELD]['relationship']);
  562. // Set the user data.
  563. $table = 'user__' . DOMAIN_ACCESS_FIELD;
  564. $data[$table][DOMAIN_ACCESS_FIELD]['field']['id'] = 'domain_access_field';
  565. $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['filter']['id'] = 'domain_access_filter';
  566. $data[$table][DOMAIN_ACCESS_FIELD . '_target_id']['argument']['id'] = 'domain_access_argument';
  567. // Since domains are not stored in the database, relationships cannot be used.
  568. unset($data[$table][DOMAIN_ACCESS_FIELD]['relationship']);
  569. }
  570. /**
  571. * Implements hook_ENTITY_TYPE_insert().
  572. */
  573. function domain_access_domain_insert($entity) {
  574. /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
  575. if ($entity->isSyncing()) {
  576. // Do not fire hook when config sync in progress.
  577. return;
  578. }
  579. $id = 'domain_access_add_action.' . $entity->id();
  580. $controller = \Drupal::entityTypeManager()->getStorage('action');
  581. if (!$controller->load($id)) {
  582. /** @var \Drupal\system\Entity\Action $action */
  583. $action = $controller->create(array(
  584. 'id' => $id,
  585. 'type' => 'node',
  586. 'label' => t('Add selected content to the @label domain', array('@label' => $entity->label())),
  587. 'configuration' => array(
  588. 'domain_id' => $entity->id(),
  589. ),
  590. 'plugin' => 'domain_access_add_action',
  591. ));
  592. $action->trustData()->save();
  593. }
  594. $remove_id = 'domain_access_remove_action.' . $entity->id();
  595. if (!$controller->load($remove_id)) {
  596. /** @var \Drupal\system\Entity\Action $action */
  597. $action = $controller->create(array(
  598. 'id' => $remove_id,
  599. 'type' => 'node',
  600. 'label' => t('Remove selected content from the @label domain', array('@label' => $entity->label())),
  601. 'configuration' => array(
  602. 'domain_id' => $entity->id(),
  603. ),
  604. 'plugin' => 'domain_access_remove_action',
  605. ));
  606. $action->trustData()->save();
  607. }
  608. $id = 'domain_access_add_editor_action.' . $entity->id();
  609. if (!$controller->load($id)) {
  610. /** @var \Drupal\system\Entity\Action $action */
  611. $action = $controller->create(array(
  612. 'id' => $id,
  613. 'type' => 'user',
  614. 'label' => t('Add editors to the @label domain', array('@label' => $entity->label())),
  615. 'configuration' => array(
  616. 'domain_id' => $entity->id(),
  617. ),
  618. 'plugin' => 'domain_access_add_editor_action',
  619. ));
  620. $action->trustData()->save();
  621. }
  622. $remove_id = 'domain_access_remove_editor_action.' . $entity->id();
  623. if (!$controller->load($remove_id)) {
  624. /** @var \Drupal\system\Entity\Action $action */
  625. $action = $controller->create(array(
  626. 'id' => $remove_id,
  627. 'type' => 'user',
  628. 'label' => t('Remove editors from the @label domain', array('@label' => $entity->label())),
  629. 'configuration' => array(
  630. 'domain_id' => $entity->id(),
  631. ),
  632. 'plugin' => 'domain_access_remove_editor_action',
  633. ));
  634. $action->trustData()->save();
  635. }
  636. }
  637. /**
  638. * Implements hook_ENTITY_TYPE_delete().
  639. */
  640. function domain_access_domain_delete(EntityInterface $entity) {
  641. $controller = \Drupal::entityTypeManager()->getStorage('action');
  642. $actions = $controller->loadMultiple(array(
  643. 'domain_access_add_action.' . $entity->id(),
  644. 'domain_access_remove_action.' . $entity->id(),
  645. 'domain_access_add_editor_action.' . $entity->id(),
  646. 'domain_access_remove_editor_action.' . $entity->id(),
  647. ));
  648. foreach ($actions as $action) {
  649. $action->delete();
  650. }
  651. }
  652. /**
  653. * Implements hook_form_alter().
  654. *
  655. * Find forms that contain the domain access field and allow those to handle
  656. * default values properly. Note that here we just care if the form saves an
  657. * entity. We then pass that entity to a helper function.
  658. *
  659. * @see domain_access_default_form_values().
  660. */
  661. function domain_access_form_alter(&$form, &$form_state, $form_id) {
  662. if ($object = $form_state->getFormObject() && !empty($object) && is_callable([$object, 'getEntity']) && $entity = $object->getEntity()) {
  663. domain_access_default_form_values($form, $form_state, $entity);
  664. }
  665. }
  666. /**
  667. * Defines default values for domain access field.
  668. *
  669. * This function is a workaround for a core bug. When the domain access field
  670. * is not accessible to some users, the existing values are not preserved.
  671. *
  672. * @see domain_access_entity_field_access().
  673. */
  674. function domain_access_default_form_values(&$form, &$form_state, $entity) {
  675. // Set domain access default value when the user does not have access
  676. // to edit the field. This seems to work fine for all affiliates, which
  677. // suggests a core bug in entity reference handling.
  678. if (!$entity->isNew() &&
  679. isset($form['field_domain_access']) &&
  680. !$form['field_domain_access']['#access'] &&
  681. empty($form['field_domain_access']['widget']['#default_value'])
  682. ) {
  683. // Set the default values correctly.
  684. $values = \Drupal::service('domain_access.manager')->getAccessValues($entity);
  685. $form['field_domain_access']['widget']['#default_value'] = array_keys($values);
  686. }
  687. }