domain.drush.inc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. <?php
  2. /**
  3. * @file
  4. * Drush commands for Domain Access.
  5. */
  6. use Drupal\Component\Utility\Html;
  7. use Drupal\domain\DomainInterface;
  8. use GuzzleHttp\Exception\RequestException;
  9. /**
  10. * Implements hook_drush_command().
  11. */
  12. function domain_drush_command() {
  13. $items = [];
  14. $items['domain-list'] = [
  15. 'description' => 'List active domains for the site.',
  16. 'examples' => [
  17. 'drush domain-list',
  18. 'drush domains',
  19. ],
  20. 'aliases' => ['domains'],
  21. ];
  22. $items['domain-add'] = [
  23. 'description' => 'Add a new domain to the site.',
  24. 'examples' => [
  25. 'drush domain-add example.com \'My Test Site\'',
  26. 'drush domain-add example.com \'My Test Site\' --inactive=1 --https==1',
  27. 'drush domain-add example.com \'My Test Site\' --weight=10',
  28. 'drush domain-add example.com \'My Test Site\' --validate=1',
  29. ],
  30. 'arguments' => [
  31. 'hostname' => 'The domain hostname to register (e.g. example.com).',
  32. 'name' => 'The name of the site (e.g. Domain Two).',
  33. ],
  34. 'options' => [
  35. 'inactive' => 'Set the domain to inactive status if set.',
  36. 'https' => 'Use https protocol for this domain if set.',
  37. 'weight' => 'Set the order (weight) of the domain.',
  38. 'is_default' => 'Set this domain as the default domain.',
  39. 'validate' => 'Force a check of the URL response before allowing registration.',
  40. ],
  41. ];
  42. $items['domain-delete'] = [
  43. 'description' => 'Delete a domain from the site.',
  44. 'examples' => [
  45. 'drush domain-delete example.com',
  46. 'drush domain-delete 1',
  47. ],
  48. 'arguments' => [
  49. 'domain' => 'The numeric id or hostname of the domain to delete.',
  50. ],
  51. ];
  52. $items['domain-test'] = [
  53. 'description' => 'Tests domains for proper response. If run from a subfolder, you must specify the --uri.',
  54. 'examples' => [
  55. 'drush domain-test',
  56. 'drush domain-test example.com',
  57. 'drush domain-test 1',
  58. ],
  59. 'arguments' => [
  60. 'domain_id' => 'The numeric id or hostname of the domain to test. If no value is passed, all domains are tested.',
  61. ],
  62. 'options' => [
  63. 'base_path' => 'The subdirectory name if Drupal is installed in a folder other than server root.',
  64. ],
  65. ];
  66. $items['domain-default'] = [
  67. 'description' => 'Sets the default domain. If run from a subfolder, you must specify the --uri.',
  68. 'examples' => [
  69. 'drush domain-default example.com',
  70. 'drush domain-default 1',
  71. 'drush domain-default 1 --validate=1',
  72. ],
  73. 'arguments' => [
  74. 'domain_id' => 'The numeric id or hostname of the domain to make default.',
  75. ],
  76. 'options' => [
  77. 'validate' => 'Force a check of the URL response before allowing registration.',
  78. ],
  79. ];
  80. $items['domain-disable'] = [
  81. 'description' => 'Sets a domain status to off.',
  82. 'examples' => [
  83. 'drush domain-disable example.com',
  84. 'drush domain-disable 1',
  85. ],
  86. 'arguments' => [
  87. 'domain_id' => 'The numeric id or hostname of the domain to disable.',
  88. ],
  89. ];
  90. $items['domain-enable'] = [
  91. 'description' => 'Sets a domain status to on.',
  92. 'examples' => [
  93. 'drush domain-disable example.com',
  94. 'drush domain-disable 1',
  95. ],
  96. 'arguments' => [
  97. 'domain_id' => 'The numeric id or hostname of the domain to enable.',
  98. ],
  99. ];
  100. $items['domain-name'] = [
  101. 'description' => 'Changes a domain label.',
  102. 'examples' => [
  103. 'drush domain-name example.com Foo',
  104. 'drush domain-name 1 Foo',
  105. ],
  106. 'arguments' => [
  107. 'domain_id' => 'The numeric id or hostname of the domain to relabel.',
  108. 'name' => 'The name to use for the domain.',
  109. ],
  110. ];
  111. $items['domain-machine-name'] = [
  112. 'description' => 'Changes a domain name.',
  113. 'examples' => [
  114. 'drush domain-machine-name example.com foo',
  115. 'drush domain-machine-name 1 foo',
  116. ],
  117. 'arguments' => [
  118. 'domain_id' => 'The numeric id or hostname of the domain to rename.',
  119. 'name' => 'The machine-readable name to use for the domain.',
  120. ],
  121. ];
  122. $items['domain-scheme'] = [
  123. 'description' => 'Changes a domain scheme.',
  124. 'examples' => [
  125. 'drush domain-scheme example.com https',
  126. 'drush domain-scheme 1 https',
  127. ],
  128. 'arguments' => [
  129. 'domain_id' => 'The numeric id or hostname of the domain to change.',
  130. 'scheme' => 'The URL schema (http or https) to use for the domain.',
  131. ],
  132. ];
  133. $items['generate-domains'] = [
  134. 'description' => 'Generate domains for testing.',
  135. 'arguments' => [
  136. 'primary' => 'The primary domain to use. This will be created and used for *.example.com hostnames.',
  137. ],
  138. 'options' => [
  139. 'count' => 'The count of extra domains to generate. Default is 15.',
  140. 'empty' => 'Pass empty=1 to truncate the {domain} table before creating records.',
  141. ],
  142. 'examples' => [
  143. 'drush domain-generate example.com',
  144. 'drush domain-generate example.com --count=25',
  145. 'drush domain-generate example.com --count=25 --empty=1',
  146. 'drush gend',
  147. 'drush gend --count=25',
  148. 'drush gend --count=25 --empty=1',
  149. ],
  150. 'aliases' => ['gend'],
  151. ];
  152. return $items;
  153. }
  154. /**
  155. * Implements hook_drush_help().
  156. */
  157. function domain_drush_help($section) {
  158. $items = domain_drush_command();
  159. $name = str_replace('domain:', '', $section);
  160. if (isset($items[$name])) {
  161. return dt($items[$name]['description']);
  162. }
  163. }
  164. /**
  165. * Shows the domain list.
  166. */
  167. function drush_domain_list() {
  168. $domains = \Drupal::entityTypeManager()->getStorage('domain')->loadMultipleSorted(NULL, TRUE);
  169. if (empty($domains)) {
  170. drush_print(dt('No domains have been created. Use drush domain-add to create one.'));
  171. return;
  172. }
  173. $header = [
  174. 'weight' => dt('Weight'),
  175. 'name' => dt('Name'),
  176. 'hostname' => dt('Hostname'),
  177. 'scheme' => dt('Scheme'),
  178. 'status' => dt('Status'),
  179. 'is_default' => dt('Default'),
  180. 'domain_id' => dt('Domain Id'),
  181. 'id' => dt('Machine name'),
  182. ];
  183. $rows = [array_values($header)];
  184. foreach ($domains as $domain) {
  185. $row = [];
  186. foreach ($header as $key => $name) {
  187. $row[] = Html::escape($domain->get($key));
  188. }
  189. $rows[] = $row;
  190. }
  191. drush_print_table($rows, TRUE);
  192. }
  193. /**
  194. * Generates a list of domains for testing.
  195. *
  196. * In my environment, I name hostnames one.* two.* up to ten. I also use
  197. * foo.* bar.* and baz.*. We also want a non-hostname here and use
  198. * myexample.com.
  199. *
  200. * The script may also add test1, test2, test3 up to any number to test a
  201. * large number of domains. This test is mostly for UI testing.
  202. *
  203. * @param string $primary
  204. * The root domain to use for domain creation.
  205. */
  206. function drush_domain_generate_domains($primary = 'example.com') {
  207. // Check the number of domains to create.
  208. $count = drush_get_option('count');
  209. $domains = \Drupal::entityTypeManager()->getStorage('domain')->loadMultiple(NULL, TRUE);
  210. if (empty($count)) {
  211. $count = 15;
  212. }
  213. // Ensure we don't duplicate any domains.
  214. $existing = [];
  215. if (!empty($domains)) {
  216. foreach ($domains as $domain) {
  217. $existing[] = $domain->getHostname();
  218. }
  219. }
  220. // Set up one.* and so on.
  221. $names = [
  222. 'one',
  223. 'two',
  224. 'three',
  225. 'four',
  226. 'five',
  227. 'six',
  228. 'seven',
  229. 'eight',
  230. 'nine',
  231. 'ten',
  232. 'foo',
  233. 'bar',
  234. 'baz',
  235. ];
  236. // Set the creation array.
  237. $new = [$primary];
  238. foreach ($names as $name) {
  239. $new[] = $name . '.' . $primary;
  240. }
  241. // Include a non hostname.
  242. $new[] = 'my' . $primary;
  243. // Filter against existing so we can count correctly.
  244. $prepared = [];
  245. foreach ($new as $key => $value) {
  246. if (!in_array($value, $existing)) {
  247. $prepared[] = $value;
  248. }
  249. }
  250. // Add any test domains that have numeric prefixes. We don't expect these URLs
  251. // to work, and mainly use these for testing the user interface.
  252. $needed = $count - count($prepared);
  253. for ($i = 1; $i <= $needed; $i++) {
  254. $prepared[] = 'test' . $i . '.' . $primary;
  255. }
  256. // Get the initial item weight for sorting.
  257. $start_weight = count($domains);
  258. $prepared = array_slice($prepared, 0, $count);
  259. // Create the domains.
  260. foreach ($prepared as $key => $item) {
  261. $hostname = mb_strtolower($item);
  262. $values = [
  263. 'name' => ($item != $primary) ? ucwords(str_replace(".$primary", '', $item)) : \Drupal::config('system.site')->get('name'),
  264. 'hostname' => $hostname,
  265. 'scheme' => 'http',
  266. 'status' => 1,
  267. 'weight' => ($item != $primary) ? $key + $start_weight + 1 : -1,
  268. 'is_default' => 0,
  269. 'id' => \Drupal::entityTypeManager()->getStorage('domain')->createMachineName($hostname),
  270. ];
  271. $domain = \Drupal::entityTypeManager()->getStorage('domain')->create($values);
  272. domain_drush_create($domain);
  273. }
  274. // If nothing created, say so.
  275. if (empty($new)) {
  276. drush_print(dt('No new domains were created.'));
  277. }
  278. }
  279. /**
  280. * Validates the domain generation script.
  281. *
  282. * @param string $primary
  283. * The root domain to use for domain creation.
  284. */
  285. function drush_domain_generate_domains_validate($primary = 'example.com') {
  286. if ($empty = drush_get_option('empty')) {
  287. db_query("TRUNCATE TABLE {domain}");
  288. }
  289. return;
  290. // TODO: Add validation.
  291. }
  292. /**
  293. * Adds a new domain.
  294. *
  295. * @param string $hostname
  296. * The domain name to register.
  297. * @param string $name
  298. * The name to use for this domain.
  299. */
  300. function drush_domain_add($hostname, $name) {
  301. $records_count = \Drupal::entityTypeManager()->getStorage('domain')->getQuery()->count()->execute();
  302. $start_weight = $records_count + 1;
  303. $hostname = mb_strtolower($hostname);
  304. /** @var \Drupal\domain\DomainStorageInterface $domain_storage */
  305. $domain_storage = \Drupal::entityTypeManager()->getStorage('domain');
  306. $values = [
  307. 'hostname' => $hostname,
  308. 'name' => $name,
  309. 'status' => (!drush_get_option('invalid')) ? 1 : 0,
  310. 'scheme' => (!drush_get_option('https')) ? 'http' : 'https',
  311. 'weight' => ($weight = drush_get_option('weight')) ? $weight : $start_weight + 1,
  312. 'is_default' => ($is_default = drush_get_option('is_default')) ? $is_default : 0,
  313. 'id' => $domain_storage->createMachineName($hostname),
  314. 'validate_url' => (drush_get_option('validate')) ? 1 : 0,
  315. ];
  316. $domain = $domain_storage->create($values);
  317. domain_drush_create($domain);
  318. }
  319. /**
  320. * Validates the domain add command arguments.
  321. *
  322. * @param string $hostname
  323. * The domain name to register.
  324. * @param string $name
  325. * The name to use for this domain.
  326. *
  327. * @return bool
  328. * TRUE when validation passed, FALSE otherwise.
  329. */
  330. function drush_domain_add_validate($hostname, $name) {
  331. $errors = domain_drush_validate_domain($hostname);
  332. if (!empty($errors)) {
  333. return drush_set_error('domain', $errors);
  334. }
  335. elseif (\Drupal::entityTypeManager()->getStorage('domain')->loadByHostname($hostname)) {
  336. return drush_set_error('domain', dt('The hostname is already registered.'));
  337. }
  338. return TRUE;
  339. }
  340. /**
  341. * Creates a domain record.
  342. *
  343. * @param Drupal\domain\DomainInterface $domain
  344. * A domain entity.
  345. */
  346. function domain_drush_create(DomainInterface $domain) {
  347. if ($error = domain_drush_check_response($domain)) {
  348. drush_set_error('hostname', $error);
  349. }
  350. else {
  351. $domain->save();
  352. if ($domain->getDomainId()) {
  353. drush_print(dt('Created @name at @domain.', ['@name' => $domain->label(), '@domain' => $domain->getHostname()]));
  354. }
  355. else {
  356. drush_print(dt('The request could not be completed.'));
  357. }
  358. }
  359. }
  360. /**
  361. * Runs a check to ensure that the domain is responsive.
  362. *
  363. * @param Drupal\domain\DomainInterface $domain
  364. * A domain entity.
  365. *
  366. * @return string
  367. * An error message if the domain url does not validate. Else empty.
  368. */
  369. function domain_drush_check_response(DomainInterface $domain) {
  370. // Check the domain response. First, clear the path value.
  371. if ($domain->validate_url) {
  372. $domain->setPath();
  373. try {
  374. $response = $domain->getResponse();
  375. }
  376. // We cannot know which Guzzle Exception class will be returned; be generic.
  377. catch (RequestException $e) {
  378. watchdog_exception('domain', $e);
  379. // File a general server failure.
  380. $domain->setResponse(500);
  381. }
  382. // If validate_url is set, then we must receive a 200 response.
  383. if ($domain->getResponse() != 200) {
  384. if (empty($response)) {
  385. $response = 500;
  386. }
  387. return dt('The server request to @url returned a @response response. To proceed, disable the test of the server response by leaving off the --validate flag.', ['@url' => $domain->getPath(), '@response' => $response]);
  388. }
  389. }
  390. }
  391. /**
  392. * Validates a domain.
  393. *
  394. * @param string $hostname
  395. * The domain name to validate for syntax and uniqueness.
  396. *
  397. * @return array
  398. * An array of errors encountered.
  399. *
  400. * @see domain_validate()
  401. */
  402. function domain_drush_validate_domain($hostname) {
  403. /** @var \Drupal\domain\DomainValidatorInterface $validator */
  404. $validator = \Drupal::service('domain.validator');
  405. return $validator->validate($hostname);
  406. }
  407. /**
  408. * Deletes a domain record.
  409. *
  410. * @param string $argument
  411. * The domain_id to delete. Pass 'all' to delete all records.
  412. */
  413. function drush_domain_delete($argument = NULL) {
  414. if (is_null($argument)) {
  415. drush_set_error('domain', dt('You must specify a domain to delete.'));
  416. }
  417. if ($argument == 'all') {
  418. $domains = \Drupal::entityTypeManager()->getStorage('domain')->loadMultiple(NULL, TRUE);
  419. if (empty($domains)) {
  420. drush_print(dt('There are no domains to delete.'));
  421. return;
  422. }
  423. $content = drush_choice([1 => dt('Delete all domains')], dt('This action may not be undone. Continue?'), '!value');
  424. if (empty($content)) {
  425. return;
  426. }
  427. }
  428. // Resolve the domain.
  429. elseif ($domain = drush_domain_get_from_argument($argument)) {
  430. if ($domain->isDefault()) {
  431. return drush_set_error('domain', dt('The primary domain may not be deleted. Use drush domain-default to set a new default domain.'));
  432. }
  433. $domains = [$domain];
  434. }
  435. else {
  436. return;
  437. }
  438. foreach ($domains as $domain) {
  439. $domain->delete();
  440. drush_print(dt('Domain record @domain deleted.', ['@domain' => $domain->getHostname()]));
  441. }
  442. return;
  443. // TODO: Set options for re-assigning content.
  444. $list = \Drupal::entityTypeManager()->getStorage('domain')->loadMultiple(NULL, TRUE);
  445. $options = ['0' => dt('Do not reassign')];
  446. foreach ($list as $data) {
  447. if ($data->id() != $domain->id()) {
  448. $options[$data->getDomainId()] = $data->getHostname();
  449. }
  450. }
  451. $content = drush_choice($options, dt('Reassign content to:'), '!value');
  452. if (empty($content)) {
  453. return;
  454. }
  455. $users = drush_choice($options, dt('Reassign users to:'), '!value');
  456. if (empty($users)) {
  457. return;
  458. }
  459. $values['domain_access'] = (!empty($content)) ? $content : 'none';
  460. $values['domain_editor'] = (!empty($content)) ? $users : 'none';
  461. domain_delete($domain, $values);
  462. drush_print(dt('Domain record deleted.'));
  463. }
  464. /**
  465. * Tests a domain record for the proper HTTP response.
  466. *
  467. * @param string $argument
  468. * The domain_id to test. Passing no value tests all records.
  469. */
  470. function drush_domain_test($argument = NULL) {
  471. // TODO: This won't work in a subdirectory without a parameter.
  472. if ($base_path = drush_get_option('base_path')) {
  473. $GLOBALS['base_path'] = '/' . $base_path . '/';
  474. }
  475. if (is_null($argument)) {
  476. $domains = \Drupal::entityTypeManager()->getStorage('domain')->loadMultiple(NULL, TRUE);
  477. }
  478. else {
  479. if ($domain = drush_domain_get_from_argument($argument)) {
  480. $domains = [$domain];
  481. }
  482. else {
  483. return;
  484. }
  485. }
  486. foreach ($domains as $domain) {
  487. if ($domain->getResponse() != 200) {
  488. drush_print(dt('Fail: !error. Please pass a --uri parameter or a --base_path to retest.', ['!error' => $domain->getResponse()]));
  489. }
  490. else {
  491. drush_print(dt('Success: !url tested successfully.', ['!url' => $domain->getPath()]));
  492. }
  493. }
  494. }
  495. /**
  496. * Sets the default domain id.
  497. */
  498. function drush_domain_default($argument) {
  499. // Resolve the domain.
  500. if ($domain = drush_domain_get_from_argument($argument)) {
  501. $validate = (drush_get_option('validate')) ? 1 : 0;
  502. $domain->addProperty('validate_url', $validate);
  503. if ($error = domain_drush_check_response($domain)) {
  504. drush_set_error('domain', $error);
  505. }
  506. else {
  507. $domain->saveDefault();
  508. drush_print(dt('!domain set to primary domain.', ['!domain' => $domain->getHostname()]));
  509. }
  510. }
  511. }
  512. /**
  513. * Disables a domain.
  514. */
  515. function drush_domain_disable($argument) {
  516. // Resolve the domain.
  517. if ($domain = drush_domain_get_from_argument($argument)) {
  518. if ($domain->status()) {
  519. $domain->disable();
  520. drush_print(dt('!domain has been disabled.', ['!domain' => $domain->getHostname()]));
  521. }
  522. else {
  523. drush_print(dt('!domain is already disabled.', ['!domain' => $domain->getHostname()]));
  524. }
  525. }
  526. }
  527. /**
  528. * Enables a domain.
  529. */
  530. function drush_domain_enable($argument) {
  531. // Resolve the domain.
  532. if ($domain = drush_domain_get_from_argument($argument)) {
  533. if (!$domain->status()) {
  534. $domain->enable();
  535. drush_print(dt('!domain has been enabled.', ['!domain' => $domain->getHostname()]));
  536. }
  537. else {
  538. drush_print(dt('!domain is already enabled.', ['!domain' => $domain->getHostname()]));
  539. }
  540. }
  541. }
  542. /**
  543. * Changes a domain name.
  544. */
  545. function drush_domain_name($argument, $name) {
  546. // Resolve the domain.
  547. if ($domain = drush_domain_get_from_argument($argument)) {
  548. $domain->saveProperty('name', $name);
  549. }
  550. }
  551. /**
  552. * Changes a domain machine_name.
  553. */
  554. function drush_domain_machine_name($argument, $machine_name) {
  555. $machine_name = \Drupal::entityTypeManager()->getStorage('domain')->createMachineName($machine_name);
  556. // Resolve the domain.
  557. if ($domain = drush_domain_get_from_argument($argument)) {
  558. $results = \Drupal::entityTypeManager()
  559. ->getStorage('domain')
  560. ->loadByProperties(['machine_name' => $machine_name]);
  561. foreach ($results as $result) {
  562. if ($result->id() == $machine_name) {
  563. drush_print(dt('The machine_name @machine_name is being used by domain @hostname.', ['@machine_name' => $machine_name, '@hostname' => $result->getHostname()]));
  564. return;
  565. }
  566. }
  567. $domain->saveProperty('id', $machine_name);
  568. }
  569. }
  570. /**
  571. * Changes a domain scheme.
  572. */
  573. function drush_domain_scheme($argument) {
  574. // Resolve the domain.
  575. if ($domain = drush_domain_get_from_argument($argument)) {
  576. $content = drush_choice([1 => dt('http'), 2 => dt('https')], dt('Select the default http scheme:'), '!value');
  577. if (empty($content)) {
  578. return;
  579. }
  580. $scheme = 'http';
  581. if ($content == 2) {
  582. $scheme = 'https';
  583. }
  584. $domain->saveProperty('scheme', $scheme);
  585. }
  586. }
  587. /**
  588. * Converts a domain string or domain_id to a $domain array.
  589. *
  590. * On failure, throws a drush error.
  591. */
  592. function drush_domain_get_from_argument($argument) {
  593. $domain = \Drupal::entityTypeManager()->getStorage('domain')->load($argument);
  594. if (!$domain) {
  595. $domain = \Drupal::entityTypeManager()->getStorage('domain')->loadByHostname($argument);
  596. }
  597. if (!$domain) {
  598. drush_set_error('domain', dt('Domain record not found.'));
  599. return NULL;
  600. }
  601. return $domain;
  602. }