3 namespace Drupal\menu_link_content\Entity;
5 use Drupal\Core\Entity\ContentEntityBase;
6 use Drupal\Core\Entity\EntityChangedTrait;
7 use Drupal\Core\Entity\EntityStorageInterface;
8 use Drupal\Core\Entity\EntityTypeInterface;
9 use Drupal\Core\Field\BaseFieldDefinition;
10 use Drupal\link\LinkItemInterface;
11 use Drupal\menu_link_content\MenuLinkContentInterface;
14 * Defines the menu link content entity class.
16 * @property \Drupal\link\LinkItemInterface link
17 * @property \Drupal\Core\Field\FieldItemList rediscover
20 * id = "menu_link_content",
21 * label = @Translation("Custom menu link"),
23 * "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
24 * "storage_schema" = "Drupal\menu_link_content\MenuLinkContentStorageSchema",
25 * "access" = "Drupal\menu_link_content\MenuLinkContentAccessControlHandler",
27 * "default" = "Drupal\menu_link_content\Form\MenuLinkContentForm",
28 * "delete" = "Drupal\menu_link_content\Form\MenuLinkContentDeleteForm"
31 * admin_permission = "administer menu",
32 * base_table = "menu_link_content",
33 * data_table = "menu_link_content_data",
34 * translatable = TRUE,
38 * "langcode" = "langcode",
43 * "canonical" = "/admin/structure/menu/item/{menu_link_content}/edit",
44 * "edit-form" = "/admin/structure/menu/item/{menu_link_content}/edit",
45 * "delete-form" = "/admin/structure/menu/item/{menu_link_content}/delete",
49 class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterface {
51 use EntityChangedTrait;
54 * A flag for whether this entity is wrapped in a plugin instance.
58 protected $insidePlugin = FALSE;
63 public function setInsidePlugin() {
64 $this->insidePlugin = TRUE;
70 public function getTitle() {
71 return $this->get('title')->value;
77 public function getUrlObject() {
78 return $this->link->first()->getUrl();
84 public function getMenuName() {
85 return $this->get('menu_name')->value;
91 public function getDescription() {
92 return $this->get('description')->value;
98 public function getPluginId() {
99 return 'menu_link_content:' . $this->uuid();
105 public function isEnabled() {
106 return (bool) $this->get('enabled')->value;
112 public function isExpanded() {
113 return (bool) $this->get('expanded')->value;
119 public function getParentId() {
120 // Cast the parent ID to a string, only an empty string means no parent,
121 // NULL keeps the existing parent.
122 return (string) $this->get('parent')->value;
128 public function getWeight() {
129 return (int) $this->get('weight')->value;
135 public function getPluginDefinition() {
137 $definition['class'] = 'Drupal\menu_link_content\Plugin\Menu\MenuLinkContent';
138 $definition['menu_name'] = $this->getMenuName();
140 if ($url_object = $this->getUrlObject()) {
141 $definition['url'] = NULL;
142 $definition['route_name'] = NULL;
143 $definition['route_parameters'] = [];
144 if (!$url_object->isRouted()) {
145 $definition['url'] = $url_object->getUri();
148 $definition['route_name'] = $url_object->getRouteName();
149 $definition['route_parameters'] = $url_object->getRouteParameters();
151 $definition['options'] = $url_object->getOptions();
154 $definition['title'] = $this->getTitle();
155 $definition['description'] = $this->getDescription();
156 $definition['weight'] = $this->getWeight();
157 $definition['id'] = $this->getPluginId();
158 $definition['metadata'] = ['entity_id' => $this->id()];
159 $definition['form_class'] = '\Drupal\menu_link_content\Form\MenuLinkContentForm';
160 $definition['enabled'] = $this->isEnabled() ? 1 : 0;
161 $definition['expanded'] = $this->isExpanded() ? 1 : 0;
162 $definition['provider'] = 'menu_link_content';
163 $definition['discovered'] = 0;
164 $definition['parent'] = $this->getParentId();
172 public static function preCreate(EntityStorageInterface $storage, array &$values) {
173 $values += ['bundle' => 'menu_link_content'];
179 public function preSave(EntityStorageInterface $storage) {
180 parent::preSave($storage);
182 if (parse_url($this->link->uri, PHP_URL_SCHEME) === 'internal') {
183 $this->setRequiresRediscovery(TRUE);
186 $this->setRequiresRediscovery(FALSE);
192 public function postSave(EntityStorageInterface $storage, $update = TRUE) {
193 parent::postSave($storage, $update);
195 /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
196 $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
198 // The menu link can just be updated if there is already an menu link entry
199 // on both entity and menu link plugin level.
200 $definition = $this->getPluginDefinition();
201 // Even when $update is FALSE, for top level links it is possible the link
202 // already is in the storage because of the getPluginDefinition() call
203 // above, see https://www.drupal.org/node/2605684#comment-10515450 for the
204 // call chain. Because of this the $update flag is ignored and only the
205 // existence of the definition (equals to being in the tree storage) is
207 if ($menu_link_manager->getDefinition($this->getPluginId(), FALSE)) {
208 // When the entity is saved via a plugin instance, we should not call
209 // the menu tree manager to update the definition a second time.
210 if (!$this->insidePlugin) {
211 $menu_link_manager->updateDefinition($this->getPluginId(), $definition, FALSE);
215 $menu_link_manager->addDefinition($this->getPluginId(), $definition);
222 public static function preDelete(EntityStorageInterface $storage, array $entities) {
223 parent::preDelete($storage, $entities);
225 /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
226 $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
228 foreach ($entities as $menu_link) {
229 /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $menu_link */
230 $menu_link_manager->removeDefinition($menu_link->getPluginId(), FALSE);
237 public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
238 /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
239 $fields = parent::baseFieldDefinitions($entity_type);
241 $fields['id']->setLabel(t('Entity ID'))
242 ->setDescription(t('The entity ID for this menu link content entity.'));
244 $fields['uuid']->setDescription(t('The content menu link UUID.'));
246 $fields['langcode']->setDescription(t('The menu link language code.'));
249 ->setDescription(t('The content menu link bundle.'))
250 ->setSetting('max_length', EntityTypeInterface::BUNDLE_MAX_LENGTH)
251 ->setSetting('is_ascii', TRUE);
253 $fields['title'] = BaseFieldDefinition::create('string')
254 ->setLabel(t('Menu link title'))
255 ->setDescription(t('The text to be used for this link in the menu.'))
257 ->setTranslatable(TRUE)
258 ->setSetting('max_length', 255)
259 ->setDisplayOptions('view', [
264 ->setDisplayOptions('form', [
265 'type' => 'string_textfield',
268 ->setDisplayConfigurable('form', TRUE);
270 $fields['description'] = BaseFieldDefinition::create('string')
271 ->setLabel(t('Description'))
272 ->setDescription(t('Shown when hovering over the menu link.'))
273 ->setTranslatable(TRUE)
274 ->setSetting('max_length', 255)
275 ->setDisplayOptions('view', [
280 ->setDisplayOptions('form', [
281 'type' => 'string_textfield',
285 $fields['menu_name'] = BaseFieldDefinition::create('string')
286 ->setLabel(t('Menu name'))
287 ->setDescription(t('The menu name. All links with the same menu name (such as "tools") are part of the same menu.'))
288 ->setDefaultValue('tools')
289 ->setSetting('is_ascii', TRUE);
291 $fields['link'] = BaseFieldDefinition::create('link')
292 ->setLabel(t('Link'))
293 ->setDescription(t('The location this menu link points to.'))
296 'link_type' => LinkItemInterface::LINK_GENERIC,
297 'title' => DRUPAL_DISABLED,
299 ->setDisplayOptions('form', [
300 'type' => 'link_default',
304 $fields['external'] = BaseFieldDefinition::create('boolean')
305 ->setLabel(t('External'))
306 ->setDescription(t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'))
307 ->setDefaultValue(FALSE);
309 $fields['rediscover'] = BaseFieldDefinition::create('boolean')
310 ->setLabel(t('Indicates whether the menu link should be rediscovered'))
311 ->setDefaultValue(FALSE);
313 $fields['weight'] = BaseFieldDefinition::create('integer')
314 ->setLabel(t('Weight'))
315 ->setDescription(t('Link weight among links in the same menu at the same depth. In the menu, the links with high weight will sink and links with a low weight will be positioned nearer the top.'))
317 ->setDisplayOptions('view', [
319 'type' => 'number_integer',
322 ->setDisplayOptions('form', [
327 $fields['expanded'] = BaseFieldDefinition::create('boolean')
328 ->setLabel(t('Show as expanded'))
329 ->setDescription(t('If selected and this menu link has children, the menu will always appear expanded.'))
330 ->setDefaultValue(FALSE)
331 ->setDisplayOptions('view', [
336 ->setDisplayOptions('form', [
337 'settings' => ['display_label' => TRUE],
341 $fields['enabled'] = BaseFieldDefinition::create('boolean')
342 ->setLabel(t('Enabled'))
343 ->setDescription(t('A flag for whether the link should be enabled in menus or hidden.'))
344 ->setDefaultValue(TRUE)
345 ->setDisplayOptions('view', [
350 ->setDisplayOptions('form', [
351 'settings' => ['display_label' => TRUE],
355 $fields['parent'] = BaseFieldDefinition::create('string')
356 ->setLabel(t('Parent plugin ID'))
357 ->setDescription(t('The ID of the parent menu link plugin, or empty string when at the top level of the hierarchy.'));
359 $fields['changed'] = BaseFieldDefinition::create('changed')
360 ->setLabel(t('Changed'))
361 ->setDescription(t('The time that the menu link was last edited.'))
362 ->setTranslatable(TRUE);
370 public function requiresRediscovery() {
371 return $this->get('rediscover')->value;
377 public function setRequiresRediscovery($rediscovery) {
378 $this->set('rediscover', $rediscovery);