Domain.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. <?php
  2. namespace Drupal\domain\Entity;
  3. use Drupal\Core\Config\ConfigValueException;
  4. use Drupal\Core\Config\Entity\ConfigEntityBase;
  5. use Drupal\Core\Entity\EntityStorageInterface;
  6. use Drupal\Core\Link;
  7. use Drupal\Core\Url;
  8. use Drupal\Core\StringTranslation\StringTranslationTrait;
  9. use Drupal\domain\DomainInterface;
  10. use Drupal\domain\DomainNegotiator;
  11. /**
  12. * Defines the domain entity.
  13. *
  14. * @ConfigEntityType(
  15. * id = "domain",
  16. * label = @Translation("Domain record"),
  17. * module = "domain",
  18. * handlers = {
  19. * "storage" = "Drupal\domain\DomainStorage",
  20. * "access" = "Drupal\domain\DomainAccessControlHandler",
  21. * "list_builder" = "Drupal\domain\DomainListBuilder",
  22. * "form" = {
  23. * "default" = "Drupal\domain\DomainForm",
  24. * "edit" = "Drupal\domain\DomainForm",
  25. * "delete" = "Drupal\domain\Form\DomainDeleteForm"
  26. * }
  27. * },
  28. * config_prefix = "record",
  29. * admin_permission = "administer domains",
  30. * entity_keys = {
  31. * "id" = "id",
  32. * "domain_id" = "domain_id",
  33. * "label" = "name",
  34. * "uuid" = "uuid",
  35. * "status" = "status",
  36. * "weight" = "weight"
  37. * },
  38. * links = {
  39. * "delete-form" = "/admin/config/domain/delete/{domain}",
  40. * "edit-form" = "/admin/config/domain/edit/{domain}",
  41. * "collection" = "/admin/config/domain",
  42. * },
  43. * uri_callback = "domain_uri",
  44. * config_export = {
  45. * "id",
  46. * "domain_id",
  47. * "hostname",
  48. * "name",
  49. * "scheme",
  50. * "status",
  51. * "weight",
  52. * "is_default",
  53. * }
  54. * )
  55. */
  56. class Domain extends ConfigEntityBase implements DomainInterface {
  57. use StringTranslationTrait;
  58. /**
  59. * The ID of the domain entity.
  60. *
  61. * @var string
  62. */
  63. protected $id;
  64. /**
  65. * The domain record ID.
  66. *
  67. * @var int
  68. */
  69. protected $domain_id;
  70. /**
  71. * The domain list name (e.g. Drupal).
  72. *
  73. * @var string
  74. */
  75. protected $name;
  76. /**
  77. * The domain hostname (e.g. example.com).
  78. *
  79. * @var string
  80. */
  81. protected $hostname;
  82. /**
  83. * The domain record sort order.
  84. *
  85. * @var int
  86. */
  87. protected $weight;
  88. /**
  89. * Indicates the default domain.
  90. *
  91. * @var bool
  92. */
  93. protected $is_default = FALSE;
  94. /**
  95. * The domain record protocol (e.g. http://).
  96. *
  97. * @var string
  98. */
  99. protected $scheme;
  100. /**
  101. * The domain record base path, a calculated value.
  102. *
  103. * @var string
  104. */
  105. protected $path;
  106. /**
  107. * The domain record current url, a calculated value.
  108. *
  109. * @var string
  110. */
  111. protected $url;
  112. /**
  113. * The domain record http response test (e.g. 200), a calculated value.
  114. *
  115. * @var int
  116. */
  117. protected $response = NULL;
  118. /**
  119. * The redirect method to use, if needed.
  120. *
  121. * @var int|null
  122. */
  123. protected $redirect = NULL;
  124. /**
  125. * The type of match returned by the negotiator.
  126. *
  127. * @var int
  128. */
  129. protected $matchType;
  130. /**
  131. * The canonical hostname for the domain.
  132. *
  133. * @var string
  134. */
  135. protected $canonical;
  136. /**
  137. * {@inheritdoc}
  138. */
  139. public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
  140. parent::preCreate($storage_controller, $values);
  141. $domain_storage = \Drupal::entityTypeManager()->getStorage('domain');
  142. $default = $domain_storage->loadDefaultId();
  143. $count = $storage_controller->getQuery()->count()->execute();
  144. $values += [
  145. 'scheme' => empty($GLOBALS['is_https']) ? 'http' : 'https',
  146. 'status' => 1,
  147. 'weight' => $count + 1,
  148. 'is_default' => (int) empty($default),
  149. ];
  150. // Note that we have not created a domain_id, which is only used for
  151. // node access control and will be added on save.
  152. }
  153. /**
  154. * {@inheritdoc}
  155. */
  156. public function isActive() {
  157. $negotiator = \Drupal::service('domain.negotiator');
  158. /** @var self $domain */
  159. $domain = $negotiator->getActiveDomain();
  160. if (empty($domain)) {
  161. return FALSE;
  162. }
  163. return ($this->id() == $domain->id());
  164. }
  165. /**
  166. * {@inheritdoc}
  167. */
  168. public function addProperty($name, $value) {
  169. if (!isset($this->{$name})) {
  170. $this->{$name} = $value;
  171. }
  172. }
  173. /**
  174. * {@inheritdoc}
  175. */
  176. public function isDefault() {
  177. return (bool) $this->is_default;
  178. }
  179. /**
  180. * {@inheritdoc}
  181. */
  182. public function isHttps() {
  183. return (bool) ($this->getScheme(FALSE) == 'https');
  184. }
  185. /**
  186. * {@inheritdoc}
  187. */
  188. public function saveDefault() {
  189. if (!$this->isDefault()) {
  190. // Swap the current default.
  191. /** @var self $default */
  192. if ($default = \Drupal::entityTypeManager()->getStorage('domain')->loadDefaultDomain()) {
  193. $default->is_default = FALSE;
  194. $default->setHostname($default->getCanonical());
  195. $default->save();
  196. }
  197. // Save the new default.
  198. $this->is_default = TRUE;
  199. $this->setHostname($this->getCanonical());
  200. $this->save();
  201. }
  202. else {
  203. \Drupal::messenger()->addMessage($this->t('The selected domain is already the default.'), 'warning');
  204. }
  205. }
  206. /**
  207. * {@inheritdoc}
  208. */
  209. public function enable() {
  210. $this->setStatus(TRUE);
  211. $this->setHostname($this->getCanonical());
  212. $this->save();
  213. }
  214. /**
  215. * {@inheritdoc}
  216. */
  217. public function disable() {
  218. if (!$this->isDefault()) {
  219. $this->setStatus(FALSE);
  220. $this->setHostname($this->getCanonical());
  221. $this->save();
  222. }
  223. else {
  224. \Drupal::messenger()->addMessage($this->t('The default domain cannot be disabled.'), 'warning');
  225. }
  226. }
  227. /**
  228. * {@inheritdoc}
  229. */
  230. public function saveProperty($name, $value) {
  231. if (isset($this->{$name})) {
  232. $this->{$name} = $value;
  233. $this->setHostname($this->getCanonical());
  234. $this->save();
  235. \Drupal::messenger()->addMessage($this->t('The @key attribute was set to @value for domain @hostname.', [
  236. '@key' => $name,
  237. '@value' => $value,
  238. '@hostname' => $this->hostname,
  239. ]));
  240. }
  241. else {
  242. \Drupal::messenger()->addMessage($this->t('The @key attribute does not exist.', ['@key' => $name]));
  243. }
  244. }
  245. /**
  246. * {@inheritdoc}
  247. */
  248. public function setPath() {
  249. global $base_path;
  250. $this->path = $this->getScheme() . $this->getHostname() . ($base_path ?: '/');
  251. }
  252. /**
  253. * {@inheritdoc}
  254. */
  255. public function setUrl() {
  256. $request = \Drupal::request();
  257. $uri = $request ? $request->getRequestUri() : '/';
  258. $this->url = $this->getScheme() . $this->getHostname() . $uri;
  259. }
  260. /**
  261. * {@inheritdoc}
  262. */
  263. public function getPath() {
  264. if (!isset($this->path)) {
  265. $this->setPath();
  266. }
  267. return $this->path;
  268. }
  269. /**
  270. * Returns the raw path of the domain object, without the base url.
  271. */
  272. public function getRawPath() {
  273. return $this->getScheme() . $this->getHostname();
  274. }
  275. /**
  276. * Builds a link from a known internal path.
  277. *
  278. * @param string $path
  279. * A Drupal-formatted internal path, starting with /. Note that it is the
  280. * caller's responsibility to handle the base_path().
  281. *
  282. * @return string
  283. * The built link.
  284. */
  285. public function buildUrl($path) {
  286. return $this->getRawPath() . $path;
  287. }
  288. /**
  289. * {@inheritdoc}
  290. */
  291. public function getUrl() {
  292. if (!isset($this->url)) {
  293. $this->setUrl();
  294. }
  295. return $this->url;
  296. }
  297. /**
  298. * {@inheritdoc}
  299. */
  300. public function preSave(EntityStorageInterface $storage) {
  301. parent::preSave($storage);
  302. // Sets the default domain properly.
  303. /** @var self $default */
  304. $default = $storage->loadDefaultDomain();
  305. if (!$default) {
  306. $this->is_default = TRUE;
  307. }
  308. elseif ($this->is_default && $default->getDomainId() != $this->getDomainId()) {
  309. // Swap the current default.
  310. $default->is_default = FALSE;
  311. $default->save();
  312. }
  313. // Ensures we have a proper domain_id.
  314. if ($this->isNew()) {
  315. $this->createDomainId();
  316. }
  317. // Prevent duplicate hostname.
  318. $hostname = $this->getHostname();
  319. // Do not use domain loader because it may change hostname.
  320. $existing = $storage->loadByProperties(['hostname' => $hostname]);
  321. $existing = reset($existing);
  322. if ($existing && $this->getDomainId() != $existing->getDomainId()) {
  323. throw new ConfigValueException("The hostname ($hostname) is already registered.");
  324. }
  325. }
  326. /**
  327. * {@inheritdoc}
  328. */
  329. public function postSave(EntityStorageInterface $storage, $update = TRUE) {
  330. parent::postSave($storage, $update);
  331. // Invalidate cache tags relevant to domains.
  332. \Drupal::service('cache_tags.invalidator')->invalidateTags(['rendered', 'url.site']);
  333. }
  334. /**
  335. * {@inheritdoc}
  336. */
  337. public static function postDelete(EntityStorageInterface $storage, array $entities) {
  338. parent::postDelete($storage, $entities);
  339. foreach ($entities as $entity) {
  340. $actions = $storage->loadMultiple([
  341. 'domain_default_action.' . $entity->id(),
  342. 'domain_delete_action.' . $entity->id(),
  343. 'domain_disable_action.' . $entity->id(),
  344. 'domain_enable_action.' . $entity->id(),
  345. ]);
  346. foreach ($actions as $action) {
  347. $action->delete();
  348. }
  349. }
  350. // Invalidate cache tags relevant to domains.
  351. \Drupal::service('cache_tags.invalidator')->invalidateTags(['rendered', 'url.site']);
  352. }
  353. /**
  354. * {@inheritdoc}
  355. */
  356. public function createDomainId() {
  357. // We cannot reliably use sequences (1, 2, 3) because those can be different
  358. // across environments. Instead, we use the crc32 hash function to create a
  359. // unique numeric id for each domain. In some systems (Windows?) we have
  360. // reports of crc32 returning a negative number. Issue #2794047.
  361. // If we don't use hash(), then crc32() returns different results for 32-
  362. // and 64-bit systems. On 32-bit systems, the number returned may also be
  363. // too large for PHP.
  364. // See #2908236.
  365. $id = hash('crc32', $this->id());
  366. $id = abs(hexdec(substr($id, 0, -2)));
  367. $this->createNumericId($id);
  368. }
  369. /**
  370. * Creates a unique numeric id for use in the {node_access} table.
  371. *
  372. * @param int $id
  373. * An integer to use as the numeric id.
  374. */
  375. public function createNumericId($id) {
  376. // Ensure that this value is unique.
  377. $storage = \Drupal::entityTypeManager()->getStorage('domain');
  378. $result = $storage->loadByProperties(['domain_id' => $id]);
  379. if (empty($result)) {
  380. $this->domain_id = $id;
  381. }
  382. else {
  383. $id++;
  384. $this->createNumericId($id);
  385. }
  386. }
  387. /**
  388. * {@inheritdoc}
  389. */
  390. public function getScheme($add_suffix = TRUE) {
  391. $scheme = $this->scheme;
  392. if ($scheme == 'variable') {
  393. $scheme = \Drupal::entityTypeManager()->getStorage('domain')->getDefaultScheme();
  394. }
  395. elseif ($scheme != 'https') {
  396. $scheme = 'http';
  397. }
  398. $scheme .= ($add_suffix) ? '://' : '';
  399. return $scheme;
  400. }
  401. /**
  402. * {@inheritdoc}
  403. */
  404. public function getRawScheme() {
  405. return $this->scheme;
  406. }
  407. /**
  408. * {@inheritdoc}
  409. */
  410. public function getResponse() {
  411. if (empty($this->response)) {
  412. $validator = \Drupal::service('domain.validator');
  413. $validator->checkResponse($this);
  414. }
  415. return $this->response;
  416. }
  417. /**
  418. * {@inheritdoc}
  419. */
  420. public function setResponse($response) {
  421. $this->response = $response;
  422. }
  423. /**
  424. * {@inheritdoc}
  425. */
  426. public function getLink($current_path = TRUE) {
  427. $options = ['absolute' => TRUE, 'https' => $this->isHttps()];
  428. if ($current_path) {
  429. $url = Url::fromUri($this->getUrl(), $options);
  430. }
  431. else {
  432. $url = Url::fromUri($this->getPath(), $options);
  433. }
  434. return Link::fromTextAndUrl($this->getCanonical(), $url)->toString();
  435. }
  436. /**
  437. * {@inheritdoc}
  438. */
  439. public function getRedirect() {
  440. return $this->redirect;
  441. }
  442. /**
  443. * {@inheritdoc}
  444. */
  445. public function setRedirect($code = 302) {
  446. $this->redirect = $code;
  447. }
  448. /**
  449. * {@inheritdoc}
  450. */
  451. public function getHostname() {
  452. return $this->hostname;
  453. }
  454. /**
  455. * {@inheritdoc}
  456. */
  457. public function setHostname($hostname) {
  458. $this->hostname = $hostname;
  459. }
  460. /**
  461. * {@inheritdoc}
  462. */
  463. public function getDomainId() {
  464. return $this->domain_id;
  465. }
  466. /**
  467. * {@inheritdoc}
  468. */
  469. public function getWeight() {
  470. return $this->weight;
  471. }
  472. /**
  473. * {@inheritdoc}
  474. */
  475. public function setMatchType($match_type = DomainNegotiator::DOMAIN_MATCH_EXACT) {
  476. $this->matchType = $match_type;
  477. }
  478. /**
  479. * {@inheritdoc}
  480. */
  481. public function getMatchType() {
  482. return $this->matchType;
  483. }
  484. /**
  485. * {@inheritdoc}
  486. */
  487. public function getPort() {
  488. $ports = explode(':', $this->getHostname());
  489. if (isset($ports[1])) {
  490. return ':' . $ports[1];
  491. }
  492. return '';
  493. }
  494. /**
  495. * {@inheritdoc}
  496. */
  497. public function setCanonical($hostname = NULL) {
  498. if (is_null($hostname)) {
  499. $this->canonical = $this->getHostname();
  500. }
  501. else {
  502. $this->canonical = $hostname;
  503. }
  504. }
  505. /**
  506. * {@inheritdoc}
  507. */
  508. public function getCanonical() {
  509. if (empty($this->canonical)) {
  510. $this->setCanonical();
  511. }
  512. return $this->canonical;
  513. }
  514. }