DomainCommands.php 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368
  1. <?php
  2. namespace Drupal\domain\Commands;
  3. use Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface;
  4. use Consolidation\AnnotatedCommand\Events\CustomEventAwareTrait;
  5. use Consolidation\OutputFormatters\StructuredData\PropertyList;
  6. use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
  7. use Drupal\Core\Config\StorageException;
  8. use Drupal\Core\Entity\EntityStorageException;
  9. use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
  10. use Drupal\Component\Plugin\Exception\PluginException;
  11. use Drupal\Component\Plugin\Exception\PluginNotFoundException;
  12. use Drupal\Component\Utility\Html;
  13. use Drupal\domain\DomainInterface;
  14. use Drupal\domain\DomainStorageInterface;
  15. use Drush\Commands\DrushCommands;
  16. use Symfony\Component\Console\Input\InputOption;
  17. /**
  18. * Drush commands for the domain module.
  19. */
  20. class DomainCommands extends DrushCommands implements CustomEventAwareInterface {
  21. use CustomEventAwareTrait;
  22. /**
  23. * The domain entity storage service.
  24. *
  25. * @var \Drupal\domain\DomainStorageInterface $domain_storage
  26. */
  27. protected $domain_storage = NULL;
  28. /**
  29. * Local cache of entity field map, kept for performance.
  30. *
  31. * @var array
  32. */
  33. protected $entity_field_map = NULL;
  34. /**
  35. * Flag set by the --dryrun cli option. If set prevents changes from
  36. * being made by code in this class.
  37. *
  38. * @var bool
  39. */
  40. protected $is_dry_run = FALSE;
  41. /**
  42. * Static array of special-case policies for reassigning field data.
  43. *
  44. * @var string[]
  45. * */
  46. protected $reassignment_policies = ['prompt', 'default', 'ignore'];
  47. /**
  48. * List active domains for the site.
  49. *
  50. * @option inactive
  51. * Show only the domains that are inactive/disabled.
  52. * @option active
  53. * Show only the domains that are active/enabled.
  54. * @usage drush domain:list
  55. * List active domains for the site.
  56. * @usage drush domains
  57. * List active domains for the site.
  58. *
  59. * @command domain:list
  60. * @aliases domains,domain-list
  61. *
  62. * @field-labels
  63. * weight: Weight
  64. * name: Name
  65. * hostname: Hostname
  66. * response: HTTP Response
  67. * scheme: Scheme
  68. * status: Status
  69. * is_default: Default
  70. * domain_id: Domain Id
  71. * id: Machine name
  72. * @default-fields id,name,hostname,scheme,status,is_default,response
  73. *
  74. * @param array $options
  75. *
  76. * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
  77. *
  78. * @throws \Drupal\domain\Commands\DomainCommandException
  79. */
  80. public function listDomains(array $options) {
  81. // Load all domains:
  82. $domains = $this->domainStorage()->loadMultipleSorted();
  83. if (empty($domains)) {
  84. $this->logger()->warning(dt('No domains have been created. Use "drush domain:add" to create one.'));
  85. return new RowsOfFields([]);
  86. }
  87. $keys = [
  88. 'weight',
  89. 'name',
  90. 'hostname',
  91. 'response',
  92. 'scheme',
  93. 'status',
  94. 'is_default',
  95. 'domain_id',
  96. 'id',
  97. ];
  98. $rows = [];
  99. /** @var \Drupal\domain\DomainInterface $domain */
  100. foreach ($domains as $domain) {
  101. $row = [];
  102. foreach ($keys as $key) {
  103. switch($key) {
  104. case 'response':
  105. try {
  106. $v = $this->checkDomain($domain);
  107. }
  108. catch(\GuzzleHttp\Exception\TransferException $ex) {
  109. $v = dt('500 - Failed');
  110. }
  111. catch(Exception $ex) {
  112. $v = dt('500 - Exception');
  113. }
  114. if ($v >= 200 && $v <= 299) {
  115. $v = dt('200 - OK');
  116. }
  117. elseif ($v == 500) {
  118. $v = dt('500 - No server');
  119. }
  120. break;
  121. case 'status':
  122. $v = $domain->get($key);
  123. if (($options['inactive'] && $v) || ($options['active'] && !$v)) {
  124. continue 3; // switch, for, for
  125. }
  126. $v = !empty($v) ? dt('Active') : dt('Inactive');
  127. break;
  128. case 'is_default':
  129. $v = $domain->get($key);
  130. $v = !empty($v) ? dt('Default') : '';
  131. break;
  132. default:
  133. $v = $domain->get($key);
  134. break;
  135. }
  136. $row[$key] = Html::escape($v);
  137. }
  138. $rows[] = $row;
  139. }
  140. return new RowsOfFields($rows);
  141. }
  142. /**
  143. * List general information about the domains on the site.
  144. *
  145. * @usage drush domain:info
  146. *
  147. * @command domain:info
  148. * @aliases domain-info,dinf
  149. *
  150. * @return \Consolidation\OutputFormatters\StructuredData\PropertyList
  151. * @field-labels
  152. * count: All Domains
  153. * count_active: Active Domains
  154. * default_id: Default Domain ID
  155. * default_host: Default Domain hostname
  156. * scheme: Fields in Domain entity
  157. * domain_admin_entities: Domain admin entities
  158. * @list-orientation true
  159. * @format table
  160. * @throws \Drupal\domain\Commands\DomainCommandException
  161. */
  162. public function infoDomains() {
  163. $default_domain = $this->domainStorage()->loadDefaultDomain();
  164. // Load all domains:
  165. $all_domains = $this->domainStorage()->loadMultiple(NULL);
  166. $active_domains = [];
  167. foreach ($all_domains as $domain) {
  168. if ($domain->status()) {
  169. $active_domains[] = $domain;
  170. }
  171. }
  172. $keys = [
  173. 'count',
  174. 'count_active',
  175. 'default_id',
  176. 'default_host',
  177. 'scheme',
  178. ];
  179. $rows = [];
  180. foreach ($keys as $key) {
  181. $v = '';
  182. switch($key) {
  183. case 'count':
  184. $v = count($all_domains);
  185. break;
  186. case 'count_active':
  187. $v = count($active_domains);
  188. break;
  189. case 'default_id':
  190. $v = '-unset-';
  191. if ($default_domain) {
  192. $v = $default_domain->id();
  193. }
  194. break;
  195. case 'default_host':
  196. $v = '-unset-';
  197. if ($default_domain) {
  198. $v = $default_domain->getHostname();
  199. }
  200. break;
  201. case 'scheme':
  202. $v = implode(', ', array_keys($this->domainStorage()->loadSchema()));
  203. break;
  204. }
  205. $rows[$key] = $v;
  206. }
  207. // Display which entities are enabled for domain by checking for the fields.
  208. $rows['domain_admin_entities'] = $this->getFieldEntities(DOMAIN_ADMIN_FIELD);
  209. return new PropertyList($rows);
  210. }
  211. /**
  212. * Finds entities that reference a specific field.
  213. *
  214. * @param $field_name
  215. * The field name to lookup.
  216. */
  217. public function getFieldEntities($field_name) {
  218. $entity_manager = \Drupal::entityManager();
  219. $field_map = $entity_manager->getFieldMap();
  220. $domain_entities = [];
  221. foreach($field_map as $type => $fields) {
  222. if (array_key_exists($field_name, $fields)) {
  223. $domain_entities[] = $type;
  224. }
  225. }
  226. return implode(', ', $domain_entities);
  227. }
  228. /**
  229. * Add a new domain to the site.
  230. *
  231. * @param $hostname
  232. * The domain hostname to register (e.g. example.com).
  233. * @param $name
  234. * The name of the site (e.g. Domain Two).
  235. * @param array $options An associative array of optional values.
  236. *
  237. * @option inactive
  238. * Set the domain to inactive status if set.
  239. * @option scheme
  240. * Use indicated protocol for this domain, defaults to 'https'. Options:
  241. * - http: normal http (no SSL).
  242. * - https: secure https (with SSL).
  243. * - variable: match the scheme used by the request.
  244. * @option weight
  245. * Set the order (weight) of the domain.
  246. * @option is_default
  247. * Set this domain as the default domain.
  248. * @option validate
  249. * Force a check of the URL response before allowing registration.
  250. *
  251. * @usage drush domain-add example.com 'My Test Site'
  252. * @usage drush domain-add example.com 'My Test Site' --scheme=https --inactive
  253. * @usage drush domain-add example.com 'My Test Site' --weight=10
  254. * @usage drush domain-add example.com 'My Test Site' --validate
  255. *
  256. * @command domain:add
  257. * @aliases domain-add
  258. *
  259. * @return string
  260. * The entity id of the created domain.
  261. *
  262. * @throws \Drupal\domain\Commands\DomainCommandException
  263. */
  264. public function add($hostname, $name, array $options = ['weight' => null, 'scheme' => null]) {
  265. // Validate the weight arg.
  266. if (!empty($options['weight']) && !is_numeric($options['weight'])) {
  267. throw new DomainCommandException(
  268. dt('Domain weight "!weight" must be a number',
  269. ['!weight' => !empty($options['weight']) ? $options['weight'] : ''])
  270. );
  271. }
  272. // Validate the scheme arg.
  273. if (!empty($options['scheme']) &&
  274. ($options['scheme'] !== 'http' && $options['scheme'] !== 'https' && $options['scheme'] !== 'variable')
  275. ) {
  276. throw new DomainCommandException(
  277. dt('Scheme name "!scheme" not known',
  278. ['!scheme' => !empty($options['scheme']) ? $options['scheme'] : ''])
  279. );
  280. }
  281. $domains = $this->domainStorage()->loadMultipleSorted();
  282. $start_weight = count($domains) + 1;
  283. $values = [
  284. 'hostname' => $hostname,
  285. 'name' => $name,
  286. 'status' => empty($options['inactive']),
  287. 'scheme' => empty($options['scheme']) ? 'http' : $options['scheme'],
  288. 'weight' => empty($options['weight']) ? $start_weight : $options['weight'],
  289. 'is_default' => !empty($options['is_default']),
  290. 'id' => $this->domainStorage()->createMachineName($hostname),
  291. ];
  292. /** @var DomainInterface $domain */
  293. $domain = $this->domainStorage()->create($values);
  294. // Check for hostname validity. This is required.
  295. $valid = $this->validateDomain($domain);
  296. if (!empty($valid)) {
  297. throw new DomainCommandException(
  298. dt('Hostname is not valid. !errors',
  299. ['!errors' => implode(" ", $valid)])
  300. );
  301. }
  302. // Check for hostname and id uniqueness.
  303. foreach ($domains as $existing) {
  304. if ($hostname == $existing->getHostname()) {
  305. throw new DomainCommandException(
  306. dt('No domain created. Hostname is a duplicate of !hostname.',
  307. ['!hostname' => $hostname])
  308. );
  309. }
  310. if ($values['id'] == $existing->id()) {
  311. throw new DomainCommandException(
  312. dt('No domain created. Id is a duplicate of !id.',
  313. ['!id' => $existing->id()])
  314. );
  315. }
  316. }
  317. $validate_response = (bool) $options['validate'];
  318. if ($this->createDomain($domain, $validate_response)) {
  319. return dt('Created the !hostname with machine id !id.', ['!hostname' => $values['hostname'], '!id' => $values['id']]);
  320. }
  321. else {
  322. return dt('No domain created.');
  323. }
  324. }
  325. /**
  326. * Delete a domain from the site.
  327. *
  328. * Deletes the domain from the Drupal configuration and optionally reassign
  329. * content and/or profiles associated with the deleted domain to another.
  330. * The domain marked as default cannot be deleted: to achieve this goal,
  331. * mark another, possibly newly created, domain as the default domain, then
  332. * delete the old default.
  333. *
  334. * The usage example descriptions are based on starting with three domains:
  335. * - id:19476, machine: example_com, domain: example.com
  336. * - id:29389, machine: example_org, domain: example.org (default)
  337. * - id:91736, machine: example_net, domain: example.net
  338. *
  339. * @param $domain_id
  340. * The numeric id, machine name, or hostname of the domain to delete. The
  341. * value "all" is taken to mean delete all except the default domain.
  342. * @param array $options
  343. * An associative array of options whose values come from cli, aliases,
  344. * config, etc.
  345. *
  346. * @usage drush domain:delete example.com
  347. * Delete the domain example.com, assigning its content and users to
  348. * the default domain, example.org.
  349. *
  350. * @usage drush domain:delete --content-assign=ignore example.com
  351. * Delete the domain example.com, leaving its content untouched but
  352. * assigning its users to the default domain.
  353. *
  354. * @usage drush domain:delete --content-assign=example_net --users-assign=example_net
  355. * example.com Delete the domain example.com, assigning its content and
  356. * users to the example.net domain.
  357. *
  358. * @usage drush domain:delete --dryrun 19476
  359. * Show the effects of delete the domain example.com and assigning its
  360. * content and users to the default domain, example.org, but not doing so.
  361. *
  362. * @usage drush domain:delete --chatty example_net
  363. * Verbosely Delete the domain example.net and assign its content and users
  364. * to the default domain, example.org.
  365. *
  366. * @usage drush domain-delete --chatty all
  367. * Verbosely Delete the domains example.com and example.net and assign
  368. * their content and users to the default domain, example.org.
  369. *
  370. * @option chatty
  371. * Document each step as it is performed.
  372. * @option dryrun
  373. * Do not do anything, but explain what would be done. Implies --chatty.
  374. * @option users-assign
  375. * Values "prompt", "ignore", "default", <name>, Reassign user accounts
  376. * associated with the the domain being deleted to the default domain,
  377. * to the domain whose machine name is <name>, or leave the user accounts
  378. * alone (and so inaccessible in the normal way). The default value is
  379. * 'prompt': ask which domain to use.
  380. *
  381. * @command domain:delete
  382. * @aliases domain-delete
  383. *
  384. * @throws \Drupal\domain\Commands\DomainCommandException
  385. *
  386. * @see https://github.com/consolidation/annotated-command#option-event-hook
  387. */
  388. public function delete($domain_id, $options = ['users-assign' => null, 'dryrun' => null, 'chatty' => null]) {
  389. if (is_null($options['users-assign'])) {
  390. $policy_users = 'prompt';
  391. }
  392. $this->is_dry_run = (bool) $options['dryrun'];
  393. // Get current domain list and perform validation checks.
  394. $default_domain = $this->domainStorage()->loadDefaultDomain();
  395. $all_domains = $this->domainStorage()->loadMultipleSorted(NULL);
  396. if (empty($all_domains)) {
  397. throw new DomainCommandException('There are no configured domains.');
  398. }
  399. if (empty($domain_id)) {
  400. throw new DomainCommandException('You must specify a domain to delete.');
  401. }
  402. // Determine which domains to be deleted.
  403. if ($domain_id === 'all') {
  404. $domains = $all_domains;
  405. if (empty($domains)) {
  406. $this->logger()->info(dt('There are no domains to delete.'));
  407. return;
  408. }
  409. $really = $this->io()->confirm(dt('This action cannot be undone. Continue?:'), FALSE);
  410. if (empty($really)) {
  411. return;
  412. }
  413. // TODO: handle deletion of all domains.
  414. }
  415. elseif ($domain = $this->getDomainFromArgument($domain_id)) {
  416. if ($domain->isDefault()) {
  417. throw new DomainCommandException('The primary domain may not be deleted.
  418. Use drush domain:default to set a new default domain.');
  419. }
  420. $domains = [$domain];
  421. }
  422. if (!empty($options['users-assign'])) {
  423. if (in_array($options['users-assign'], $this->reassignment_policies, TRUE)) {
  424. $policy_users = $options['users-assign'];
  425. }
  426. }
  427. $delete_options = [
  428. 'entity_filter' => 'user',
  429. 'policy' => $policy_users,
  430. 'field' => DOMAIN_ADMIN_FIELD,
  431. ];
  432. if ($policy_users !== 'ignore') {
  433. $messages[] = $this->doReassign($domain, $delete_options);
  434. }
  435. // Fire any registered hooks for deletion, passing them current imput.
  436. $handlers = $this->getCustomEventHandlers('domain-delete');
  437. $messages = [];
  438. foreach ($handlers as $handler) {
  439. $messages[] = $handler($domain, $options);
  440. }
  441. $this->deleteDomain($domains, $options);
  442. $message = dt('Domain record !domain deleted.', ['!domain' => $domain->id()]);
  443. if ($messages) {
  444. $message .= "\n" . implode("\n", $messages);
  445. }
  446. $this->logger()->info($message);
  447. return $message;
  448. }
  449. /**
  450. * Handles reassignment of entities to another domain.
  451. *
  452. * This method includes necessary UI elements if the user is prompted to
  453. * choose a new domain.
  454. *
  455. * @param Drupal\domain\DomainInterface $target_domain
  456. * The domain selected for deletion.
  457. * @param array $delete_options
  458. * A selection of options for deletion, defined in reassignLinkedEntities().
  459. */
  460. public function doReassign(DomainInterface $target_domain, array $delete_options) {
  461. $policy = $delete_options['policy'];
  462. $default_domain = $this->domainStorage()->loadDefaultDomain();
  463. $all_domains = $this->domainStorage()->loadMultipleSorted(NULL);
  464. // Perform the 'prompt' for a destination domain.
  465. if ($policy === 'prompt') {
  466. // Make a list of the eligible destination domains in form id -> name.
  467. $noassign_domain = [$target_domain->id()];
  468. $reassign_list = $this->filterDomains($all_domains, $noassign_domain);
  469. $reassign_base = [
  470. 'ignore' => dt('Do not reassign'),
  471. 'default' => dt('Reassign to default domain'),
  472. ];
  473. $reassign_list = array_map(
  474. function (DomainInterface $d) {
  475. return $d->getHostname();
  476. },
  477. $reassign_list
  478. );
  479. $reassign_list = array_merge($reassign_base, $reassign_list);
  480. $policy = $this->io()->choice(dt('Reassign @type field @field data to:', ['@type' => $delete_options['entity_filter'], '@field' => $delete_options['field']]), $reassign_list);
  481. }
  482. elseif ($policy === 'default') {
  483. $policy = $default_domain->id();
  484. }
  485. if ($policy !== 'ignore') {
  486. $delete_options['policy'] = $policy;
  487. $target = [$target_domain];
  488. $count = $this->reassignLinkedEntities($target, $delete_options);
  489. return dt('@count @type entities updated field @field.', ['@count' => $count, '@type' => $delete_options['entity_filter'], '@field' => $delete_options['field']]);
  490. }
  491. }
  492. /**
  493. * Tests domains for proper response.
  494. *
  495. * If run from a subfolder, you must specify the --uri.
  496. *
  497. * @param $domain_id
  498. * The machine name or hostname of the domain to make default.
  499. *
  500. * @usage drush domain-test
  501. * @usage drush domain-test example.com
  502. *
  503. * @command domain:test
  504. * @aliases domain-test
  505. *
  506. * @field-labels
  507. * id: Machine name
  508. * url: URL
  509. * response: HTTP Response
  510. * @default-fields id,url,response
  511. *
  512. * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
  513. *
  514. * @throws \Drupal\domain\Commands\DomainCommandException
  515. */
  516. public function test($domain_id = null) {
  517. if (is_null($domain_id)) {
  518. $domains = $this->domainStorage()->loadMultipleSorted();
  519. }
  520. else {
  521. if ($domain = $this->getDomainFromArgument($domain_id)) {
  522. $domains = [$domain];
  523. }
  524. else {
  525. throw new DomainCommandException(dt('Domain @domain not found.',
  526. ['@domain' => $options['domain']]));
  527. }
  528. }
  529. $keys = ['url', 'response'];
  530. $rows = [];
  531. foreach ($domains as $domain) {
  532. $rows[] = [
  533. 'id' => $domain->id(),
  534. 'url' => $domain->getPath(),
  535. 'response' => $domain->getResponse(),
  536. ];
  537. }
  538. return new RowsOfFields($rows);
  539. }
  540. /**
  541. * Sets the default domain.
  542. *
  543. * @param $domain_id
  544. * The machine name or hostname of the domain to make default.
  545. * @param array $options
  546. * An associative array of options whose values come from cli, aliases,
  547. * config, etc.
  548. * @option validate
  549. * Force a check of the URL response before allowing registration.
  550. * @usage drush domain-default www.example.com
  551. * @usage drush domain-default example_org
  552. * @usage drush domain-default www.example.org --validate=1
  553. *
  554. * @command domain:default
  555. * @aliases domain-default
  556. *
  557. * @return string
  558. * The machine name of the default domain.
  559. *
  560. * @throws \Drupal\domain\Commands\DomainCommandException
  561. */
  562. public function defaultDomain($domain_id, array $options = ['validate' => null]) {
  563. // Resolve the domain.
  564. if (!empty($domain_id) && $domain = $this->getDomainFromArgument($domain_id)) {
  565. $validate = ($options['validate']) ? 1 : 0;
  566. $domain->addProperty('validate_url', $validate);
  567. if ($error = $this->checkHTTPResponse($domain)) {
  568. throw new DomainCommandException(dt('Unable to verify domain !domain: !error',
  569. ['!domain' => $domain->getHostname(), '!error' => $error]));
  570. }
  571. else {
  572. $domain->saveDefault();
  573. }
  574. }
  575. // Now, ask for the current default, so we know if it worked.
  576. $domain = $this->domainStorage()->loadDefaultDomain();
  577. if ($domain->status()) {
  578. $this->logger()->info(dt('!domain set to primary domain.',
  579. ['!domain' => $domain->getHostname()]));
  580. }
  581. else {
  582. $this->logger()->warning(dt('!domain set to primary domain, but is also inactive.',
  583. ['!domain' => $domain->getHostname()]));
  584. }
  585. return $domain->id();
  586. }
  587. /**
  588. * Deactivates the domain.
  589. *
  590. * @param $domain_id
  591. * The numeric id or hostname of the domain to disable.
  592. * @usage drush domain-disable example.com
  593. * @usage drush domain-disable 1
  594. *
  595. * @command domain:disable
  596. * @aliases domain-disable
  597. *
  598. * @return string
  599. * 'disabled' if the domain is now disabled.
  600. *
  601. * @throws \Drupal\domain\Commands\DomainCommandException
  602. */
  603. public function disable($domain_id) {
  604. // Resolve the domain.
  605. if ($domain = $this->getDomainFromArgument($domain_id)) {
  606. if ($domain->status()) {
  607. $domain->disable();
  608. $this->logger()->info(dt('!domain has been disabled.',
  609. ['!domain' => $domain->getHostname()]));
  610. return dt('Disabled !domain.', ['!domain' => $domain->getHostname()]);
  611. }
  612. else {
  613. $this->logger()->info(dt('!domain is already disabled.',
  614. ['!domain' => $domain->getHostname()]));
  615. return dt('!domain is already disabled.', ['!domain' => $domain->getHostname()]);
  616. }
  617. }
  618. return dt('No matching domain record found.');
  619. }
  620. /**
  621. * Activates the domain.
  622. *
  623. * @param $domain_id
  624. * The numeric id or hostname of the domain to enable.
  625. * @usage drush domain-disable example.com
  626. * @usage drush domain-enable 1
  627. *
  628. * @command domain:enable
  629. * @aliases domain-enable
  630. *
  631. * @return string
  632. * 'enabled' if the domain is now enabled.
  633. *
  634. * @throws \Drupal\domain\Commands\DomainCommandException
  635. */
  636. public function enable($domain_id) {
  637. // Resolve the domain.
  638. if ($domain = $this->getDomainFromArgument($domain_id)) {
  639. if (!$domain->status()) {
  640. $domain->enable();
  641. $this->logger()->info(dt('!domain has been enabled.',
  642. ['!domain' => $domain->getHostname()]));
  643. return dt('Enabled !domain.', ['!domain' => $domain->getHostname()]);
  644. }
  645. else {
  646. $this->logger()->info(dt('!domain is already enabled.',
  647. ['!domain' => $domain->getHostname()]));
  648. return dt('!domain is already enabled.', ['!domain' => $domain->getHostname()]);
  649. }
  650. }
  651. return dt('No matching domain record found.');
  652. }
  653. /**
  654. * Changes a domain label.
  655. *
  656. * @param $domain_id
  657. * The machine name or hostname of the domain to relabel.
  658. * @param $name
  659. * The name to use for the domain.
  660. * @usage drush domain-name example.com Foo
  661. * @usage drush domain-name 1 Foo
  662. *
  663. * @command domain:name
  664. * @aliases domain-name
  665. *
  666. * @return string
  667. * @throws \Drupal\domain\Commands\DomainCommandException
  668. */
  669. public function renameDomain($domain_id, $name) {
  670. // Resolve the domain.
  671. if ($domain = $this->getDomainFromArgument($domain_id)) {
  672. $domain->saveProperty('name', $name);
  673. return dt('Renamed !domain to !name.', ['!domain' => $domain->getHostname(), '!name' => $domain->label()]);
  674. }
  675. return dt('No matching domain record found.');
  676. }
  677. /**
  678. * Changes a domain scheme.
  679. *
  680. * @param $domain_id
  681. * The machine name or hostname of the domain to change.
  682. * @param $scheme
  683. * The scheme to use for the domain: http, https, or variable.
  684. *
  685. * @usage drush domain-scheme example.com http
  686. * @usage drush domain-scheme example_com https
  687. *
  688. * @command domain:scheme
  689. * @aliases domain-scheme
  690. *
  691. * @return string
  692. * @throws \Drupal\domain\Commands\DomainCommandException
  693. */
  694. public function scheme($domain_id, $scheme = null) {
  695. $new_scheme = null;
  696. // Resolve the domain.
  697. if ($domain = $this->getDomainFromArgument($domain_id)) {
  698. if (!empty($scheme)) {
  699. // --set with a value
  700. $new_scheme = $scheme;
  701. }
  702. else {
  703. // Prompt for selection.
  704. $new_scheme = $this->io()->choice(dt('Select the default http scheme:'),
  705. [
  706. 'http' => 'http',
  707. 'https' => 'https',
  708. 'variable' => 'variable',
  709. ]);
  710. }
  711. // If we were asked to change scheme, validate the value and do so.
  712. if (!empty($new_scheme)) {
  713. switch ($new_scheme) {
  714. case 'http':
  715. $new_scheme = 'http';
  716. break;
  717. case 'https':
  718. $new_scheme = 'https';
  719. break;
  720. case 'variable':
  721. $new_scheme = 'variable';
  722. break;
  723. default:
  724. throw new DomainCommandException(
  725. dt('Scheme name "!scheme" not known', ['!scheme' => $new_scheme])
  726. );
  727. }
  728. $domain->saveProperty('scheme', $new_scheme);
  729. }
  730. // Either way, return the (new | current) scheme for this domain.
  731. return dt('Scheme is now to "!scheme" for !domain', ['!scheme' => $domain->get('scheme'),'!domain' => $domain->id()]);
  732. }
  733. // We couldn't find the domain - so fail.
  734. throw new DomainCommandException(
  735. dt('Domain name "!domain" not known', ['!domain' => $domain_id])
  736. );
  737. }
  738. /**
  739. * Generate domains for testing.
  740. *
  741. * @param $primary
  742. * The primary domain to use. This will be created and used for
  743. * *.example.com hostnames.
  744. * @param array $options
  745. * An associative array of options whose values come from cli, aliases,
  746. * config, etc.
  747. * @option count
  748. * The count of extra domains to generate. Default is 15.
  749. * @option empty
  750. * Pass empty=1 to truncate the {domain} table before creating records.
  751. * @usage drush domain-generate example.com
  752. * @usage drush domain-generate example.com --count=25
  753. * @usage drush domain-generate example.com --count=25 --empty=1
  754. * @usage drush gend
  755. * @usage drush gend --count=25
  756. * @usage drush gend --count=25 --empty=1
  757. *
  758. * @command domain:generate
  759. * @aliases gend,domgen,domain-generate
  760. *
  761. * @throws \Drupal\domain\Commands\DomainCommandException
  762. */
  763. public function generate($primary = 'example.com', array $options = ['count' => null, 'empty' => null]) {
  764. // Check the number of domains to create.
  765. $count = $options['count'];
  766. if (is_null($count)) {
  767. $count = 15;
  768. }
  769. $domains = $this->domainStorage()->loadMultiple(NULL);
  770. if (!empty($options['empty'])) {
  771. $this->domainStorage()->delete($domains);
  772. $domains = $this->domainStorage()->loadMultiple(NULL);
  773. }
  774. // Ensure we don't duplicate any domains.
  775. $existing = [];
  776. if (!empty($domains)) {
  777. /** @var DomainInterface $domain */
  778. foreach ($domains as $domain) {
  779. $existing[] = $domain->getHostname();
  780. }
  781. }
  782. // Set up one.* and so on.
  783. $names = [
  784. 'one',
  785. 'two',
  786. 'three',
  787. 'four',
  788. 'five',
  789. 'six',
  790. 'seven',
  791. 'eight',
  792. 'nine',
  793. 'ten',
  794. 'foo',
  795. 'bar',
  796. 'baz',
  797. ];
  798. // Set the creation array.
  799. $new = [$primary];
  800. foreach ($names as $name) {
  801. $new[] = $name . '.' . $primary;
  802. }
  803. // Include a non hostname.
  804. $new[] = 'my' . $primary;
  805. // Filter against existing so we can count correctly.
  806. $prepared = [];
  807. foreach ($new as $key => $value) {
  808. if (!in_array($value, $existing, true)) {
  809. $prepared[] = $value;
  810. }
  811. }
  812. // Add any test domains that have numeric prefixes. We don't expect these URLs to work,
  813. // and mainly use these for testing the user interface.
  814. // Test that we already have test domains.
  815. $start = 1;
  816. foreach ($existing as $exists) {
  817. $name = explode('.', $exists);
  818. if (substr_count($name[0], 'test') > 0) {
  819. $num = (int) str_replace('test', '', $name[0]) + 1;
  820. if ($num > $start) {
  821. $start = $num;
  822. }
  823. }
  824. }
  825. $needed = $count - count($prepared) + $start;
  826. for ($i = $start; $i <= $needed; $i++) {
  827. $prepared[] = 'test' . $i . '.' . $primary;
  828. }
  829. // Get the initial item weight for sorting.
  830. $start_weight = count($domains);
  831. $prepared = array_slice($prepared, 0, $count);
  832. $list = [];
  833. // Create the domains.
  834. foreach ($prepared as $key => $item) {
  835. $hostname = mb_strtolower($item);
  836. $values = [
  837. 'name' => ($item != $primary) ? ucwords(str_replace(".$primary", '', $item)) : \Drupal::config('system.site')->get('name'),
  838. 'hostname' => $hostname,
  839. 'scheme' => 'http',
  840. 'status' => 1,
  841. 'weight' => ($item != $primary) ? $key + $start_weight + 1 : -1,
  842. 'is_default' => 0,
  843. 'id' => $this->domainStorage()->createMachineName($hostname),
  844. ];
  845. $domain = $this->domainStorage()->create($values);
  846. $domain->save();
  847. $list[] = dt('Created @domain.', ['@domain' => $domain->getHostname()]);
  848. }
  849. // If nothing created, say so.
  850. if (empty($prepared)) {
  851. return dt('No new domains were created.');
  852. }
  853. else {
  854. return dt("Created @count new domains:\n@list", ['@count' => count($prepared), '@list' => implode("\n", $list)]);
  855. }
  856. }
  857. /**
  858. * Gets a domain storage object or throw an exception.
  859. *
  860. * Note that domain can run very early in the bootstrap, so we cannot
  861. * reliably inject this service.
  862. *
  863. * @return DomainStorageInterface
  864. *
  865. * @throws \Drupal\domain\Commands\DomainCommandException
  866. */
  867. protected function domainStorage() {
  868. if (!is_null($this->domain_storage)) {
  869. return $this->domain_storage;
  870. }
  871. try {
  872. $this->domain_storage = \Drupal::entityTypeManager()->getStorage('domain');
  873. }
  874. catch (PluginNotFoundException $e) {
  875. throw new DomainCommandException('Unable to get domain: no storage', $e);
  876. }
  877. catch (InvalidPluginDefinitionException $e) {
  878. throw new DomainCommandException('Unable to get domain: bad storage', $e);
  879. }
  880. return $this->domain_storage;
  881. }
  882. /**
  883. * Loads a domain based on a string identifier.
  884. *
  885. * @param string $argument
  886. * The machine name or the hostname of an existing domain.
  887. *
  888. * @return \Drupal\domain\DomainInterface
  889. *
  890. * @throws \Drupal\domain\Commands\DomainCommandException
  891. */
  892. protected function getDomainFromArgument($argument) {
  893. // Try loading domain assuming arg is a machine name.
  894. $domain = $this->domainStorage()->load($argument);
  895. if (!$domain) {
  896. // Try loading assuming it is a host name.
  897. $domain = $this->domainStorage()->loadByHostname($argument);
  898. }
  899. // domain_id (an INT) is only used internally because the Node Access
  900. // system demands the use of numeric keys. It should never be used to load
  901. // or identify domain records. Use the machine_name or hostname instead.
  902. if (!$domain) {
  903. throw new DomainCommandException(
  904. dt('Domain record could not be found from "!a".', ['!a' => $argument])
  905. );
  906. }
  907. return $domain;
  908. }
  909. /**
  910. * Filters a list of domains by excluding domains appearing in a specific list.
  911. *
  912. * @param DomainInterface[] $domains
  913. * List of domains.
  914. * @param string[] $exclude
  915. * List of domain id to exclude from the list.
  916. * @param DomainInterface[] $initial
  917. * Initial value of list that will be returned.
  918. *
  919. * @return array
  920. */
  921. protected function filterDomains(array $domains, array $exclude, array $initial = []) {
  922. foreach ($domains as $domain) {
  923. // Exclude unwanted domains.
  924. if (!in_array($domain->id(), $exclude, FALSE)) {
  925. $initial[$domain->id()] = $domain;
  926. }
  927. }
  928. return $initial;
  929. }
  930. /**
  931. * Checks the domain response.
  932. *
  933. * @param \Drupal\domain\DomainInterface $domain
  934. * The domain to check.
  935. * @param bool $validate_url
  936. * True to validate this domain by performing a URL lookup; False to skip
  937. * the checks.
  938. *
  939. * @return bool
  940. * True if the domain resolves properly, or we are not checking,
  941. * False otherwise.
  942. */
  943. protected function checkHTTPResponse(DomainInterface $domain, $validate_url = FALSE) {
  944. // Ensure the url is rebuilt.
  945. if ($validate_url) {
  946. $code = $this->checkDomain($domain);
  947. // Some sort of success:
  948. return $code >= 200 && $code <= 299;
  949. }
  950. // Not validating, so all is well!
  951. return FALSE;
  952. }
  953. /**
  954. * Helper function: check a domain is responsive and create it.
  955. *
  956. * @param DomainInterface $domain
  957. * The (as yet unsaved) domain to create.
  958. * @param bool $check_response
  959. * Indicates that registration should not be allowed unless the server
  960. * returns a 200 response.
  961. *
  962. * @return bool
  963. * TODO: stndardize this return so we can issue good messages.
  964. *
  965. * @throws \Drupal\domain\Commands\DomainCommandException
  966. */
  967. protected function createDomain(DomainInterface $domain, $check_response = FALSE) {
  968. if ($check_response) {
  969. $valid = $this->checkHTTPResponse($domain, TRUE);
  970. if (!$valid) {
  971. throw new DomainCommandException(
  972. dt('The server did not return a 200 response for !d. Domain creation failed. Remove the --validate flag to save this domain.', ['!d' => $domain->getHostname()])
  973. );
  974. }
  975. }
  976. else {
  977. try {
  978. $domain->save();
  979. }
  980. catch (EntityStorageException $e) {
  981. throw new DomainCommandException('Unable to save domain', $e);
  982. }
  983. if ($domain->getDomainId()) {
  984. $this->logger()->info(dt('Created @name at @domain.',
  985. ['@name' => $domain->label(), '@domain' => $domain->getHostname()]));
  986. return TRUE;
  987. }
  988. else {
  989. $this->logger()->error(dt('The request could not be completed.'));
  990. }
  991. }
  992. return FALSE;
  993. }
  994. /**
  995. * Checks a domain exists by trying to do an http request to it.
  996. *
  997. * @param DomainInterface $domain
  998. * The domain to validate for syntax and uniqueness.
  999. *
  1000. * @return int
  1001. * The server response code for the request.
  1002. *
  1003. * @see domain_validate()
  1004. */
  1005. protected function checkDomain(DomainInterface $domain) {
  1006. /** @var \Drupal\domain\DomainValidatorInterface $validator */
  1007. $validator = \Drupal::service('domain.validator');
  1008. return $validator->checkResponse($domain);
  1009. }
  1010. /**
  1011. * Validates a domain meets the standards for a hostname.
  1012. *
  1013. * @param DomainInterface $domain
  1014. * The domain to validate for syntax and uniqueness.
  1015. * @return string[]
  1016. * Array of strings indicating issues found.
  1017. *
  1018. * @see domain_validate()
  1019. */
  1020. protected function validateDomain(DomainInterface $domain) {
  1021. /** @var \Drupal\domain\DomainValidatorInterface $validator */
  1022. $validator = \Drupal::service('domain.validator');
  1023. return $validator->validate($domain->getHostname());
  1024. }
  1025. /**
  1026. * Deletes a domain record.
  1027. *
  1028. * @param DomainInterface[] $domains
  1029. * The domain_id to delete. Pass 'all' to delete all records.
  1030. *
  1031. * @throws \Drupal\domain\Commands\DomainCommandException
  1032. * @throws \UnexpectedValueException
  1033. */
  1034. protected function deleteDomain(array $domains) {
  1035. foreach ($domains as $domain) {
  1036. if (!$domain instanceof DomainInterface) {
  1037. throw new StorageException('deleting domains: value is not a domain');
  1038. }
  1039. $hostname = $domain->getHostname();
  1040. if ($this->is_dry_run) {
  1041. $this->logger()->info(dt('DRYRUN: Domain record @domain deleted.',
  1042. ['@domain' => $hostname]));
  1043. continue;
  1044. }
  1045. try {
  1046. $domain->delete();
  1047. }
  1048. catch (EntityStorageException $e) {
  1049. throw new DomainCommandException(dt('Unable to delete domain: @domain',
  1050. ['@domain' => $hostname]), $e);
  1051. }
  1052. $this->logger()->info(dt('Domain record @domain deleted.',
  1053. ['@domain' => $hostname]));
  1054. }
  1055. }
  1056. /**
  1057. * Returns a list of the entity types that are domain enabled.
  1058. *
  1059. * A domain-enabled entity is defined here as an entity type that includes
  1060. * the domain access field(s).
  1061. *
  1062. * @param string $using_field
  1063. * The specific field name to look for.
  1064. *
  1065. * @return string[]
  1066. * List of entity machine names that support domain references.
  1067. */
  1068. protected function findDomainEnabledEntities($using_field = DOMAIN_ADMIN_FIELD) {
  1069. $this->ensureEntityFieldMap();
  1070. $entities = [];
  1071. foreach($this->entity_field_map as $type => $fields) {
  1072. if (array_key_exists($using_field, $fields)) {
  1073. $entities[] = $type;
  1074. }
  1075. }
  1076. return $entities;
  1077. }
  1078. /**
  1079. * Determines whether or not a given entity is domain-enabled.
  1080. *
  1081. * @param string $entity_type
  1082. * The machine name of the entity.
  1083. * @param string $field
  1084. * The name of the field to check for existence.
  1085. *
  1086. * @return bool
  1087. * True if this type of entity has a domain field.
  1088. */
  1089. protected function entityHasDomainField($entity_type, $field = DOMAIN_ADMIN_FIELD) {
  1090. // Try to avoid repeated calls to getFieldMap(), assuming it's expensive.
  1091. $this->ensureEntityFieldMap();
  1092. return array_key_exists($field, $this->entity_field_map[$entity_type]);
  1093. }
  1094. /**
  1095. * Ensure the local entity field map has been defined.
  1096. *
  1097. * Asking for the entity field map cause a lot of lookup, so we lazily
  1098. * fetch it and then remember it to avoid repeated checks.
  1099. */
  1100. protected function ensureEntityFieldMap() {
  1101. // Try to avoid repeated calls to getFieldMap() assuming it's expensive.
  1102. if (empty($this->entity_field_map)) {
  1103. $entity_manager = \Drupal::entityManager();
  1104. $this->entity_field_map = $entity_manager->getFieldMap();
  1105. }
  1106. }
  1107. /**
  1108. * Enumerate entity instances of the supplied type and domain.
  1109. *
  1110. * @param string $entity_type
  1111. * The entity type name, e.g. 'node'
  1112. * @param string $domain_id
  1113. * The machine name of the domain to enumerate.
  1114. * @param string $field
  1115. * The field to manipulate in the entity, e.g. DOMAIN_ACCESS_FIELD.
  1116. *
  1117. * @return int|string[]
  1118. * List of entity IDs for the selected domain.
  1119. * @todo: should this really be a string[] of fields?
  1120. */
  1121. protected function enumerateDomainEntities($entity_type, $domain_id, $field, $just_count = FALSE) {
  1122. if (!$this->entityHasDomainField($entity_type, $field)) {
  1123. $this->logger()->info('Entity type @entity_type does not have field @field, so none found.',
  1124. ['@entity_type'=> $entity_type,
  1125. '@field' => $field]);
  1126. return [];
  1127. }
  1128. $efq = \Drupal::entityQuery($entity_type);
  1129. // Don't access check or we wont get all of the possible entities moved.
  1130. $efq->accessCheck(FALSE);
  1131. $efq->condition($field, $domain_id, '=');
  1132. if ($just_count) {
  1133. $efq->count();
  1134. }
  1135. return $efq->execute();
  1136. }
  1137. /**
  1138. * Reassign old_domain entities, of the supplied type, to the new_domain.
  1139. *
  1140. * @param string $entity_type
  1141. * The entity type name, e.g. 'node'
  1142. * @param string $field
  1143. * The field to manipulate in the entity, e.g. DOMAIN_ADMIN_FIELD.
  1144. * @param \Drupal\domain\DomainInterface $old_domain
  1145. * The domain the entities currently belong to. It is not an error for
  1146. * entity ids to be passed in that are not in this domain, though of course
  1147. * not very useful.
  1148. * @param \Drupal\domain\DomainInterface $new_domain
  1149. * The domain the entities should now belong to: When an entity belongs to
  1150. * the old_domain, this domain replaces it.
  1151. * @param array $ids
  1152. * List of entity IDs for the selected domain and all of type $entity_type.
  1153. *
  1154. * @return int
  1155. *
  1156. * @throws \Drupal\Component\Plugin\Exception\PluginException
  1157. * @throws \Drupal\Core\Entity\EntityStorageException
  1158. */
  1159. protected function reassignEntities($entity_type, $field, DomainInterface $old_domain, DomainInterface $new_domain, array $ids) {
  1160. $entity_storage = \Drupal::entityTypeManager()->getStorage($entity_type);
  1161. $entities = $entity_storage->loadMultiple($ids);
  1162. foreach($entities as $entity) {
  1163. $changed = FALSE;
  1164. if (!$entity->hasField($field)) {
  1165. continue;
  1166. }
  1167. // Multivalue fields are used, so check each one.
  1168. foreach ($entity->get($field) as $k => $item) {
  1169. if ($item->target_id == $old_domain->id()) {
  1170. if ($this->is_dry_run) {
  1171. $this->logger()->info(dt('DRYRUN: Update domain membership for entity @id to @new.',
  1172. [ '@id' => $entity->id(), '@new' => $new_domain->id() ]));
  1173. // Don't set changed, so don't save either.
  1174. continue;
  1175. }
  1176. $changed = TRUE;
  1177. $item->target_id = $new_domain->id();
  1178. }
  1179. }
  1180. if ($changed) {
  1181. $entity->save();
  1182. }
  1183. }
  1184. return count($entities);
  1185. }
  1186. /**
  1187. * Return the Domain object corresponding to a policy string.
  1188. *
  1189. * @param string $policy
  1190. * In general one of 'prompt' | 'default' | 'ignore' or a domain entity
  1191. * machine name, but this function does not process 'prompt'.
  1192. *
  1193. * @return \Drupal\Core\Entity\EntityInterface|\Drupal\domain\DomainInterface|null
  1194. * @throws \Drupal\domain\Commands\DomainCommandException
  1195. */
  1196. protected function getDomainInstanceFromPolicy($policy) {
  1197. switch($policy) {
  1198. /* Use the Default Domain machine name */
  1199. case 'default':
  1200. $new_domain = $this->domainStorage()->loadDefaultDomain();
  1201. break;
  1202. /* Ask interactively for a Domain machine name */
  1203. case 'prompt':
  1204. case 'ignore':
  1205. return NULL;
  1206. /* Use this (specified) Domain machine name */
  1207. default:
  1208. $new_domain = $this->domainStorage()->load($policy);
  1209. break;
  1210. }
  1211. return $new_domain;
  1212. }
  1213. /**
  1214. * Reassign entities of the supplied type to the $policy domain.
  1215. *
  1216. * @param array $options
  1217. * [
  1218. * 'entity_filter' => 'node',
  1219. * 'policy' => 'prompt' | 'default' | 'ignore' | {domain_id}
  1220. * 'field' => DOMAIN_ACCESS_FIELD,
  1221. * ];
  1222. *
  1223. * @param DomainInterface[] $domains
  1224. * List of the domains to reassign content away from.
  1225. *
  1226. * @throws \Drupal\domain\Commands\DomainCommandException
  1227. */
  1228. protected function reassignLinkedEntities($domains, array $options) {
  1229. $count = 0;
  1230. $field = $options['field'];
  1231. $entity_typenames = $this->findDomainEnabledEntities($field);
  1232. $new_domain = $this->getDomainInstanceFromPolicy($options['policy']);
  1233. if (empty($new_domain)) {
  1234. throw new DomainCommandException('invalid destination domain');
  1235. }
  1236. // For each entity type...
  1237. $exceptions = FALSE;
  1238. foreach ($entity_typenames as $name) {
  1239. if (empty($options['entity_filter']) || $options['entity_filter'] === $name) {
  1240. // For each domain being reassigned from...
  1241. foreach ($domains as $domain) {
  1242. $ids = $this->enumerateDomainEntities($name, $domain->id(), $field);
  1243. if (!empty($ids)) {
  1244. try {
  1245. if ($options['chatty']) {
  1246. $this->logger()->info('Reassigning @count @entity_name entities to @domain',
  1247. ['@entity_name'=>'',
  1248. '@count' => \count($ids),
  1249. '@domain' => $new_domain->id()]);
  1250. }
  1251. $count = $this->reassignEntities($name, $field, $domain, $new_domain, $ids);
  1252. }
  1253. catch (PluginException $e) {
  1254. $exceptions = TRUE;
  1255. $this->logger()->error('Unable to reassign content to @new_domain: plugin exception: @ex',
  1256. ['@ex' => $e->getMessage(),
  1257. '@new_domain' => $new_domain->id()]);
  1258. }
  1259. catch (EntityStorageException $e) {
  1260. $exceptions = TRUE;
  1261. $this->logger()->error('Unable to reassign content to @new_domain: storage exception: @ex',
  1262. ['@ex' => $e->getMessage(),
  1263. '@new_domain' => $new_domain->id()]);
  1264. }
  1265. }
  1266. }
  1267. }
  1268. }
  1269. if ($exceptions) {
  1270. throw new DomainCommandException('Errors encountered during reassign.');
  1271. }
  1272. return $count;
  1273. }
  1274. }