first commit
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Drupal\taxonomy\VocabularyInterface;
|
||||
|
||||
/**
|
||||
* Provides route responses for taxonomy.module.
|
||||
*/
|
||||
class TaxonomyController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Returns a form to add a new term to a vocabulary.
|
||||
*
|
||||
* @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary
|
||||
* The vocabulary this term will be added to.
|
||||
*
|
||||
* @return array
|
||||
* The taxonomy term add form.
|
||||
*/
|
||||
public function addForm(VocabularyInterface $taxonomy_vocabulary) {
|
||||
$term = $this->entityTypeManager()->getStorage('taxonomy_term')->create(['vid' => $taxonomy_vocabulary->id()]);
|
||||
return $this->entityFormBuilder()->getForm($term);
|
||||
}
|
||||
|
||||
/**
|
||||
* Route title callback.
|
||||
*
|
||||
* @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary
|
||||
* The vocabulary.
|
||||
*
|
||||
* @return string
|
||||
* The vocabulary label as a render array.
|
||||
*/
|
||||
public function vocabularyTitle(VocabularyInterface $taxonomy_vocabulary) {
|
||||
return ['#markup' => $taxonomy_vocabulary->label(), '#allowed_tags' => Xss::getHtmlTagList()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Route title callback.
|
||||
*
|
||||
* @param \Drupal\taxonomy\TermInterface $taxonomy_term
|
||||
* The taxonomy term.
|
||||
*
|
||||
* @return array
|
||||
* The term label as a render array.
|
||||
*/
|
||||
public function termTitle(TermInterface $taxonomy_term) {
|
||||
return ['#markup' => $taxonomy_term->getName(), '#allowed_tags' => Xss::getHtmlTagList()];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Entity\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
class VocabularyRouteProvider extends AdminHtmlRouteProvider {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutes(EntityTypeInterface $entity_type) {
|
||||
|
||||
$collection = parent::getRoutes($entity_type);
|
||||
|
||||
if ($reset_page_route = $this->getResetPageRoute($entity_type)) {
|
||||
$collection->add("entity.taxonomy_vocabulary.reset_form", $reset_page_route);
|
||||
}
|
||||
|
||||
if ($overview_page_route = $this->getOverviewPageRoute($entity_type)) {
|
||||
$collection->add("entity.taxonomy_vocabulary.overview_form", $overview_page_route);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCollectionRoute(EntityTypeInterface $entity_type) {
|
||||
if ($route = parent::getCollectionRoute($entity_type)) {
|
||||
$route->setRequirement('_permission', 'access taxonomy overview+administer taxonomy');
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reset page route.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function getResetPageRoute(EntityTypeInterface $entity_type) {
|
||||
$route = new Route('/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/reset');
|
||||
$route->setDefault('_entity_form', 'taxonomy_vocabulary.reset');
|
||||
$route->setDefault('_title', 'Reset');
|
||||
$route->setRequirement('_permission', $entity_type->getAdminPermission());
|
||||
$route->setOption('_admin_route', TRUE);
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the overview page route.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The generated route, if available.
|
||||
*/
|
||||
protected function getOverviewPageRoute(EntityTypeInterface $entity_type) {
|
||||
$route = new Route('/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/overview');
|
||||
$route->setDefault('_title_callback', 'Drupal\taxonomy\Controller\TaxonomyController::vocabularyTitle');
|
||||
$route->setDefault('_form', 'Drupal\taxonomy\Form\OverviewTerms');
|
||||
$route->setRequirement('_entity_access', 'taxonomy_vocabulary.access taxonomy overview');
|
||||
$route->setOption('_admin_route', TRUE);
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Entity;
|
||||
|
||||
use Drupal\Core\Entity\EditorialContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Drupal\user\StatusItem;
|
||||
|
||||
/**
|
||||
* Defines the taxonomy term entity.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "taxonomy_term",
|
||||
* label = @Translation("Taxonomy term"),
|
||||
* label_collection = @Translation("Taxonomy terms"),
|
||||
* label_singular = @Translation("taxonomy term"),
|
||||
* label_plural = @Translation("taxonomy terms"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count taxonomy term",
|
||||
* plural = "@count taxonomy terms",
|
||||
* ),
|
||||
* bundle_label = @Translation("Vocabulary"),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\taxonomy\TermStorage",
|
||||
* "storage_schema" = "Drupal\taxonomy\TermStorageSchema",
|
||||
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
|
||||
* "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
|
||||
* "access" = "Drupal\taxonomy\TermAccessControlHandler",
|
||||
* "views_data" = "Drupal\taxonomy\TermViewsData",
|
||||
* "form" = {
|
||||
* "default" = "Drupal\taxonomy\TermForm",
|
||||
* "delete" = "Drupal\taxonomy\Form\TermDeleteForm"
|
||||
* },
|
||||
* "translation" = "Drupal\taxonomy\TermTranslationHandler"
|
||||
* },
|
||||
* base_table = "taxonomy_term_data",
|
||||
* data_table = "taxonomy_term_field_data",
|
||||
* revision_table = "taxonomy_term_revision",
|
||||
* revision_data_table = "taxonomy_term_field_revision",
|
||||
* uri_callback = "taxonomy_term_uri",
|
||||
* translatable = TRUE,
|
||||
* entity_keys = {
|
||||
* "id" = "tid",
|
||||
* "revision" = "revision_id",
|
||||
* "bundle" = "vid",
|
||||
* "label" = "name",
|
||||
* "langcode" = "langcode",
|
||||
* "uuid" = "uuid",
|
||||
* "published" = "status",
|
||||
* },
|
||||
* revision_metadata_keys = {
|
||||
* "revision_user" = "revision_user",
|
||||
* "revision_created" = "revision_created",
|
||||
* "revision_log_message" = "revision_log_message",
|
||||
* },
|
||||
* bundle_entity_type = "taxonomy_vocabulary",
|
||||
* field_ui_base_route = "entity.taxonomy_vocabulary.overview_form",
|
||||
* common_reference_target = TRUE,
|
||||
* links = {
|
||||
* "canonical" = "/taxonomy/term/{taxonomy_term}",
|
||||
* "delete-form" = "/taxonomy/term/{taxonomy_term}/delete",
|
||||
* "edit-form" = "/taxonomy/term/{taxonomy_term}/edit",
|
||||
* "create" = "/taxonomy/term",
|
||||
* },
|
||||
* permission_granularity = "bundle",
|
||||
* constraints = {
|
||||
* "TaxonomyHierarchy" = {}
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Term extends EditorialContentEntityBase implements TermInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
|
||||
// See if any of the term's children are about to be become orphans.
|
||||
$orphans = [];
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
foreach ($entities as $tid => $term) {
|
||||
if ($children = $storage->getChildren($term)) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $child */
|
||||
foreach ($children as $child) {
|
||||
$parent = $child->get('parent');
|
||||
// Update child parents item list.
|
||||
$parent->filter(function ($item) use ($tid) {
|
||||
return $item->target_id != $tid;
|
||||
});
|
||||
|
||||
// If the term has multiple parents, we don't delete it.
|
||||
if ($parent->count()) {
|
||||
$child->save();
|
||||
}
|
||||
else {
|
||||
$orphans[] = $child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($orphans)) {
|
||||
$storage->delete($orphans);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSave(EntityStorageInterface $storage) {
|
||||
parent::preSave($storage);
|
||||
// Terms with no parents are mandatory children of <root>.
|
||||
if (!$this->get('parent')->count()) {
|
||||
$this->parent->target_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
// @todo Remove the usage of StatusItem in
|
||||
// https://www.drupal.org/project/drupal/issues/2936864.
|
||||
$fields['status']->getItemDefinition()->setClass(StatusItem::class);
|
||||
|
||||
$fields['tid']->setLabel(t('Term ID'))
|
||||
->setDescription(t('The term ID.'));
|
||||
|
||||
$fields['uuid']->setDescription(t('The term UUID.'));
|
||||
|
||||
$fields['status']
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => TRUE,
|
||||
],
|
||||
'weight' => 100,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['vid']->setLabel(t('Vocabulary'))
|
||||
->setDescription(t('The vocabulary to which the term is assigned.'));
|
||||
|
||||
$fields['langcode']->setDescription(t('The term language code.'));
|
||||
|
||||
$fields['name'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Name'))
|
||||
->setTranslatable(TRUE)
|
||||
->setRevisionable(TRUE)
|
||||
->setRequired(TRUE)
|
||||
->setSetting('max_length', 255)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'string',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['description'] = BaseFieldDefinition::create('text_long')
|
||||
->setLabel(t('Description'))
|
||||
->setTranslatable(TRUE)
|
||||
->setRevisionable(TRUE)
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'text_default',
|
||||
'weight' => 0,
|
||||
])
|
||||
->setDisplayConfigurable('view', TRUE)
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'text_textfield',
|
||||
'weight' => 0,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['weight'] = BaseFieldDefinition::create('integer')
|
||||
->setLabel(t('Weight'))
|
||||
->setDescription(t('The weight of this term in relation to other terms.'))
|
||||
->setDefaultValue(0);
|
||||
|
||||
$fields['parent'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Term Parents'))
|
||||
->setDescription(t('The parents of this term.'))
|
||||
->setSetting('target_type', 'taxonomy_term')
|
||||
->setCardinality(BaseFieldDefinition::CARDINALITY_UNLIMITED);
|
||||
|
||||
$fields['changed'] = BaseFieldDefinition::create('changed')
|
||||
->setLabel(t('Changed'))
|
||||
->setDescription(t('The time that the term was last edited.'))
|
||||
->setTranslatable(TRUE)
|
||||
->setRevisionable(TRUE);
|
||||
|
||||
// @todo Keep this field hidden until we have a revision UI for terms.
|
||||
// @see https://www.drupal.org/project/drupal/issues/2936995
|
||||
$fields['revision_log_message']->setDisplayOptions('form', [
|
||||
'region' => 'hidden',
|
||||
]);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
|
||||
// Only terms in the same bundle can be a parent.
|
||||
$fields['parent'] = clone $base_field_definitions['parent'];
|
||||
$fields['parent']->setSetting('handler_settings', ['target_bundles' => [$bundle => $bundle]]);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->get('description')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
$this->set('description', $description);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormat() {
|
||||
return $this->get('description')->format;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFormat($format) {
|
||||
$this->get('description')->format = $format;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->label();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setName($name) {
|
||||
$this->set('name', $name);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWeight() {
|
||||
return $this->get('weight')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setWeight($weight) {
|
||||
$this->set('weight', $weight);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVocabularyId() {
|
||||
@trigger_error('The ' . __METHOD__ . ' method is deprecated since version 8.4.0 and will be removed before 9.0.0. Use ' . __CLASS__ . '::bundle() instead to get the vocabulary ID.', E_USER_DEPRECATED);
|
||||
return $this->bundle();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\taxonomy\VocabularyInterface;
|
||||
|
||||
/**
|
||||
* Defines the taxonomy vocabulary entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "taxonomy_vocabulary",
|
||||
* label = @Translation("Taxonomy vocabulary"),
|
||||
* label_singular = @Translation("vocabulary"),
|
||||
* label_plural = @Translation("vocabularies"),
|
||||
* label_collection = @Translation("Taxonomy"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count vocabulary",
|
||||
* plural = "@count vocabularies"
|
||||
* ),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\taxonomy\VocabularyStorage",
|
||||
* "list_builder" = "Drupal\taxonomy\VocabularyListBuilder",
|
||||
* "access" = "Drupal\taxonomy\VocabularyAccessControlHandler",
|
||||
* "form" = {
|
||||
* "default" = "Drupal\taxonomy\VocabularyForm",
|
||||
* "reset" = "Drupal\taxonomy\Form\VocabularyResetForm",
|
||||
* "delete" = "Drupal\taxonomy\Form\VocabularyDeleteForm",
|
||||
* "overview" = "Drupal\taxonomy\Form\OverviewTerms"
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\taxonomy\Entity\Routing\VocabularyRouteProvider",
|
||||
* }
|
||||
* },
|
||||
* admin_permission = "administer taxonomy",
|
||||
* config_prefix = "vocabulary",
|
||||
* bundle_of = "taxonomy_term",
|
||||
* entity_keys = {
|
||||
* "id" = "vid",
|
||||
* "label" = "name",
|
||||
* "weight" = "weight"
|
||||
* },
|
||||
* links = {
|
||||
* "add-form" = "/admin/structure/taxonomy/add",
|
||||
* "delete-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/delete",
|
||||
* "reset-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/reset",
|
||||
* "overview-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/overview",
|
||||
* "edit-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}",
|
||||
* "collection" = "/admin/structure/taxonomy",
|
||||
* },
|
||||
* config_export = {
|
||||
* "name",
|
||||
* "vid",
|
||||
* "description",
|
||||
* "weight",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Vocabulary extends ConfigEntityBundleBase implements VocabularyInterface {
|
||||
|
||||
/**
|
||||
* The taxonomy vocabulary ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $vid;
|
||||
|
||||
/**
|
||||
* Name of the vocabulary.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Description of the vocabulary.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* The weight of this vocabulary in relation to other vocabularies.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $weight = 0;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHierarchy() {
|
||||
@trigger_error('\Drupal\taxonomy\VocabularyInterface::getHierarchy() is deprecated in Drupal 8.7.x and will be removed before Drupal 9.0.x. Use \Drupal\taxonomy\TermStorage::getVocabularyHierarchyType() instead.', E_USER_DEPRECATED);
|
||||
return $this->entityTypeManager()->getStorage('taxonomy_term')->getVocabularyHierarchyType($this->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setHierarchy($hierarchy) {
|
||||
@trigger_error('\Drupal\taxonomy\VocabularyInterface::setHierarchy() is deprecated in Drupal 8.7.x and will be removed before Drupal 9.0.x. Reset the cache of the taxonomy_term storage controller instead.', E_USER_DEPRECATED);
|
||||
$this->entityTypeManager()->getStorage('taxonomy_term')->resetCache();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function id() {
|
||||
return $this->vid;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::preDelete($storage, $entities);
|
||||
|
||||
// Only load terms without a parent, child terms will get deleted too.
|
||||
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
|
||||
$terms = $term_storage->loadMultiple($storage->getToplevelTids(array_keys($entities)));
|
||||
$term_storage->delete($terms);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
|
||||
// Reset caches.
|
||||
$storage->resetCache(array_keys($entities));
|
||||
|
||||
if (reset($entities)->isSyncing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$vocabularies = [];
|
||||
foreach ($entities as $vocabulary) {
|
||||
$vocabularies[$vocabulary->id()] = $vocabulary->id();
|
||||
}
|
||||
// Load all Taxonomy module fields and delete those which use only this
|
||||
// vocabulary.
|
||||
$field_storages = \Drupal::entityTypeManager()->getStorage('field_storage_config')->loadByProperties(['module' => 'taxonomy']);
|
||||
foreach ($field_storages as $field_storage) {
|
||||
$modified_storage = FALSE;
|
||||
// Term reference fields may reference terms from more than one
|
||||
// vocabulary.
|
||||
foreach ($field_storage->getSetting('allowed_values') as $key => $allowed_value) {
|
||||
if (isset($vocabularies[$allowed_value['vocabulary']])) {
|
||||
$allowed_values = $field_storage->getSetting('allowed_values');
|
||||
unset($allowed_values[$key]);
|
||||
$field_storage->setSetting('allowed_values', $allowed_values);
|
||||
$modified_storage = TRUE;
|
||||
}
|
||||
}
|
||||
if ($modified_storage) {
|
||||
$allowed_values = $field_storage->getSetting('allowed_values');
|
||||
if (empty($allowed_values)) {
|
||||
$field_storage->delete();
|
||||
}
|
||||
else {
|
||||
// Update the field definition with the new allowed values.
|
||||
$field_storage->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,616 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Form;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Pager\PagerManagerInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\taxonomy\VocabularyInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides terms overview form for a taxonomy vocabulary.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class OverviewTerms extends FormBase {
|
||||
use DeprecatedServicePropertyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The term storage handler.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermStorageInterface
|
||||
*/
|
||||
protected $storageController;
|
||||
|
||||
/**
|
||||
* The term list builder.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityListBuilderInterface
|
||||
*/
|
||||
protected $termListBuilder;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The entity repository.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityRepositoryInterface
|
||||
*/
|
||||
protected $entityRepository;
|
||||
|
||||
/**
|
||||
* The pager manager.
|
||||
*
|
||||
* @var \Drupal\Core\Pager\PagerManagerInterface
|
||||
*/
|
||||
protected $pagerManager;
|
||||
|
||||
/**
|
||||
* Constructs an OverviewTerms object.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
|
||||
* The entity repository.
|
||||
* @param \Drupal\Core\Pager\PagerManagerInterface|null $pager_manager
|
||||
* The pager manager.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer = NULL, EntityRepositoryInterface $entity_repository = NULL, PagerManagerInterface $pager_manager = NULL) {
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->storageController = $entity_type_manager->getStorage('taxonomy_term');
|
||||
$this->termListBuilder = $entity_type_manager->getListBuilder('taxonomy_term');
|
||||
$this->renderer = $renderer ?: \Drupal::service('renderer');
|
||||
if (!$entity_repository) {
|
||||
@trigger_error('Calling OverviewTerms::__construct() with the $entity_repository argument is supported in drupal:8.7.0 and will be required before drupal:9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
|
||||
$entity_repository = \Drupal::service('entity.repository');
|
||||
}
|
||||
$this->entityRepository = $entity_repository;
|
||||
if (!$pager_manager) {
|
||||
@trigger_error('Calling OverviewTerms::__construct() without the $pager_manager argument is deprecated in drupal:8.8.0 and the $pager_manager argument will be required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
|
||||
$pager_manager = \Drupal::service('pager.manager');
|
||||
}
|
||||
$this->pagerManager = $pager_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('module_handler'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('renderer'),
|
||||
$container->get('entity.repository'),
|
||||
$container->get('pager.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'taxonomy_overview_terms';
|
||||
}
|
||||
|
||||
/**
|
||||
* Form constructor.
|
||||
*
|
||||
* Display a tree of all the terms in a vocabulary, with options to edit
|
||||
* each one. The form is made drag and drop by the theme function.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
* @param \Drupal\taxonomy\VocabularyInterface $taxonomy_vocabulary
|
||||
* The vocabulary to display the overview form for.
|
||||
*
|
||||
* @return array
|
||||
* The form structure.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL) {
|
||||
$form_state->set(['taxonomy', 'vocabulary'], $taxonomy_vocabulary);
|
||||
$vocabulary_hierarchy = $this->storageController->getVocabularyHierarchyType($taxonomy_vocabulary->id());
|
||||
$parent_fields = FALSE;
|
||||
|
||||
$page = $this->getRequest()->query->get('page') ?: 0;
|
||||
// Number of terms per page.
|
||||
$page_increment = $this->config('taxonomy.settings')->get('terms_per_page_admin');
|
||||
// Elements shown on this page.
|
||||
$page_entries = 0;
|
||||
// Elements at the root level before this page.
|
||||
$before_entries = 0;
|
||||
// Elements at the root level after this page.
|
||||
$after_entries = 0;
|
||||
// Elements at the root level on this page.
|
||||
$root_entries = 0;
|
||||
|
||||
// Terms from previous and next pages are shown if the term tree would have
|
||||
// been cut in the middle. Keep track of how many extra terms we show on
|
||||
// each page of terms.
|
||||
$back_step = NULL;
|
||||
$forward_step = 0;
|
||||
|
||||
// An array of the terms to be displayed on this page.
|
||||
$current_page = [];
|
||||
|
||||
$delta = 0;
|
||||
$term_deltas = [];
|
||||
$tree = $this->storageController->loadTree($taxonomy_vocabulary->id(), 0, NULL, TRUE);
|
||||
$tree_index = 0;
|
||||
do {
|
||||
// In case this tree is completely empty.
|
||||
if (empty($tree[$tree_index])) {
|
||||
break;
|
||||
}
|
||||
$delta++;
|
||||
// Count entries before the current page.
|
||||
if ($page && ($page * $page_increment) > $before_entries && !isset($back_step)) {
|
||||
$before_entries++;
|
||||
continue;
|
||||
}
|
||||
// Count entries after the current page.
|
||||
elseif ($page_entries > $page_increment && isset($complete_tree)) {
|
||||
$after_entries++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not let a term start the page that is not at the root.
|
||||
$term = $tree[$tree_index];
|
||||
if (isset($term->depth) && ($term->depth > 0) && !isset($back_step)) {
|
||||
$back_step = 0;
|
||||
while ($pterm = $tree[--$tree_index]) {
|
||||
$before_entries--;
|
||||
$back_step++;
|
||||
if ($pterm->depth == 0) {
|
||||
$tree_index--;
|
||||
// Jump back to the start of the root level parent.
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
$back_step = isset($back_step) ? $back_step : 0;
|
||||
|
||||
// Continue rendering the tree until we reach the a new root item.
|
||||
if ($page_entries >= $page_increment + $back_step + 1 && $term->depth == 0 && $root_entries > 1) {
|
||||
$complete_tree = TRUE;
|
||||
// This new item at the root level is the first item on the next page.
|
||||
$after_entries++;
|
||||
continue;
|
||||
}
|
||||
if ($page_entries >= $page_increment + $back_step) {
|
||||
$forward_step++;
|
||||
}
|
||||
|
||||
// Finally, if we've gotten down this far, we're rendering a term on this
|
||||
// page.
|
||||
$page_entries++;
|
||||
$term_deltas[$term->id()] = isset($term_deltas[$term->id()]) ? $term_deltas[$term->id()] + 1 : 0;
|
||||
$key = 'tid:' . $term->id() . ':' . $term_deltas[$term->id()];
|
||||
|
||||
// Keep track of the first term displayed on this page.
|
||||
if ($page_entries == 1) {
|
||||
$form['#first_tid'] = $term->id();
|
||||
}
|
||||
// Keep a variable to make sure at least 2 root elements are displayed.
|
||||
if ($term->parents[0] == 0) {
|
||||
$root_entries++;
|
||||
}
|
||||
$current_page[$key] = $term;
|
||||
} while (isset($tree[++$tree_index]));
|
||||
|
||||
// Because we didn't use a pager query, set the necessary pager variables.
|
||||
$total_entries = $before_entries + $page_entries + $after_entries;
|
||||
$this->pagerManager->createPager($total_entries, $page_increment);
|
||||
|
||||
// If this form was already submitted once, it's probably hit a validation
|
||||
// error. Ensure the form is rebuilt in the same order as the user
|
||||
// submitted.
|
||||
$user_input = $form_state->getUserInput();
|
||||
if (!empty($user_input)) {
|
||||
// Get the POST order.
|
||||
$order = array_flip(array_keys($user_input['terms']));
|
||||
// Update our form with the new order.
|
||||
$current_page = array_merge($order, $current_page);
|
||||
foreach ($current_page as $key => $term) {
|
||||
// Verify this is a term for the current page and set at the current
|
||||
// depth.
|
||||
if (is_array($user_input['terms'][$key]) && is_numeric($user_input['terms'][$key]['term']['tid'])) {
|
||||
$current_page[$key]->depth = $user_input['terms'][$key]['term']['depth'];
|
||||
}
|
||||
else {
|
||||
unset($current_page[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$args = [
|
||||
'%capital_name' => Unicode::ucfirst($taxonomy_vocabulary->label()),
|
||||
'%name' => $taxonomy_vocabulary->label(),
|
||||
];
|
||||
if ($this->currentUser()->hasPermission('administer taxonomy') || $this->currentUser()->hasPermission('edit terms in ' . $taxonomy_vocabulary->id())) {
|
||||
switch ($vocabulary_hierarchy) {
|
||||
case VocabularyInterface::HIERARCHY_DISABLED:
|
||||
$help_message = $this->t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', $args);
|
||||
break;
|
||||
|
||||
case VocabularyInterface::HIERARCHY_SINGLE:
|
||||
$help_message = $this->t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', $args);
|
||||
break;
|
||||
|
||||
case VocabularyInterface::HIERARCHY_MULTIPLE:
|
||||
$help_message = $this->t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', $args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch ($vocabulary_hierarchy) {
|
||||
case VocabularyInterface::HIERARCHY_DISABLED:
|
||||
$help_message = $this->t('%capital_name contains the following terms.', $args);
|
||||
break;
|
||||
|
||||
case VocabularyInterface::HIERARCHY_SINGLE:
|
||||
$help_message = $this->t('%capital_name contains terms grouped under parent terms', $args);
|
||||
break;
|
||||
|
||||
case VocabularyInterface::HIERARCHY_MULTIPLE:
|
||||
$help_message = $this->t('%capital_name contains terms with multiple parents.', $args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the IDs of the terms edited on the current page which have pending
|
||||
// revisions.
|
||||
$edited_term_ids = array_map(function ($item) {
|
||||
return $item->id();
|
||||
}, $current_page);
|
||||
$pending_term_ids = array_intersect($this->storageController->getTermIdsWithPendingRevisions(), $edited_term_ids);
|
||||
if ($pending_term_ids) {
|
||||
$help_message = $this->formatPlural(
|
||||
count($pending_term_ids),
|
||||
'%capital_name contains 1 term with pending revisions. Drag and drop of terms with pending revisions is not supported, but you can re-enable drag-and-drop support by getting each term to a published state.',
|
||||
'%capital_name contains @count terms with pending revisions. Drag and drop of terms with pending revisions is not supported, but you can re-enable drag-and-drop support by getting each term to a published state.',
|
||||
$args
|
||||
);
|
||||
}
|
||||
|
||||
// Only allow access to change parents and reorder the tree if there are no
|
||||
// pending revisions and there are no terms with multiple parents.
|
||||
$update_tree_access = AccessResult::allowedIf(empty($pending_term_ids) && $vocabulary_hierarchy !== VocabularyInterface::HIERARCHY_MULTIPLE);
|
||||
|
||||
$form['help'] = [
|
||||
'#type' => 'container',
|
||||
'message' => ['#markup' => $help_message],
|
||||
];
|
||||
if (!$update_tree_access->isAllowed()) {
|
||||
$form['help']['#attributes']['class'] = ['messages', 'messages--warning'];
|
||||
}
|
||||
|
||||
$errors = $form_state->getErrors();
|
||||
$row_position = 0;
|
||||
// Build the actual form.
|
||||
$access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_term');
|
||||
$create_access = $access_control_handler->createAccess($taxonomy_vocabulary->id(), NULL, [], TRUE);
|
||||
if ($create_access->isAllowed()) {
|
||||
$empty = $this->t('No terms available. <a href=":link">Add term</a>.', [':link' => Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $taxonomy_vocabulary->id()])->toString()]);
|
||||
}
|
||||
else {
|
||||
$empty = $this->t('No terms available.');
|
||||
}
|
||||
$form['terms'] = [
|
||||
'#type' => 'table',
|
||||
'#empty' => $empty,
|
||||
'#header' => [
|
||||
'term' => $this->t('Name'),
|
||||
'operations' => $this->t('Operations'),
|
||||
'weight' => $update_tree_access->isAllowed() ? $this->t('Weight') : NULL,
|
||||
],
|
||||
'#attributes' => [
|
||||
'id' => 'taxonomy',
|
||||
],
|
||||
];
|
||||
$this->renderer->addCacheableDependency($form['terms'], $create_access);
|
||||
|
||||
foreach ($current_page as $key => $term) {
|
||||
$form['terms'][$key] = [
|
||||
'term' => [],
|
||||
'operations' => [],
|
||||
'weight' => $update_tree_access->isAllowed() ? [] : NULL,
|
||||
];
|
||||
/** @var $term \Drupal\Core\Entity\EntityInterface */
|
||||
$term = $this->entityRepository->getTranslationFromContext($term);
|
||||
$form['terms'][$key]['#term'] = $term;
|
||||
$indentation = [];
|
||||
if (isset($term->depth) && $term->depth > 0) {
|
||||
$indentation = [
|
||||
'#theme' => 'indentation',
|
||||
'#size' => $term->depth,
|
||||
];
|
||||
}
|
||||
$form['terms'][$key]['term'] = [
|
||||
'#prefix' => !empty($indentation) ? $this->renderer->render($indentation) : '',
|
||||
'#type' => 'link',
|
||||
'#title' => $term->getName(),
|
||||
'#url' => $term->toUrl(),
|
||||
];
|
||||
|
||||
// Add a special class for terms with pending revision so we can highlight
|
||||
// them in the form.
|
||||
$form['terms'][$key]['#attributes']['class'] = [];
|
||||
if (in_array($term->id(), $pending_term_ids)) {
|
||||
$form['terms'][$key]['#attributes']['class'][] = 'color-warning';
|
||||
$form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term--pending-revision';
|
||||
}
|
||||
|
||||
if ($update_tree_access->isAllowed() && count($tree) > 1) {
|
||||
$parent_fields = TRUE;
|
||||
$form['terms'][$key]['term']['tid'] = [
|
||||
'#type' => 'hidden',
|
||||
'#value' => $term->id(),
|
||||
'#attributes' => [
|
||||
'class' => ['term-id'],
|
||||
],
|
||||
];
|
||||
$form['terms'][$key]['term']['parent'] = [
|
||||
'#type' => 'hidden',
|
||||
// Yes, default_value on a hidden. It needs to be changeable by the
|
||||
// javascript.
|
||||
'#default_value' => $term->parents[0],
|
||||
'#attributes' => [
|
||||
'class' => ['term-parent'],
|
||||
],
|
||||
];
|
||||
$form['terms'][$key]['term']['depth'] = [
|
||||
'#type' => 'hidden',
|
||||
// Same as above, the depth is modified by javascript, so it's a
|
||||
// default_value.
|
||||
'#default_value' => $term->depth,
|
||||
'#attributes' => [
|
||||
'class' => ['term-depth'],
|
||||
],
|
||||
];
|
||||
}
|
||||
$update_access = $term->access('update', NULL, TRUE);
|
||||
$update_tree_access = $update_tree_access->andIf($update_access);
|
||||
|
||||
if ($update_tree_access->isAllowed()) {
|
||||
$form['terms'][$key]['weight'] = [
|
||||
'#type' => 'weight',
|
||||
'#delta' => $delta,
|
||||
'#title' => $this->t('Weight for added term'),
|
||||
'#title_display' => 'invisible',
|
||||
'#default_value' => $term->getWeight(),
|
||||
'#attributes' => ['class' => ['term-weight']],
|
||||
];
|
||||
}
|
||||
|
||||
if ($operations = $this->termListBuilder->getOperations($term)) {
|
||||
$form['terms'][$key]['operations'] = [
|
||||
'#type' => 'operations',
|
||||
'#links' => $operations,
|
||||
];
|
||||
}
|
||||
|
||||
if ($parent_fields) {
|
||||
$form['terms'][$key]['#attributes']['class'][] = 'draggable';
|
||||
}
|
||||
|
||||
// Add classes that mark which terms belong to previous and next pages.
|
||||
if ($row_position < $back_step || $row_position >= $page_entries - $forward_step) {
|
||||
$form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-preview';
|
||||
}
|
||||
|
||||
if ($row_position !== 0 && $row_position !== count($tree) - 1) {
|
||||
if ($row_position == $back_step - 1 || $row_position == $page_entries - $forward_step - 1) {
|
||||
$form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-top';
|
||||
}
|
||||
elseif ($row_position == $back_step || $row_position == $page_entries - $forward_step) {
|
||||
$form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-divider-bottom';
|
||||
}
|
||||
}
|
||||
|
||||
// Add an error class if this row contains a form error.
|
||||
foreach ($errors as $error_key => $error) {
|
||||
if (strpos($error_key, $key) === 0) {
|
||||
$form['terms'][$key]['#attributes']['class'][] = 'error';
|
||||
}
|
||||
}
|
||||
$row_position++;
|
||||
}
|
||||
|
||||
$this->renderer->addCacheableDependency($form['terms'], $update_tree_access);
|
||||
if ($update_tree_access->isAllowed()) {
|
||||
if ($parent_fields) {
|
||||
$form['terms']['#tabledrag'][] = [
|
||||
'action' => 'match',
|
||||
'relationship' => 'parent',
|
||||
'group' => 'term-parent',
|
||||
'subgroup' => 'term-parent',
|
||||
'source' => 'term-id',
|
||||
'hidden' => FALSE,
|
||||
];
|
||||
$form['terms']['#tabledrag'][] = [
|
||||
'action' => 'depth',
|
||||
'relationship' => 'group',
|
||||
'group' => 'term-depth',
|
||||
'hidden' => FALSE,
|
||||
];
|
||||
$form['terms']['#attached']['library'][] = 'taxonomy/drupal.taxonomy';
|
||||
$form['terms']['#attached']['drupalSettings']['taxonomy'] = [
|
||||
'backStep' => $back_step,
|
||||
'forwardStep' => $forward_step,
|
||||
];
|
||||
}
|
||||
$form['terms']['#tabledrag'][] = [
|
||||
'action' => 'order',
|
||||
'relationship' => 'sibling',
|
||||
'group' => 'term-weight',
|
||||
];
|
||||
}
|
||||
|
||||
if ($update_tree_access->isAllowed() && count($tree) > 1) {
|
||||
$form['actions'] = ['#type' => 'actions', '#tree' => FALSE];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
'#button_type' => 'primary',
|
||||
];
|
||||
$form['actions']['reset_alphabetical'] = [
|
||||
'#type' => 'submit',
|
||||
'#submit' => ['::submitReset'],
|
||||
'#value' => $this->t('Reset to alphabetical'),
|
||||
];
|
||||
}
|
||||
|
||||
$form['pager_pager'] = ['#type' => 'pager'];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler.
|
||||
*
|
||||
* Rather than using a textfield or weight field, this form depends entirely
|
||||
* upon the order of form elements on the page to determine new weights.
|
||||
*
|
||||
* Because there might be hundreds or thousands of taxonomy terms that need to
|
||||
* be ordered, terms are weighted from 0 to the number of terms in the
|
||||
* vocabulary, rather than the standard -10 to 10 scale. Numbers are sorted
|
||||
* lowest to highest, but are not necessarily sequential. Numbers may be
|
||||
* skipped when a term has children so that reordering is minimal when a child
|
||||
* is added or removed from a term.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
// Sort term order based on weight.
|
||||
uasort($form_state->getValue('terms'), ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
|
||||
|
||||
$vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
|
||||
$changed_terms = [];
|
||||
$tree = $this->storageController->loadTree($vocabulary->id(), 0, NULL, TRUE);
|
||||
|
||||
if (empty($tree)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a list of all terms that need to be updated on previous pages.
|
||||
$weight = 0;
|
||||
$term = $tree[0];
|
||||
while ($term->id() != $form['#first_tid']) {
|
||||
if ($term->parents[0] == 0 && $term->getWeight() != $weight) {
|
||||
$term->setWeight($weight);
|
||||
$changed_terms[$term->id()] = $term;
|
||||
}
|
||||
$weight++;
|
||||
$term = $tree[$weight];
|
||||
}
|
||||
|
||||
// Renumber the current page weights and assign any new parents.
|
||||
$level_weights = [];
|
||||
foreach ($form_state->getValue('terms') as $tid => $values) {
|
||||
if (isset($form['terms'][$tid]['#term'])) {
|
||||
$term = $form['terms'][$tid]['#term'];
|
||||
// Give terms at the root level a weight in sequence with terms on previous pages.
|
||||
if ($values['term']['parent'] == 0 && $term->getWeight() != $weight) {
|
||||
$term->setWeight($weight);
|
||||
$changed_terms[$term->id()] = $term;
|
||||
}
|
||||
// Terms not at the root level can safely start from 0 because they're all on this page.
|
||||
elseif ($values['term']['parent'] > 0) {
|
||||
$level_weights[$values['term']['parent']] = isset($level_weights[$values['term']['parent']]) ? $level_weights[$values['term']['parent']] + 1 : 0;
|
||||
if ($level_weights[$values['term']['parent']] != $term->getWeight()) {
|
||||
$term->setWeight($level_weights[$values['term']['parent']]);
|
||||
$changed_terms[$term->id()] = $term;
|
||||
}
|
||||
}
|
||||
// Update any changed parents.
|
||||
if ($values['term']['parent'] != $term->parents[0]) {
|
||||
$term->parent->target_id = $values['term']['parent'];
|
||||
$changed_terms[$term->id()] = $term;
|
||||
}
|
||||
$weight++;
|
||||
}
|
||||
}
|
||||
|
||||
// Build a list of all terms that need to be updated on following pages.
|
||||
for ($weight; $weight < count($tree); $weight++) {
|
||||
$term = $tree[$weight];
|
||||
if ($term->parents[0] == 0 && $term->getWeight() != $weight) {
|
||||
$term->parent->target_id = $term->parents[0];
|
||||
$term->setWeight($weight);
|
||||
$changed_terms[$term->id()] = $term;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($changed_terms)) {
|
||||
$pending_term_ids = $this->storageController->getTermIdsWithPendingRevisions();
|
||||
|
||||
// Force a form rebuild if any of the changed terms has a pending
|
||||
// revision.
|
||||
if (array_intersect_key(array_flip($pending_term_ids), $changed_terms)) {
|
||||
$this->messenger()->addError($this->t('The terms with updated parents have been modified by another user, the changes could not be saved.'));
|
||||
$form_state->setRebuild();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Save all updated terms.
|
||||
foreach ($changed_terms as $term) {
|
||||
$term->save();
|
||||
}
|
||||
|
||||
$this->messenger()->addStatus($this->t('The configuration options have been saved.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to confirmation form for the reset action.
|
||||
*/
|
||||
public function submitReset(array &$form, FormStateInterface $form_state) {
|
||||
/** @var $vocabulary \Drupal\taxonomy\VocabularyInterface */
|
||||
$vocabulary = $form_state->get(['taxonomy', 'vocabulary']);
|
||||
$form_state->setRedirectUrl($vocabulary->toUrl('reset-form'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityDeleteForm;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a deletion confirmation form for taxonomy term.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TermDeleteForm extends ContentEntityDeleteForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
// The cancel URL is the vocabulary collection, terms have no global
|
||||
// list page.
|
||||
return new Url('entity.taxonomy_vocabulary.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getRedirectUrl() {
|
||||
return $this->getCancelUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->t('Deleting a term will delete all its children if there are any. This action cannot be undone.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDeletionMessage() {
|
||||
return $this->t('Deleted term %name.', ['%name' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityDeleteForm;
|
||||
|
||||
/**
|
||||
* Provides a deletion confirmation form for taxonomy vocabulary.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class VocabularyDeleteForm extends EntityDeleteForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'taxonomy_vocabulary_confirm_delete';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete the vocabulary %title?', ['%title' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDeletionMessage() {
|
||||
return $this->t('Deleted vocabulary %name.', ['%name' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\taxonomy\TermStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides confirmation form for resetting a vocabulary to alphabetical order.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class VocabularyResetForm extends EntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* The term storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermStorageInterface
|
||||
*/
|
||||
protected $termStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new VocabularyResetForm object.
|
||||
*
|
||||
* @param \Drupal\taxonomy\TermStorageInterface $term_storage
|
||||
* The term storage.
|
||||
*/
|
||||
public function __construct(TermStorageInterface $term_storage) {
|
||||
$this->termStorage = $term_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_term')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'taxonomy_vocabulary_confirm_reset_alphabetical';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to reset the vocabulary %title to alphabetical order?', ['%title' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return $this->entity->toUrl('overview-form');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->t('Resetting a vocabulary will discard all custom ordering and sort items alphabetically.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Reset to alphabetical');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
$this->termStorage->resetWeights($this->entity->id());
|
||||
|
||||
$this->messenger()->addStatus($this->t('Reset vocabulary %name to alphabetical order.', ['%name' => $this->entity->label()]));
|
||||
$this->logger('taxonomy')->notice('Reset vocabulary %name to alphabetical order.', ['%name' => $this->entity->label()]);
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
|
||||
/**
|
||||
* Provides specific access control for the taxonomy_term entity type.
|
||||
*
|
||||
* @EntityReferenceSelection(
|
||||
* id = "default:taxonomy_term",
|
||||
* label = @Translation("Taxonomy Term selection"),
|
||||
* entity_types = {"taxonomy_term"},
|
||||
* group = "default",
|
||||
* weight = 1
|
||||
* )
|
||||
*/
|
||||
class TermSelection extends DefaultSelection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'sort' => [
|
||||
'field' => 'name',
|
||||
'direction' => 'asc',
|
||||
],
|
||||
] + parent::defaultConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildConfigurationForm($form, $form_state);
|
||||
|
||||
// Sorting is not possible for taxonomy terms because we use
|
||||
// \Drupal\taxonomy\TermStorageInterface::loadTree() to retrieve matches.
|
||||
$form['sort']['#access'] = FALSE;
|
||||
|
||||
return $form;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
|
||||
if ($match || $limit) {
|
||||
return parent::getReferenceableEntities($match, $match_operator, $limit);
|
||||
}
|
||||
|
||||
$options = [];
|
||||
|
||||
$bundles = $this->entityTypeBundleInfo->getBundleInfo('taxonomy_term');
|
||||
$bundle_names = $this->getConfiguration()['target_bundles'] ?: array_keys($bundles);
|
||||
|
||||
$has_admin_access = $this->currentUser->hasPermission('administer taxonomy');
|
||||
$unpublished_terms = [];
|
||||
foreach ($bundle_names as $bundle) {
|
||||
if ($vocabulary = Vocabulary::load($bundle)) {
|
||||
/** @var \Drupal\taxonomy\TermInterface[] $terms */
|
||||
if ($terms = $this->entityTypeManager->getStorage('taxonomy_term')->loadTree($vocabulary->id(), 0, NULL, TRUE)) {
|
||||
foreach ($terms as $term) {
|
||||
if (!$has_admin_access && (!$term->isPublished() || in_array($term->parent->target_id, $unpublished_terms))) {
|
||||
$unpublished_terms[] = $term->id();
|
||||
continue;
|
||||
}
|
||||
$options[$vocabulary->id()][$term->id()] = str_repeat('-', $term->depth) . Html::escape($this->entityRepository->getTranslationFromContext($term)->label());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
|
||||
if ($match) {
|
||||
return parent::countReferenceableEntities($match, $match_operator);
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
$referenceable_entities = $this->getReferenceableEntities($match, $match_operator, 0);
|
||||
foreach ($referenceable_entities as $bundle => $entities) {
|
||||
$total += count($entities);
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityQuery($match, $match_operator);
|
||||
|
||||
// Adding the 'taxonomy_term_access' tag is sadly insufficient for terms:
|
||||
// core requires us to also know about the concept of 'published' and
|
||||
// 'unpublished'.
|
||||
if (!$this->currentUser->hasPermission('administer taxonomy')) {
|
||||
$query->condition('status', 1);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
|
||||
$term = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
|
||||
|
||||
// In order to create a referenceable term, it needs to published.
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
$term->setPublished();
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateReferenceableNewEntities(array $entities) {
|
||||
$entities = parent::validateReferenceableNewEntities($entities);
|
||||
// Mirror the conditions checked in buildEntityQuery().
|
||||
if (!$this->currentUser->hasPermission('administer taxonomy')) {
|
||||
$entities = array_filter($entities, function ($term) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
return $term->isPublished();
|
||||
});
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'entity reference taxonomy term RSS' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "entity_reference_rss_category",
|
||||
* label = @Translation("RSS category"),
|
||||
* description = @Translation("Display reference to taxonomy term in RSS."),
|
||||
* field_types = {
|
||||
* "entity_reference"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class EntityReferenceTaxonomyTermRssFormatter extends EntityReferenceFormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$parent_entity = $items->getEntity();
|
||||
$elements = [];
|
||||
|
||||
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) {
|
||||
$parent_entity->rss_elements[] = [
|
||||
'key' => 'category',
|
||||
'value' => $entity->label(),
|
||||
'attributes' => [
|
||||
'domain' => $entity->id() ? Url::fromRoute('entity.taxonomy_term.canonical', ['taxonomy_term' => $entity->id()], ['absolute' => TRUE])->toString() : '',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
// This formatter is only available for taxonomy terms.
|
||||
return $field_definition->getFieldStorageDefinition()->getSetting('target_type') == 'taxonomy_term';
|
||||
}
|
||||
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
|
||||
|
||||
/**
|
||||
* Validation constraint for changing the term hierarchy in pending revisions.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "TaxonomyHierarchy",
|
||||
* label = @Translation("Taxonomy term hierarchy.", context = "Validation"),
|
||||
* )
|
||||
*/
|
||||
class TaxonomyTermHierarchyConstraint extends CompositeConstraintBase {
|
||||
|
||||
/**
|
||||
* The default violation message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $message = 'You can only change the hierarchy for the <em>published</em> version of this term.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function coversFields() {
|
||||
return ['parent', 'weight'];
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\taxonomy\TermStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Constraint validator for changing term parents in pending revisions.
|
||||
*/
|
||||
class TaxonomyTermHierarchyConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
private $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Creates a new TaxonomyTermHierarchyConstraintValidator instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($entity, Constraint $constraint) {
|
||||
$term_storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
|
||||
assert($term_storage instanceof TermStorageInterface);
|
||||
|
||||
// Newly created entities should be able to specify a parent.
|
||||
if ($entity && $entity->isNew()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_pending_revision = !$entity->isDefaultRevision();
|
||||
$pending_term_ids = $term_storage->getTermIdsWithPendingRevisions();
|
||||
$ancestors = $term_storage->loadAllParents($entity->id());
|
||||
$ancestor_is_pending_revision = (bool) array_intersect_key($ancestors, array_flip($pending_term_ids));
|
||||
|
||||
$new_parents = array_column($entity->parent->getValue(), 'target_id');
|
||||
$original_parents = array_keys($term_storage->loadParents($entity->id())) ?: [0];
|
||||
if (($is_pending_revision || $ancestor_is_pending_revision) && $new_parents != $original_parents) {
|
||||
$a = 1;
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->atPath('parent')
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
$original = $term_storage->loadUnchanged($entity->id());
|
||||
if (($is_pending_revision || $ancestor_is_pending_revision) && !$entity->weight->equals($original->weight)) {
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->atPath('weight')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\migrate\Plugin\MigrationDeriverTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Deriver for Drupal 6 term node migrations based on vocabularies.
|
||||
*/
|
||||
class D6TermNodeDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
use MigrationDeriverTrait;
|
||||
|
||||
/**
|
||||
* The base plugin ID this derivative is for.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $basePluginId;
|
||||
|
||||
/**
|
||||
* The migration plugin manager.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* D6TermNodeDeriver constructor.
|
||||
*
|
||||
* @param string $base_plugin_id
|
||||
* The base plugin ID this derivative is for.
|
||||
* @param \Drupal\Component\Plugin\PluginManagerInterface $migration_plugin_manager
|
||||
* The migration plugin manager.
|
||||
*/
|
||||
public function __construct($base_plugin_id, PluginManagerInterface $migration_plugin_manager) {
|
||||
$this->basePluginId = $base_plugin_id;
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$base_plugin_id,
|
||||
$container->get('plugin.manager.migration')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition, $base_plugin_definitions = NULL) {
|
||||
try {
|
||||
foreach (static::getSourcePlugin('d6_taxonomy_vocabulary') as $row) {
|
||||
$source_vid = $row->getSourceProperty('vid');
|
||||
$definition = $base_plugin_definition;
|
||||
$definition['source']['vid'] = $source_vid;
|
||||
// migrate_drupal_migration_plugins_alter() adds to this definition.
|
||||
$this->derivatives[$source_vid] = $definition;
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// It is possible no D6 tables are loaded so just eat exceptions.
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Database\DatabaseExceptionWrapper;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrationDeriverTrait;
|
||||
use Drupal\migrate_drupal\FieldDiscoveryInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Deriver for Drupal 7 taxonomy term migrations based on vocabularies.
|
||||
*/
|
||||
class D7TaxonomyTermDeriver extends DeriverBase implements ContainerDeriverInterface {
|
||||
use MigrationDeriverTrait;
|
||||
|
||||
/**
|
||||
* The base plugin ID this derivative is for.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $basePluginId;
|
||||
|
||||
/**
|
||||
* The migration field discovery service.
|
||||
*
|
||||
* @var \Drupal\migrate_drupal\FieldDiscoveryInterface
|
||||
*/
|
||||
protected $fieldDiscovery;
|
||||
|
||||
/**
|
||||
* D7TaxonomyTermDeriver constructor.
|
||||
*
|
||||
* @param string $base_plugin_id
|
||||
* The base plugin ID for the plugin ID.
|
||||
* @param \Drupal\migrate_drupal\FieldDiscoveryInterface $field_discovery
|
||||
* The migration field discovery service.
|
||||
*/
|
||||
public function __construct($base_plugin_id, FieldDiscoveryInterface $field_discovery) {
|
||||
$this->basePluginId = $base_plugin_id;
|
||||
$this->fieldDiscovery = $field_discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$base_plugin_id,
|
||||
$container->get('migrate_drupal.field_discovery')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
|
||||
$vocabulary_source_plugin = static::getSourcePlugin('d7_taxonomy_vocabulary');
|
||||
try {
|
||||
$vocabulary_source_plugin->checkRequirements();
|
||||
}
|
||||
catch (RequirementsException $e) {
|
||||
// If the d7_taxonomy_vocabulary requirements failed, that means we do not
|
||||
// have a Drupal source database configured - there is nothing to
|
||||
// generate.
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($vocabulary_source_plugin as $row) {
|
||||
$bundle = $row->getSourceProperty('machine_name');
|
||||
$values = $base_plugin_definition;
|
||||
|
||||
$values['label'] = t('@label (@type)', [
|
||||
'@label' => $values['label'],
|
||||
'@type' => $row->getSourceProperty('name'),
|
||||
]);
|
||||
$values['source']['bundle'] = $bundle;
|
||||
$values['destination']['default_bundle'] = $bundle;
|
||||
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($values);
|
||||
$this->fieldDiscovery->addBundleFieldProcesses($migration, 'taxonomy_term', $bundle);
|
||||
$this->derivatives[$bundle] = $migration->getPluginDefinition();
|
||||
}
|
||||
}
|
||||
catch (DatabaseExceptionWrapper $e) {
|
||||
// Once we begin iterating the source plugin it is possible that the
|
||||
// source tables will not exist. This can happen when the
|
||||
// MigrationPluginManager gathers up the migration definitions but we do
|
||||
// not actually have a Drupal 7 source database.
|
||||
}
|
||||
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\cckfield;
|
||||
|
||||
@trigger_error('TaxonomyTermReference is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.x. Use \Drupal\taxonomy\Plugin\migrate\field\TaxonomyTermReference instead.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
|
||||
|
||||
/**
|
||||
* @MigrateCckField(
|
||||
* id = "taxonomy_term_reference",
|
||||
* type_map = {
|
||||
* "taxonomy_term_reference" = "entity_reference"
|
||||
* },
|
||||
* core = {6,7},
|
||||
* source_module = "taxonomy",
|
||||
* destination_module = "core",
|
||||
* )
|
||||
*
|
||||
* @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. Use
|
||||
* \Drupal\taxonomy\Plugin\migrate\field\TaxonomyTermReference instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2751897
|
||||
*/
|
||||
class TaxonomyTermReference extends CckFieldPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
|
||||
$process = [
|
||||
'plugin' => 'sub_process',
|
||||
'source' => $field_name,
|
||||
'process' => [
|
||||
'target_id' => 'tid',
|
||||
],
|
||||
];
|
||||
$migration->setProcessOfProperty($field_name, $process);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\field;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase;
|
||||
|
||||
/**
|
||||
* @MigrateField(
|
||||
* id = "taxonomy_term_reference",
|
||||
* type_map = {
|
||||
* "taxonomy_term_reference" = "entity_reference"
|
||||
* },
|
||||
* core = {6,7},
|
||||
* source_module = "taxonomy",
|
||||
* destination_module = "core",
|
||||
* )
|
||||
*/
|
||||
class TaxonomyTermReference extends FieldPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldFormatterMap() {
|
||||
return [
|
||||
'taxonomy_term_reference_link' => 'entity_reference_label',
|
||||
'i18n_taxonomy_term_reference_link' => 'entity_reference_label',
|
||||
'entityreference_entity_view' => 'entity_reference_entity_view',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
|
||||
$process = [
|
||||
'plugin' => 'sub_process',
|
||||
'source' => $field_name,
|
||||
'process' => [
|
||||
'target_id' => 'tid',
|
||||
],
|
||||
];
|
||||
$migration->setProcessOfProperty($field_name, $process);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Checks if the vocabulary being migrated is the one used for forums.
|
||||
*
|
||||
* Drupal 8 Forum is expecting specific machine names for its field and
|
||||
* vocabulary names. This process plugin forces a given machine name to the
|
||||
* field or vocabulary that is being migrated.
|
||||
*
|
||||
* The 'forum_vocabulary' source property is evaluated in the
|
||||
* d6_taxonomy_vocabulary or d7_taxonomy_vocabulary source plugins and is set to
|
||||
* true if the vocabulary vid being migrated is the same as the one in the
|
||||
* 'forum_nav_vocabulary' variable on the source site.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* field_name:
|
||||
* plugin: forum_vocabulary
|
||||
* machine_name: taxonomy_forums
|
||||
* @endcode
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "forum_vocabulary"
|
||||
* )
|
||||
*/
|
||||
class ForumVocabulary extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if ($row->getSourceProperty('forum_vocabulary') && !empty($this->configuration['machine_name'])) {
|
||||
$value = $this->configuration['machine_name'];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Taxonomy term source from database.
|
||||
*
|
||||
* @todo Support term_relation, term_synonym table if possible.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "taxonomy_term",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*
|
||||
* @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0.
|
||||
* Use \Drupal\taxonomy\Plugin\migrate\source\d6\Term or
|
||||
* \Drupal\taxonomy\Plugin\migrate\source\d7\Term.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2879193
|
||||
*/
|
||||
class Term extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* Name of the term data table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $termDataTable;
|
||||
|
||||
/**
|
||||
* Name of the term hierarchy table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $termHierarchyTable;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
if ($this->getModuleSchemaVersion('taxonomy') >= 7000) {
|
||||
$this->termDataTable = 'taxonomy_term_data';
|
||||
$this->termHierarchyTable = 'taxonomy_term_hierarchy';
|
||||
}
|
||||
else {
|
||||
$this->termDataTable = 'term_data';
|
||||
$this->termHierarchyTable = 'term_hierarchy';
|
||||
}
|
||||
|
||||
$query = $this->select($this->termDataTable, 'td')
|
||||
->fields('td')
|
||||
->distinct()
|
||||
->orderBy('td.tid');
|
||||
|
||||
if (isset($this->configuration['vocabulary'])) {
|
||||
$query->condition('td.vid', (array) $this->configuration['vocabulary'], 'IN');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'tid' => $this->t('The term ID.'),
|
||||
'vid' => $this->t('Existing term VID'),
|
||||
'name' => $this->t('The name of the term.'),
|
||||
'description' => $this->t('The term description.'),
|
||||
'weight' => $this->t('Weight'),
|
||||
'parent' => $this->t("The Drupal term IDs of the term's parents."),
|
||||
];
|
||||
if ($this->getModuleSchemaVersion('taxonomy') >= 7000) {
|
||||
$fields['format'] = $this->t('Format of the term description.');
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// Find parents for this row.
|
||||
$parents = $this->select($this->termHierarchyTable, 'th')
|
||||
->fields('th', ['parent', 'tid'])
|
||||
->condition('tid', $row->getSourceProperty('tid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$row->setSourceProperty('parent', $parents);
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['tid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Taxonomy term source from database.
|
||||
*
|
||||
* @todo Support term_relation, term_synonym table if possible.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_taxonomy_term",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class Term extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('term_data', 'td')
|
||||
->fields('td')
|
||||
->distinct()
|
||||
->orderBy('td.tid');
|
||||
|
||||
if (isset($this->configuration['bundle'])) {
|
||||
$query->condition('td.vid', (array) $this->configuration['bundle'], 'IN');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'tid' => $this->t('The term ID.'),
|
||||
'vid' => $this->t('Existing term VID'),
|
||||
'name' => $this->t('The name of the term.'),
|
||||
'description' => $this->t('The term description.'),
|
||||
'weight' => $this->t('Weight'),
|
||||
'parent' => $this->t("The Drupal term IDs of the term's parents."),
|
||||
];
|
||||
if (isset($this->configuration['translations'])) {
|
||||
$fields['language'] = $this->t('The term language.');
|
||||
$fields['trid'] = $this->t('Translation ID.');
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// Find parents for this row.
|
||||
$parents = $this->select('term_hierarchy', 'th')
|
||||
->fields('th', ['parent', 'tid'])
|
||||
->condition('tid', $row->getSourceProperty('tid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$row->setSourceProperty('parent', $parents);
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['tid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Gets i18n taxonomy terms from source database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_term_localized_translation",
|
||||
* source_module = "i18ntaxonomy"
|
||||
* )
|
||||
*/
|
||||
class TermLocalizedTranslation extends Term {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// Ideally, the query would return rows for each language for each taxonomy
|
||||
// term with the translations for both the name and description or just the
|
||||
// name translation or just the description translation. That query quickly
|
||||
// became complex and would be difficult to maintain.
|
||||
// Therefore, build a query based on i18nstrings table where each row has
|
||||
// the translation for only one property, either name or description. The
|
||||
// method prepareRow() is then used to obtain the translation for the other
|
||||
// property.
|
||||
$query = parent::query();
|
||||
$query->addField('td', 'language', 'td.language');
|
||||
|
||||
// Add in the property, which is either name or description.
|
||||
// Cast td.tid as char for PostgreSQL compatibility.
|
||||
$query->leftJoin('i18n_strings', 'i18n', 'CAST(td.tid AS CHAR(255)) = i18n.objectid');
|
||||
$query->addField('i18n', 'lid');
|
||||
$query->addField('i18n', 'property');
|
||||
|
||||
// Add in the translation for the property.
|
||||
$query->innerJoin('locales_target', 'lt', 'i18n.lid = lt.lid');
|
||||
$query->addField('lt', 'language', 'lt.language');
|
||||
$query->addField('lt', 'translation');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$language = $row->getSourceProperty('ltlanguage');
|
||||
$row->setSourceProperty('language', $language);
|
||||
$tid = $row->getSourceProperty('tid');
|
||||
|
||||
// If this row has been migrated it is a duplicate then skip it.
|
||||
if ($this->idMap->lookupDestinationIds(['tid' => $tid, 'language' => $language])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Save the translation for this property.
|
||||
$property = $row->getSourceProperty('property');
|
||||
$row->setSourceProperty($property . '_translated', $row->getSourceProperty('translation'));
|
||||
|
||||
// Get the translation, if one exists, for the property not already in the
|
||||
// row.
|
||||
$other_property = ($property == 'name') ? 'description' : 'name';
|
||||
$query = $this->select('i18n_strings', 'i18n')
|
||||
->fields('i18n', ['lid'])
|
||||
->condition('i18n.property', $other_property)
|
||||
->condition('i18n.objectid', $tid);
|
||||
$query->leftJoin('locales_target', 'lt', 'i18n.lid = lt.lid');
|
||||
$query->condition('lt.language', $language);
|
||||
$query->addField('lt', 'translation');
|
||||
$results = $query->execute()->fetchAssoc();
|
||||
if ($results) {
|
||||
$row->setSourceProperty($other_property . '_translated', $results['translation']);
|
||||
}
|
||||
else {
|
||||
// The translation does not exist.
|
||||
$row->setSourceProperty($other_property . '_translated', NULL);
|
||||
}
|
||||
|
||||
parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'language' => $this->t('Language for this term.'),
|
||||
'name_translated' => $this->t('Term name translation.'),
|
||||
'description_translated' => $this->t('Term description translation.'),
|
||||
];
|
||||
return parent::fields() + $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['language']['type'] = 'string';
|
||||
$ids['language']['alias'] = 'lt';
|
||||
return parent::getIds() + $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Source returning tids from the term_node table for the current revision.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_term_node",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class TermNode extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* The join options between the node and the term node table.
|
||||
*/
|
||||
const JOIN = 'tn.vid = n.vid';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('term_node', 'tn')
|
||||
->distinct()
|
||||
->fields('tn', ['nid', 'vid'])
|
||||
->fields('n', ['type']);
|
||||
// Because this is an inner join it enforces the current revision.
|
||||
$query->innerJoin('term_data', 'td', 'td.tid = tn.tid AND td.vid = :vid', [':vid' => $this->configuration['vid']]);
|
||||
$query->innerJoin('node', 'n', static::JOIN);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'nid' => $this->t('The node revision ID.'),
|
||||
'vid' => $this->t('The node revision ID.'),
|
||||
'tid' => $this->t('The term ID.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// Select the terms belonging to the revision selected.
|
||||
$query = $this->select('term_node', 'tn')
|
||||
->fields('tn', ['tid'])
|
||||
->condition('n.nid', $row->getSourceProperty('nid'));
|
||||
$query->join('node', 'n', static::JOIN);
|
||||
$query->innerJoin('term_data', 'td', 'td.tid = tn.tid AND td.vid = :vid', [':vid' => $this->configuration['vid']]);
|
||||
$row->setSourceProperty('tid', $query->execute()->fetchCol());
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
$ids['vid']['alias'] = 'tn';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
/**
|
||||
* Source returning tids from the term_node table for the non-current revision.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_term_node_revision",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class TermNodeRevision extends TermNode {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
const JOIN = 'tn.nid = n.nid AND tn.vid != n.vid';
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Drupal 6 vocabularies source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_taxonomy_vocabulary",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class Vocabulary extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('vocabulary', 'v')
|
||||
->fields('v', [
|
||||
'vid',
|
||||
'name',
|
||||
'description',
|
||||
'help',
|
||||
'relations',
|
||||
'hierarchy',
|
||||
'multiple',
|
||||
'required',
|
||||
'tags',
|
||||
'module',
|
||||
'weight',
|
||||
]);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'vid' => $this->t('The vocabulary ID.'),
|
||||
'name' => $this->t('The name of the vocabulary.'),
|
||||
'description' => $this->t('The description of the vocabulary.'),
|
||||
'help' => $this->t('Help text to display for the vocabulary.'),
|
||||
'relations' => $this->t('Whether or not related terms are enabled within the vocabulary. (0 = disabled, 1 = enabled)'),
|
||||
'hierarchy' => $this->t('The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)'),
|
||||
'multiple' => $this->t('Whether or not multiple terms from this vocabulary may be assigned to a node. (0 = disabled, 1 = enabled)'),
|
||||
'required' => $this->t('Whether or not terms are required for nodes using this vocabulary. (0 = disabled, 1 = enabled)'),
|
||||
'tags' => $this->t('Whether or not free tagging is enabled for the vocabulary. (0 = disabled, 1 = enabled)'),
|
||||
'weight' => $this->t('The weight of the vocabulary in relation to other vocabularies.'),
|
||||
'parents' => $this->t("The Drupal term IDs of the term's parents."),
|
||||
'node_types' => $this->t('The names of the node types the vocabulary may be used with.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// Find node types for this row.
|
||||
$node_types = $this->select('vocabulary_node_types', 'nt')
|
||||
->fields('nt', ['type', 'vid'])
|
||||
->condition('vid', $row->getSourceProperty('vid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$row->setSourceProperty('node_types', $node_types);
|
||||
$row->setSourceProperty('cardinality', ($row->getSourceProperty('tags') == 1 || $row->getSourceProperty('multiple') == 1) ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : 1);
|
||||
|
||||
// If the vocabulary being migrated is the one defined in the
|
||||
// 'forum_nav_vocabulary' variable, set the 'forum_vocabulary' source
|
||||
// property to true so we know this is the vocabulary used by Forum.
|
||||
if ($this->variableGet('forum_nav_vocabulary', 0) == $row->getSourceProperty('vid')) {
|
||||
$row->setSourceProperty('forum_vocabulary', TRUE);
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Gets all the vocabularies based on the node types that have Taxonomy enabled.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_taxonomy_vocabulary_per_type",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class VocabularyPerType extends Vocabulary {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = parent::query();
|
||||
$query->join('vocabulary_node_types', 'nt', 'v.vid = nt.vid');
|
||||
$query->fields('nt', ['type']);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// Get the i18n taxonomy translation setting for this vocabulary.
|
||||
// 0 - No multilingual options
|
||||
// 1 - Localizable terms. Run through the localization system.
|
||||
// 2 - Predefined language for a vocabulary and its terms.
|
||||
// 3 - Per-language terms, translatable (referencing terms with different
|
||||
// languages) but not localizable.
|
||||
$i18ntaxonomy_vocab = $this->variableGet('i18ntaxonomy_vocabulary', []);
|
||||
$vid = $row->getSourceProperty('vid');
|
||||
$i18ntaxonomy_vocabulary = FALSE;
|
||||
if (array_key_exists($vid, $i18ntaxonomy_vocab)) {
|
||||
$i18ntaxonomy_vocabulary = $i18ntaxonomy_vocab[$vid];
|
||||
}
|
||||
$row->setSourceProperty('i18ntaxonomy_vocabulary', $i18ntaxonomy_vocabulary);
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
$ids['vid']['alias'] = 'nt';
|
||||
$ids['type']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 6 vocabulary translations from source database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_taxonomy_vocabulary_translation",
|
||||
* source_module = "i18ntaxonomy"
|
||||
* )
|
||||
*/
|
||||
class VocabularyTranslation extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('vocabulary', 'v')
|
||||
->fields('v')
|
||||
->fields('i18n', ['lid', 'type', 'property', 'objectid'])
|
||||
->fields('lt', ['lid', 'translation'])
|
||||
->condition('i18n.type', 'vocabulary');
|
||||
$query->addField('lt', 'language', 'lt.language');
|
||||
// The i18n_strings table has two columns containing the object ID, objectid
|
||||
// and objectindex. The objectid column is a text field. Therefore, for the
|
||||
// join to work in PostgreSQL, use the objectindex field as this is numeric
|
||||
// like the vid field.
|
||||
$query->join('i18n_strings', 'i18n', 'v.vid = i18n.objectindex');
|
||||
$query->innerJoin('locales_target', 'lt', 'lt.lid = i18n.lid');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'vid' => $this->t('The vocabulary ID.'),
|
||||
'language' => $this->t('Language for this field.'),
|
||||
'property' => $this->t('Name of property being translated.'),
|
||||
'translation' => $this->t('Translation of either the title or explanation.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// For ease of reading the migration use 'language' as the property name for
|
||||
// the language.
|
||||
$language = $row->getSourceProperty('ltlanguage');
|
||||
$row->setSourceProperty('language', $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
$ids['language']['type'] = 'string';
|
||||
$ids['language']['alias'] = 'lt';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
|
||||
|
||||
/**
|
||||
* Taxonomy term source from database.
|
||||
*
|
||||
* @todo Support term_relation, term_synonym table if possible.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_taxonomy_term",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class Term extends FieldableEntity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('taxonomy_term_data', 'td')
|
||||
->fields('td')
|
||||
->distinct()
|
||||
->orderBy('tid');
|
||||
$query->leftJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
|
||||
$query->addField('tv', 'machine_name');
|
||||
|
||||
if ($this->getDatabase()
|
||||
->schema()
|
||||
->fieldExists('taxonomy_vocabulary', 'i18n_mode')) {
|
||||
$query->addField('tv', 'i18n_mode');
|
||||
}
|
||||
|
||||
if (isset($this->configuration['bundle'])) {
|
||||
$query->condition('tv.machine_name', (array) $this->configuration['bundle'], 'IN');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'tid' => $this->t('The term ID.'),
|
||||
'vid' => $this->t('Existing term VID'),
|
||||
'machine_name' => $this->t('Vocabulary machine name'),
|
||||
'name' => $this->t('The name of the term.'),
|
||||
'description' => $this->t('The term description.'),
|
||||
'weight' => $this->t('Weight'),
|
||||
'parent' => $this->t("The Drupal term IDs of the term's parents."),
|
||||
'format' => $this->t("Format of the term description."),
|
||||
];
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$tid = $row->getSourceProperty('tid');
|
||||
$vocabulary = $row->getSourceProperty('machine_name');
|
||||
$default_language = (array) $this->variableGet('language_default', ['language' => 'en']);
|
||||
|
||||
// If this entity was translated using Entity Translation, we need to get
|
||||
// its source language to get the field values in the right language.
|
||||
// The translations will be migrated by the d7_node_entity_translation
|
||||
// migration.
|
||||
$translatable_vocabularies = array_keys(array_filter($this->variableGet('entity_translation_taxonomy', [])));
|
||||
$entity_translatable = $this->isEntityTranslatable('taxonomy_term') && in_array($vocabulary, $translatable_vocabularies, TRUE);
|
||||
|
||||
if ($entity_translatable) {
|
||||
$source_language = $this->getEntityTranslationSourceLanguage('taxonomy_term', $tid);
|
||||
$language = $entity_translatable && $source_language ? $source_language : $default_language['language'];
|
||||
}
|
||||
// If this is an i18n translation use the default language when i18n_mode
|
||||
// is localized.
|
||||
if ($row->get('i18n_mode')) {
|
||||
$language = ($row->get('i18n_mode') === '1') ? $default_language['language'] : $row->get('language');
|
||||
}
|
||||
|
||||
$language = $language ?? $default_language['language'];
|
||||
$row->setSourceProperty('language', $language);
|
||||
|
||||
// Get Field API field values.
|
||||
foreach ($this->getFields('taxonomy_term', $vocabulary) as $field_name => $field) {
|
||||
// Ensure we're using the right language if the entity and the field are
|
||||
// translatable.
|
||||
$field_language = $entity_translatable && $field['translatable'] ? $language : NULL;
|
||||
$row->setSourceProperty($field_name, $this->getFieldValues('taxonomy_term', $field_name, $tid, NULL, $field_language));
|
||||
}
|
||||
|
||||
// Find parents for this row.
|
||||
$parents = $this->select('taxonomy_term_hierarchy', 'th')
|
||||
->fields('th', ['parent', 'tid'])
|
||||
->condition('tid', $row->getSourceProperty('tid'))
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$row->setSourceProperty('parent', $parents);
|
||||
|
||||
// Determine if this is a forum container.
|
||||
$forum_container_tids = $this->variableGet('forum_containers', []);
|
||||
$current_tid = $row->getSourceProperty('tid');
|
||||
$row->setSourceProperty('is_container', in_array($current_tid, $forum_container_tids));
|
||||
|
||||
// If the term name or term description were replaced by real fields using
|
||||
// the Drupal 7 Title module, use the fields value instead of the term name
|
||||
// or term description.
|
||||
if ($this->moduleExists('title')) {
|
||||
$name_field = $row->getSourceProperty('name_field');
|
||||
if (isset($name_field[0]['value'])) {
|
||||
$row->setSourceProperty('name', $name_field[0]['value']);
|
||||
}
|
||||
$description_field = $row->getSourceProperty('description_field');
|
||||
if (isset($description_field[0]['value'])) {
|
||||
$row->setSourceProperty('description', $description_field[0]['value']);
|
||||
}
|
||||
if (isset($description_field[0]['format'])) {
|
||||
$row->setSourceProperty('format', $description_field[0]['format']);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['tid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
|
||||
|
||||
/**
|
||||
* Provides Drupal 7 taxonomy term entity translation source plugin.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_taxonomy_term_entity_translation",
|
||||
* source_module = "entity_translation"
|
||||
* )
|
||||
*/
|
||||
class TermEntityTranslation extends FieldableEntity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('entity_translation', 'et')
|
||||
->fields('et')
|
||||
->fields('td', [
|
||||
'name',
|
||||
'description',
|
||||
'format',
|
||||
])
|
||||
->fields('tv', [
|
||||
'machine_name',
|
||||
])
|
||||
->condition('et.entity_type', 'taxonomy_term')
|
||||
->condition('et.source', '', '<>');
|
||||
|
||||
$query->innerJoin('taxonomy_term_data', 'td', 'td.tid = et.entity_id');
|
||||
$query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
|
||||
|
||||
if (isset($this->configuration['bundle'])) {
|
||||
$query->condition('tv.machine_name', (array) $this->configuration['bundle'], 'IN');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$tid = $row->getSourceProperty('entity_id');
|
||||
$vocabulary = $row->getSourceProperty('machine_name');
|
||||
$language = $row->getSourceProperty('language');
|
||||
|
||||
// Get Field API field values.
|
||||
foreach ($this->getFields('taxonomy_term', $vocabulary) as $field_name => $field) {
|
||||
// Ensure we're using the right language if the entity is translatable.
|
||||
$field_language = $field['translatable'] ? $language : NULL;
|
||||
$row->setSourceProperty($field_name, $this->getFieldValues('taxonomy_term', $field_name, $tid, NULL, $field_language));
|
||||
}
|
||||
|
||||
// If the term name or term description were replaced by real fields using
|
||||
// the Drupal 7 Title module, use the fields value instead of the term name
|
||||
// or term description.
|
||||
if ($this->moduleExists('title')) {
|
||||
$name_field = $row->getSourceProperty('name_field');
|
||||
if (isset($name_field[0]['value'])) {
|
||||
$row->setSourceProperty('name', $name_field[0]['value']);
|
||||
}
|
||||
$description_field = $row->getSourceProperty('description_field');
|
||||
if (isset($description_field[0]['value'])) {
|
||||
$row->setSourceProperty('description', $description_field[0]['value']);
|
||||
}
|
||||
if (isset($description_field[0]['format'])) {
|
||||
$row->setSourceProperty('format', $description_field[0]['format']);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if this is a forum container.
|
||||
$forum_container_tids = $this->variableGet('forum_containers', []);
|
||||
$row->setSourceProperty('is_container', in_array($tid, $forum_container_tids));
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'entity_type' => $this->t('The entity type this translation relates to'),
|
||||
'entity_id' => $this->t('The entity ID this translation relates to'),
|
||||
'revision_id' => $this->t('The entity revision ID this translation relates to'),
|
||||
'language' => $this->t('The target language for this translation.'),
|
||||
'source' => $this->t('The source language from which this translation was created.'),
|
||||
'uid' => $this->t('The author of this translation.'),
|
||||
'status' => $this->t('Boolean indicating whether the translation is published (visible to non-administrators).'),
|
||||
'translate' => $this->t('A boolean indicating whether this translation needs to be updated.'),
|
||||
'created' => $this->t('The Unix timestamp when the translation was created.'),
|
||||
'changed' => $this->t('The Unix timestamp when the translation was most recently saved.'),
|
||||
'name' => $this->t('The name of the term.'),
|
||||
'description' => $this->t('The term description.'),
|
||||
'format' => $this->t('Format of the term description.'),
|
||||
'machine_name' => $this->t('Vocabulary machine name'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [
|
||||
'entity_id' => [
|
||||
'type' => 'integer',
|
||||
'alias' => 'et',
|
||||
],
|
||||
'language' => [
|
||||
'type' => 'string',
|
||||
'alias' => 'et',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Gets i18n taxonomy terms from source database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_term_localized_translation",
|
||||
* source_module = "i18n_taxonomy"
|
||||
* )
|
||||
*/
|
||||
class TermLocalizedTranslation extends Term {
|
||||
|
||||
use I18nQueryTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// Ideally, the query would return rows for each language for each taxonomy
|
||||
// term with the translations for both the name and description or just the
|
||||
// name translation or just the description translation. That query quickly
|
||||
// became complex and would be difficult to maintain.
|
||||
// Therefore, build a query based on i18nstrings table where each row has
|
||||
// the translation for only one property, either name or description. The
|
||||
// method prepareRow() is then used to obtain the translation for the other
|
||||
// property.
|
||||
$query = parent::query();
|
||||
$query->addField('td', 'language', 'td.language');
|
||||
|
||||
// Add in the property, which is either name or description.
|
||||
// Cast td.tid as char for PostgreSQL compatibility.
|
||||
$query->leftJoin('i18n_string', 'i18n', 'CAST(td.tid AS CHAR(255)) = i18n.objectid');
|
||||
$query->condition('i18n.type', 'term');
|
||||
$query->addField('i18n', 'lid');
|
||||
$query->addField('i18n', 'property');
|
||||
|
||||
// Add in the translation for the property.
|
||||
$query->innerJoin('locales_target', 'lt', 'i18n.lid = lt.lid');
|
||||
$query->addField('lt', 'language', 'lt.language');
|
||||
$query->addField('lt', 'translation');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$language = $row->getSourceProperty('ltlanguage');
|
||||
$tid = $row->getSourceProperty('tid');
|
||||
|
||||
// If this row has been migrated it is a duplicate then skip it.
|
||||
if ($this->idMap->lookupDestinationIds(['tid' => $tid, 'language' => $language])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Save the translation for the property already in the row.
|
||||
$property_in_row = $row->getSourceProperty('property');
|
||||
$row->setSourceProperty($property_in_row . '_translated', $row->getSourceProperty('translation'));
|
||||
|
||||
// Get the translation for the property not already in the row and save it
|
||||
// in the row.
|
||||
$property_not_in_row = ($property_in_row == 'name') ? 'description' : 'name';
|
||||
|
||||
// Get the translation, if one exists, for the property not already in the
|
||||
// row.
|
||||
$query = $this->select('i18n_string', 'i18n')
|
||||
->fields('i18n', ['lid'])
|
||||
->condition('i18n.property', $property_not_in_row);
|
||||
$query->leftJoin('locales_target', 'lt', 'i18n.lid = lt.lid');
|
||||
$query->condition('lt.language', $language);
|
||||
$query->addField('lt', 'translation');
|
||||
$results = $query->execute()->fetchAssoc();
|
||||
if (!$results) {
|
||||
$row->setSourceProperty($property_not_in_row . '_translated', NULL);
|
||||
}
|
||||
else {
|
||||
$row->setSourceProperty($property_not_in_row . '_translated', $results['translation']);
|
||||
}
|
||||
parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'language' => $this->t('Language for this term.'),
|
||||
'name_translated' => $this->t('Term name translation.'),
|
||||
'description_translated' => $this->t('Term description translation.'),
|
||||
];
|
||||
return parent::fields() + $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['language']['type'] = 'string';
|
||||
$ids['language']['alias'] = 'lt';
|
||||
return parent::getIds() + $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Gets i18n taxonomy terms from source database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_taxonomy_term_translation",
|
||||
* source_module = "i18n_taxonomy"
|
||||
* )
|
||||
*/
|
||||
class TermTranslation extends Term {
|
||||
|
||||
use I18nQueryTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = parent::query();
|
||||
if ($this->database->schema()->fieldExists('taxonomy_term_data', 'language')) {
|
||||
$query->addField('td', 'language', 'td_language');
|
||||
}
|
||||
if ($this->database->schema()->fieldExists('taxonomy_vocabulary', 'i18n_mode')) {
|
||||
$query->addField('tv', 'i18n_mode');
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
parent::prepareRow($row);
|
||||
$row->setSourceProperty('language', $row->getSourceProperty('td_language'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'language' => $this->t('Language for this term.'),
|
||||
'name_translated' => $this->t('Term name translation.'),
|
||||
'description_translated' => $this->t('Term description translation.'),
|
||||
];
|
||||
return parent::fields() + $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['language']['type'] = 'string';
|
||||
$ids['language']['alias'] = 'td';
|
||||
return parent::getIds() + $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 7 vocabularies source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_taxonomy_vocabulary",
|
||||
* source_module = "taxonomy"
|
||||
* )
|
||||
*/
|
||||
class Vocabulary extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('taxonomy_vocabulary', 'v')
|
||||
->fields('v', [
|
||||
'vid',
|
||||
'name',
|
||||
'description',
|
||||
'hierarchy',
|
||||
'module',
|
||||
'weight',
|
||||
'machine_name',
|
||||
]);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'vid' => $this->t('The vocabulary ID.'),
|
||||
'name' => $this->t('The name of the vocabulary.'),
|
||||
'description' => $this->t('The description of the vocabulary.'),
|
||||
'hierarchy' => $this->t('The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)'),
|
||||
'module' => $this->t('Module responsible for the vocabulary.'),
|
||||
'weight' => $this->t('The weight of the vocabulary in relation to other vocabularies.'),
|
||||
'machine_name' => $this->t('Unique machine name of the vocabulary.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
// If the vocabulary being migrated is the one defined in the
|
||||
// 'forum_nav_vocabulary' variable, set the 'forum_vocabulary' source
|
||||
// property to true so we know this is the vocabulary used by Forum.
|
||||
if ($this->variableGet('forum_nav_vocabulary', 0) == $row->getSourceProperty('vid')) {
|
||||
$row->setSourceProperty('forum_vocabulary', TRUE);
|
||||
}
|
||||
|
||||
return parent::prepareRow($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['vid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\migrate\source\d7;
|
||||
|
||||
/**
|
||||
* Drupal 7 vocabulary translations from source database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_taxonomy_vocabulary_translation",
|
||||
* source_module = "i18n_taxonomy"
|
||||
* )
|
||||
*/
|
||||
class VocabularyTranslation extends Vocabulary {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = parent::query();
|
||||
$query->leftjoin('i18n_string', 'i18n', 'CAST (v.vid AS CHAR(222))= i18n.objectid');
|
||||
$query->innerJoin('locales_target', 'lt', 'lt.lid = i18n.lid');
|
||||
$query
|
||||
->condition('type', 'vocabulary')
|
||||
->fields('lt')
|
||||
->fields('i18n');
|
||||
$query->addField('lt', 'lid', 'lt_lid');
|
||||
|
||||
if ($this->getDatabase()
|
||||
->schema()
|
||||
->fieldExists('taxonomy_vocabulary', 'language')) {
|
||||
$query->addField('v', 'language', 'v_language');
|
||||
}
|
||||
if ($this->getDatabase()
|
||||
->schema()
|
||||
->fieldExists('taxonomy_vocabulary', 'i18n_mode')) {
|
||||
$query->addField('v', 'i18n_mode');
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'i18n_mode' => $this->t('Internationalization mode.'),
|
||||
'v_language' => $this->t('Language from the taxonomy_vocabulary table.'),
|
||||
'property' => $this->t('Name of property being translated.'),
|
||||
'type' => $this->t('Name of property being translated.'),
|
||||
'objectid' => $this->t('Name of property being translated.'),
|
||||
'lt_lid' => $this->t('Name of property being translated.'),
|
||||
'translation' => $this->t('Translation of either the name or the description.'),
|
||||
'lid' => $this->t('Language string ID'),
|
||||
'textgroup' => $this->t('A module defined group of translations'),
|
||||
'context' => $this->t('Full string ID for quick search: type:objectid:property.'),
|
||||
'objectindex' => $this->t('Integer value of Object ID'),
|
||||
'format' => $this->t('The {filter_format}.format of the string'),
|
||||
'language' => $this->t('Language code from locales_target table'),
|
||||
'plid' => $this->t('Parent lid'),
|
||||
'plural' => $this->t('Plural index number'),
|
||||
'i18n_status' => $this->t('Translation needs update'),
|
||||
];
|
||||
return parent::fields() + $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids = [];
|
||||
$ids['language']['type'] = 'string';
|
||||
$ids['language']['alias'] = 'lt';
|
||||
$ids['property']['type'] = 'string';
|
||||
return parent::getIds() + $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument;
|
||||
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\views\Plugin\views\argument\ManyToOne;
|
||||
|
||||
/**
|
||||
* Allow taxonomy term ID(s) as argument.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*
|
||||
* @ViewsArgument("taxonomy_index_tid")
|
||||
*/
|
||||
class IndexTid extends ManyToOne {
|
||||
|
||||
public function titleQuery() {
|
||||
$titles = [];
|
||||
$terms = Term::loadMultiple($this->value);
|
||||
foreach ($terms as $term) {
|
||||
$titles[] = \Drupal::service('entity.repository')->getTranslationFromContext($term)->label();
|
||||
}
|
||||
return $titles;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Argument handler for taxonomy terms with depth.
|
||||
*
|
||||
* This handler is actually part of the node table and has some restrictions,
|
||||
* because it uses a subquery to find nodes with.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*
|
||||
* @ViewsArgument("taxonomy_index_tid_depth")
|
||||
*/
|
||||
class IndexTidDepth extends ArgumentPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $termStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $termStorage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->termStorage = $termStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_term')
|
||||
);
|
||||
}
|
||||
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['depth'] = ['default' => 0];
|
||||
$options['break_phrase'] = ['default' => FALSE];
|
||||
$options['use_taxonomy_term_path'] = ['default' => FALSE];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['depth'] = [
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Depth'),
|
||||
'#default_value' => $this->options['depth'],
|
||||
'#description' => $this->t('The depth will match nodes tagged with terms in the hierarchy. For example, if you have the term "fruit" and a child term "apple", with a depth of 1 (or higher) then filtering for the term "fruit" will get nodes that are tagged with "apple" as well as "fruit". If negative, the reverse is true; searching for "apple" will also pick up nodes tagged with "fruit" if depth is -1 (or lower).'),
|
||||
];
|
||||
|
||||
$form['break_phrase'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Allow multiple values'),
|
||||
'#description' => $this->t('If selected, users can enter multiple values in the form of 1+2+3. Due to the number of JOINs it would require, AND will be treated as OR with this filter.'),
|
||||
'#default_value' => !empty($this->options['break_phrase']),
|
||||
];
|
||||
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override defaultActions() to remove summary actions.
|
||||
*/
|
||||
protected function defaultActions($which = NULL) {
|
||||
if ($which) {
|
||||
if (in_array($which, ['ignore', 'not found', 'empty', 'default'])) {
|
||||
return parent::defaultActions($which);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$actions = parent::defaultActions();
|
||||
unset($actions['summary asc']);
|
||||
unset($actions['summary desc']);
|
||||
unset($actions['summary asc by count']);
|
||||
unset($actions['summary desc by count']);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
public function query($group_by = FALSE) {
|
||||
$this->ensureMyTable();
|
||||
|
||||
if (!empty($this->options['break_phrase'])) {
|
||||
$break = static::breakString($this->argument);
|
||||
if ($break->value === [-1]) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$operator = (count($break->value) > 1) ? 'IN' : '=';
|
||||
$tids = $break->value;
|
||||
}
|
||||
else {
|
||||
$operator = "=";
|
||||
$tids = $this->argument;
|
||||
}
|
||||
// Now build the subqueries.
|
||||
$subquery = Database::getConnection()->select('taxonomy_index', 'tn');
|
||||
$subquery->addField('tn', 'nid');
|
||||
$where = (new Condition('OR'))->condition('tn.tid', $tids, $operator);
|
||||
$last = "tn";
|
||||
|
||||
if ($this->options['depth'] > 0) {
|
||||
$subquery->leftJoin('taxonomy_term__parent', 'th', "th.entity_id = tn.tid");
|
||||
$last = "th";
|
||||
foreach (range(1, abs($this->options['depth'])) as $count) {
|
||||
$subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.parent_target_id = th$count.entity_id");
|
||||
$where->condition("th$count.entity_id", $tids, $operator);
|
||||
$last = "th$count";
|
||||
}
|
||||
}
|
||||
elseif ($this->options['depth'] < 0) {
|
||||
foreach (range(1, abs($this->options['depth'])) as $count) {
|
||||
$field = $count == 1 ? 'tid' : 'entity_id';
|
||||
$subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.$field = th$count.parent_target_id");
|
||||
$where->condition("th$count.entity_id", $tids, $operator);
|
||||
$last = "th$count";
|
||||
}
|
||||
}
|
||||
|
||||
$subquery->condition($where);
|
||||
$this->query->addWhere(0, "$this->tableAlias.$this->realField", $subquery, 'IN');
|
||||
}
|
||||
|
||||
public function title() {
|
||||
$term = $this->termStorage->load($this->argument);
|
||||
if (!empty($term)) {
|
||||
return $term->getName();
|
||||
}
|
||||
// TODO review text
|
||||
return $this->t('No name');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
|
||||
|
||||
/**
|
||||
* Argument handler for to modify depth for a previous term.
|
||||
*
|
||||
* This handler is actually part of the node table and has some restrictions,
|
||||
* because it uses a subquery to find nodes with.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*
|
||||
* @ViewsArgument("taxonomy_index_tid_depth_modifier")
|
||||
*/
|
||||
class IndexTidDepthModifier extends ArgumentPluginBase {
|
||||
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {}
|
||||
|
||||
public function query($group_by = FALSE) {}
|
||||
|
||||
public function preQuery() {
|
||||
// We don't know our argument yet, but it's based upon our position:
|
||||
$argument = isset($this->view->args[$this->position]) ? $this->view->args[$this->position] : NULL;
|
||||
if (!is_numeric($argument)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($argument > 10) {
|
||||
$argument = 10;
|
||||
}
|
||||
|
||||
if ($argument < -10) {
|
||||
$argument = -10;
|
||||
}
|
||||
|
||||
// figure out which argument preceded us.
|
||||
$keys = array_reverse(array_keys($this->view->argument));
|
||||
$skip = TRUE;
|
||||
foreach ($keys as $key) {
|
||||
if ($key == $this->options['id']) {
|
||||
$skip = FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($this->view->argument[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($handler)) {
|
||||
unset($handler);
|
||||
}
|
||||
|
||||
$handler = &$this->view->argument[$key];
|
||||
if (empty($handler->definition['accept depth modifier'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Finally!
|
||||
$handler->options['depth'] = $argument;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\views\Plugin\views\argument\NumericArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Argument handler for basic taxonomy tid.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*
|
||||
* @ViewsArgument("taxonomy")
|
||||
*/
|
||||
class Taxonomy extends NumericArgument implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $termStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $term_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->termStorage = $term_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_term')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the behavior of title(). Get the title of the node.
|
||||
*/
|
||||
public function title() {
|
||||
// There might be no valid argument.
|
||||
if ($this->argument) {
|
||||
$term = $this->termStorage->load($this->argument);
|
||||
if (!empty($term)) {
|
||||
return $term->getName();
|
||||
}
|
||||
}
|
||||
// TODO review text
|
||||
return $this->t('No name');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument;
|
||||
|
||||
use Drupal\views\Plugin\views\argument\NumericArgument;
|
||||
use Drupal\taxonomy\VocabularyStorageInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Argument handler to accept a vocabulary id.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*
|
||||
* @ViewsArgument("vocabulary_vid")
|
||||
*/
|
||||
class VocabularyVid extends NumericArgument {
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyStorageInterface
|
||||
*/
|
||||
protected $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* Constructs the VocabularyVid object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
|
||||
* The vocabulary storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->vocabularyStorage = $vocabulary_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the behavior of title(). Get the name of the vocabulary.
|
||||
*/
|
||||
public function title() {
|
||||
$vocabulary = $this->vocabularyStorage->load($this->argument);
|
||||
if ($vocabulary) {
|
||||
return $vocabulary->label();
|
||||
}
|
||||
|
||||
return $this->t('No vocabulary');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument_default;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\taxonomy\VocabularyStorageInterface;
|
||||
|
||||
/**
|
||||
* Taxonomy tid default argument.
|
||||
*
|
||||
* @ViewsArgumentDefault(
|
||||
* id = "taxonomy_tid",
|
||||
* title = @Translation("Taxonomy term ID from URL")
|
||||
* )
|
||||
*/
|
||||
class Tid extends ArgumentDefaultPluginBase implements CacheableDependencyInterface {
|
||||
|
||||
/**
|
||||
* The route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyStorageInterface
|
||||
*/
|
||||
protected $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new Tid instance.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition. *
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
|
||||
* The vocabulary storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match, VocabularyStorageInterface $vocabulary_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->routeMatch = $route_match;
|
||||
$this->vocabularyStorage = $vocabulary_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('current_route_match'),
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
// @todo Remove the legacy code.
|
||||
// Convert legacy vids option to machine name vocabularies.
|
||||
if (!empty($this->options['vids'])) {
|
||||
$vocabularies = taxonomy_vocabulary_get_names();
|
||||
foreach ($this->options['vids'] as $vid) {
|
||||
if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
|
||||
$this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['term_page'] = ['default' => TRUE];
|
||||
$options['node'] = ['default' => FALSE];
|
||||
$options['anyall'] = ['default' => ','];
|
||||
$options['limit'] = ['default' => FALSE];
|
||||
$options['vids'] = ['default' => []];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['term_page'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Load default filter from term page'),
|
||||
'#default_value' => $this->options['term_page'],
|
||||
];
|
||||
$form['node'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t("Load default filter from node page, that's good for related taxonomy blocks"),
|
||||
'#default_value' => $this->options['node'],
|
||||
];
|
||||
|
||||
$form['limit'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Limit terms by vocabulary'),
|
||||
'#default_value' => $this->options['limit'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[argument_default][taxonomy_tid][node]"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$options = [];
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
foreach ($vocabularies as $voc) {
|
||||
$options[$voc->id()] = $voc->label();
|
||||
}
|
||||
|
||||
$form['vids'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Vocabularies'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->options['vids'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[argument_default][taxonomy_tid][limit]"]' => ['checked' => TRUE],
|
||||
':input[name="options[argument_default][taxonomy_tid][node]"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$form['anyall'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Multiple-value handling'),
|
||||
'#default_value' => $this->options['anyall'],
|
||||
'#options' => [
|
||||
',' => $this->t('Filter to items that share all terms'),
|
||||
'+' => $this->t('Filter to items that share any term'),
|
||||
],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[argument_default][taxonomy_tid][node]"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitOptionsForm(&$form, FormStateInterface $form_state, &$options = []) {
|
||||
// Filter unselected items so we don't unnecessarily store giant arrays.
|
||||
$options['vids'] = array_filter($options['vids']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getArgument() {
|
||||
// Load default argument from taxonomy page.
|
||||
if (!empty($this->options['term_page'])) {
|
||||
if (($taxonomy_term = $this->routeMatch->getParameter('taxonomy_term')) && $taxonomy_term instanceof TermInterface) {
|
||||
return $taxonomy_term->id();
|
||||
}
|
||||
}
|
||||
// Load default argument from node.
|
||||
if (!empty($this->options['node'])) {
|
||||
// Just check, if a node could be detected.
|
||||
if (($node = $this->routeMatch->getParameter('node')) && $node instanceof NodeInterface) {
|
||||
$taxonomy = [];
|
||||
foreach ($node->getFieldDefinitions() as $field) {
|
||||
if ($field->getType() == 'entity_reference' && $field->getSetting('target_type') == 'taxonomy_term') {
|
||||
$taxonomy_terms = $node->{$field->getName()}->referencedEntities();
|
||||
/** @var \Drupal\taxonomy\TermInterface $taxonomy_term */
|
||||
foreach ($taxonomy_terms as $taxonomy_term) {
|
||||
$taxonomy[$taxonomy_term->id()] = $taxonomy_term->bundle();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($this->options['limit'])) {
|
||||
$tids = [];
|
||||
// filter by vocabulary
|
||||
foreach ($taxonomy as $tid => $vocab) {
|
||||
if (!empty($this->options['vids'][$vocab])) {
|
||||
$tids[] = $tid;
|
||||
}
|
||||
}
|
||||
return implode($this->options['anyall'], $tids);
|
||||
}
|
||||
// Return all tids.
|
||||
else {
|
||||
return implode($this->options['anyall'], array_keys($taxonomy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return Cache::PERMANENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return ['url'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
foreach ($this->vocabularyStorage->loadMultiple(array_keys($this->options['vids'])) as $vocabulary) {
|
||||
$dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
|
||||
}
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument_validator;
|
||||
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\argument_validator\Entity;
|
||||
|
||||
/**
|
||||
* Adds legacy vocabulary handling to standard Entity Argument validation..
|
||||
*/
|
||||
class Term extends Entity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
// @todo Remove the legacy code.
|
||||
// Convert legacy vids option to machine name vocabularies.
|
||||
if (!empty($this->options['vids'])) {
|
||||
$vocabularies = taxonomy_vocabulary_get_names();
|
||||
foreach ($this->options['vids'] as $vid) {
|
||||
if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
|
||||
$this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\argument_validator;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Plugin\views\argument_validator\Entity;
|
||||
|
||||
/**
|
||||
* Validates whether a term name is a valid term argument.
|
||||
*
|
||||
* @ViewsArgumentValidator(
|
||||
* id = "taxonomy_term_name",
|
||||
* title = @Translation("Taxonomy term name"),
|
||||
* entity_type = "taxonomy_term"
|
||||
* )
|
||||
*/
|
||||
class TermName extends Entity {
|
||||
|
||||
/**
|
||||
* The taxonomy term storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermStorageInterface
|
||||
*/
|
||||
protected $termStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_type_bundle_info);
|
||||
// Not handling exploding term names.
|
||||
$this->multipleCapable = FALSE;
|
||||
$this->termStorage = $entity_type_manager->getStorage('taxonomy_term');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['transform'] = ['default' => FALSE];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
|
||||
$form['transform'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Transform dashes in URL to spaces in term name filter values'),
|
||||
'#default_value' => $this->options['transform'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateArgument($argument) {
|
||||
if ($this->options['transform']) {
|
||||
$argument = str_replace('-', ' ', $argument);
|
||||
$this->argument->argument = $argument;
|
||||
}
|
||||
$terms = $this->termStorage->loadByProperties(['name' => $argument]);
|
||||
|
||||
if (!$terms) {
|
||||
// Returned empty array no terms with the name.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Not knowing which term will be used if more than one is returned check
|
||||
// each one.
|
||||
foreach ($terms as $term) {
|
||||
if (!$this->validateEntity($term)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\field;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\field\PrerenderList;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\taxonomy\VocabularyStorageInterface;
|
||||
|
||||
/**
|
||||
* Field handler to display all taxonomy terms of a node.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*
|
||||
* @ViewsField("taxonomy_index_tid")
|
||||
*/
|
||||
class TaxonomyIndexTid extends PrerenderList {
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyStorageInterface
|
||||
*/
|
||||
protected $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* Constructs a TaxonomyIndexTid object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
|
||||
* The vocabulary storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->vocabularyStorage = $vocabulary_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
// @todo: Wouldn't it be possible to use $this->base_table and no if here?
|
||||
if ($view->storage->get('base_table') == 'node_field_revision') {
|
||||
$this->additional_fields['nid'] = ['table' => 'node_field_revision', 'field' => 'nid'];
|
||||
}
|
||||
else {
|
||||
$this->additional_fields['nid'] = ['table' => 'node_field_data', 'field' => 'nid'];
|
||||
}
|
||||
}
|
||||
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['link_to_taxonomy'] = ['default' => TRUE];
|
||||
$options['limit'] = ['default' => FALSE];
|
||||
$options['vids'] = ['default' => []];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide "link to term" option.
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['link_to_taxonomy'] = [
|
||||
'#title' => $this->t('Link this field to its term page'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !empty($this->options['link_to_taxonomy']),
|
||||
];
|
||||
|
||||
$form['limit'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Limit terms by vocabulary'),
|
||||
'#default_value' => $this->options['limit'],
|
||||
];
|
||||
|
||||
$options = [];
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
foreach ($vocabularies as $voc) {
|
||||
$options[$voc->id()] = $voc->label();
|
||||
}
|
||||
|
||||
$form['vids'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Vocabularies'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->options['vids'],
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[limit]"]' => ['checked' => TRUE],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this term to the query
|
||||
*/
|
||||
public function query() {
|
||||
$this->addAdditionalFields();
|
||||
}
|
||||
|
||||
public function preRender(&$values) {
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
$this->field_alias = $this->aliases['nid'];
|
||||
$nids = [];
|
||||
foreach ($values as $result) {
|
||||
if (!empty($result->{$this->aliases['nid']})) {
|
||||
$nids[] = $result->{$this->aliases['nid']};
|
||||
}
|
||||
}
|
||||
|
||||
if ($nids) {
|
||||
$vocabs = array_filter($this->options['vids']);
|
||||
if (empty($this->options['limit'])) {
|
||||
$vocabs = [];
|
||||
}
|
||||
$result = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getNodeTerms($nids, $vocabs);
|
||||
|
||||
foreach ($result as $node_nid => $data) {
|
||||
foreach ($data as $tid => $term) {
|
||||
$this->items[$node_nid][$tid]['name'] = \Drupal::service('entity.repository')->getTranslationFromContext($term)->label();
|
||||
$this->items[$node_nid][$tid]['tid'] = $tid;
|
||||
$this->items[$node_nid][$tid]['vocabulary_vid'] = $term->bundle();
|
||||
$this->items[$node_nid][$tid]['vocabulary'] = $vocabularies[$term->bundle()]->label();
|
||||
|
||||
if (!empty($this->options['link_to_taxonomy'])) {
|
||||
$this->items[$node_nid][$tid]['make_link'] = TRUE;
|
||||
$this->items[$node_nid][$tid]['path'] = 'taxonomy/term/' . $tid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function render_item($count, $item) {
|
||||
return $item['name'];
|
||||
}
|
||||
|
||||
protected function documentSelfTokens(&$tokens) {
|
||||
$tokens['{{ ' . $this->options['id'] . '__tid' . ' }}'] = $this->t('The taxonomy term ID for the term.');
|
||||
$tokens['{{ ' . $this->options['id'] . '__name' . ' }}'] = $this->t('The taxonomy term name for the term.');
|
||||
$tokens['{{ ' . $this->options['id'] . '__vocabulary_vid' . ' }}'] = $this->t('The machine name for the vocabulary the term belongs to.');
|
||||
$tokens['{{ ' . $this->options['id'] . '__vocabulary' . ' }}'] = $this->t('The name for the vocabulary the term belongs to.');
|
||||
}
|
||||
|
||||
protected function addSelfTokens(&$tokens, $item) {
|
||||
foreach (['tid', 'name', 'vocabulary_vid', 'vocabulary'] as $token) {
|
||||
$tokens['{{ ' . $this->options['id'] . '__' . $token . ' }}'] = isset($item[$token]) ? $item[$token] : '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\field;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\views\Plugin\views\field\EntityField;
|
||||
use Drupal\views\ResultRow;
|
||||
|
||||
/**
|
||||
* Displays taxonomy term names and allows converting spaces to hyphens.
|
||||
*
|
||||
* @ingroup views_field_handlers
|
||||
*
|
||||
* @ViewsField("term_name")
|
||||
*/
|
||||
class TermName extends EntityField {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItems(ResultRow $values) {
|
||||
$items = parent::getItems($values);
|
||||
if ($this->options['convert_spaces']) {
|
||||
foreach ($items as &$item) {
|
||||
// Replace spaces with hyphens.
|
||||
$name = $item['raw']->get('value')->getValue();
|
||||
// @todo Add link support https://www.drupal.org/node/2567745
|
||||
$item['rendered']['#context']['value'] = str_replace(' ', '-', $name);
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['convert_spaces'] = ['default' => FALSE];
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$form['convert_spaces'] = [
|
||||
'#title' => $this->t('Convert spaces in term names to hyphens'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => !empty($this->options['convert_spaces']),
|
||||
];
|
||||
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\filter;
|
||||
|
||||
use Drupal\Core\Entity\Element\EntityAutocomplete;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\taxonomy\TermStorageInterface;
|
||||
use Drupal\taxonomy\VocabularyStorageInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\filter\ManyToOne;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Filter by term id.
|
||||
*
|
||||
* @ingroup views_filter_handlers
|
||||
*
|
||||
* @ViewsFilter("taxonomy_index_tid")
|
||||
*/
|
||||
class TaxonomyIndexTid extends ManyToOne {
|
||||
|
||||
// Stores the exposed input for this filter.
|
||||
public $validated_exposed_input = NULL;
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyStorageInterface
|
||||
*/
|
||||
protected $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* The term storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermStorageInterface
|
||||
*/
|
||||
protected $termStorage;
|
||||
|
||||
/**
|
||||
* Constructs a TaxonomyIndexTid object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
|
||||
* The vocabulary storage.
|
||||
* @param \Drupal\taxonomy\TermStorageInterface $term_storage
|
||||
* The term storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage, TermStorageInterface $term_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->vocabularyStorage = $vocabulary_storage;
|
||||
$this->termStorage = $term_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_vocabulary'),
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_term')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
if (!empty($this->definition['vocabulary'])) {
|
||||
$this->options['vid'] = $this->definition['vocabulary'];
|
||||
}
|
||||
}
|
||||
|
||||
public function hasExtraOptions() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValueOptions() {
|
||||
return $this->valueOptions;
|
||||
}
|
||||
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['type'] = ['default' => 'textfield'];
|
||||
$options['limit'] = ['default' => TRUE];
|
||||
$options['vid'] = ['default' => ''];
|
||||
$options['hierarchy'] = ['default' => FALSE];
|
||||
$options['error_message'] = ['default' => TRUE];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
$options = [];
|
||||
foreach ($vocabularies as $voc) {
|
||||
$options[$voc->id()] = $voc->label();
|
||||
}
|
||||
|
||||
if ($this->options['limit']) {
|
||||
// We only do this when the form is displayed.
|
||||
if (empty($this->options['vid'])) {
|
||||
$first_vocabulary = reset($vocabularies);
|
||||
$this->options['vid'] = $first_vocabulary->id();
|
||||
}
|
||||
|
||||
if (empty($this->definition['vocabulary'])) {
|
||||
$form['vid'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Vocabulary'),
|
||||
'#options' => $options,
|
||||
'#description' => $this->t('Select which vocabulary to show terms for in the regular options.'),
|
||||
'#default_value' => $this->options['vid'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$form['type'] = [
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Selection type'),
|
||||
'#options' => ['select' => $this->t('Dropdown'), 'textfield' => $this->t('Autocomplete')],
|
||||
'#default_value' => $this->options['type'],
|
||||
];
|
||||
|
||||
$form['hierarchy'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Show hierarchy in dropdown'),
|
||||
'#default_value' => !empty($this->options['hierarchy']),
|
||||
'#states' => [
|
||||
'visible' => [
|
||||
':input[name="options[type]"]' => ['value' => 'select'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function valueForm(&$form, FormStateInterface $form_state) {
|
||||
$vocabulary = $this->vocabularyStorage->load($this->options['vid']);
|
||||
if (empty($vocabulary) && $this->options['limit']) {
|
||||
$form['markup'] = [
|
||||
'#markup' => '<div class="js-form-item form-item">' . $this->t('An invalid vocabulary is selected. Please change it in the options.') . '</div>',
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->options['type'] == 'textfield') {
|
||||
$terms = $this->value ? Term::loadMultiple(($this->value)) : [];
|
||||
$form['value'] = [
|
||||
'#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@voc' => $vocabulary->label()]) : $this->t('Select terms'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => EntityAutocomplete::getEntityLabels($terms),
|
||||
];
|
||||
|
||||
if ($this->options['limit']) {
|
||||
$form['value']['#type'] = 'entity_autocomplete';
|
||||
$form['value']['#target_type'] = 'taxonomy_term';
|
||||
$form['value']['#selection_settings']['target_bundles'] = [$vocabulary->id()];
|
||||
$form['value']['#tags'] = TRUE;
|
||||
$form['value']['#process_default_value'] = FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!empty($this->options['hierarchy']) && $this->options['limit']) {
|
||||
$tree = $this->termStorage->loadTree($vocabulary->id(), 0, NULL, TRUE);
|
||||
$options = [];
|
||||
|
||||
if ($tree) {
|
||||
foreach ($tree as $term) {
|
||||
$choice = new \stdClass();
|
||||
$choice->option = [$term->id() => str_repeat('-', $term->depth) . \Drupal::service('entity.repository')->getTranslationFromContext($term)->label()];
|
||||
$options[] = $choice;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$options = [];
|
||||
$query = \Drupal::entityQuery('taxonomy_term')
|
||||
// @todo Sorting on vocabulary properties -
|
||||
// https://www.drupal.org/node/1821274.
|
||||
->sort('weight')
|
||||
->sort('name')
|
||||
->addTag('taxonomy_term_access');
|
||||
if ($this->options['limit']) {
|
||||
$query->condition('vid', $vocabulary->id());
|
||||
}
|
||||
$terms = Term::loadMultiple($query->execute());
|
||||
foreach ($terms as $term) {
|
||||
$options[$term->id()] = \Drupal::service('entity.repository')->getTranslationFromContext($term)->label();
|
||||
}
|
||||
}
|
||||
|
||||
$default_value = (array) $this->value;
|
||||
|
||||
if ($exposed = $form_state->get('exposed')) {
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
|
||||
if (!empty($this->options['expose']['reduce'])) {
|
||||
$options = $this->reduceValueOptions($options);
|
||||
|
||||
if (!empty($this->options['expose']['multiple']) && empty($this->options['expose']['required'])) {
|
||||
$default_value = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->options['expose']['multiple'])) {
|
||||
if (empty($this->options['expose']['required']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) {
|
||||
$default_value = 'All';
|
||||
}
|
||||
elseif (empty($default_value)) {
|
||||
$keys = array_keys($options);
|
||||
$default_value = array_shift($keys);
|
||||
}
|
||||
// Due to #1464174 there is a chance that array('') was saved in the admin ui.
|
||||
// Let's choose a safe default value.
|
||||
elseif ($default_value == ['']) {
|
||||
$default_value = 'All';
|
||||
}
|
||||
else {
|
||||
$copy = $default_value;
|
||||
$default_value = array_shift($copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
$form['value'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->options['limit'] ? $this->t('Select terms from vocabulary @voc', ['@voc' => $vocabulary->label()]) : $this->t('Select terms'),
|
||||
'#multiple' => TRUE,
|
||||
'#options' => $options,
|
||||
'#size' => min(9, count($options)),
|
||||
'#default_value' => $default_value,
|
||||
];
|
||||
|
||||
$user_input = $form_state->getUserInput();
|
||||
if ($exposed && isset($identifier) && !isset($user_input[$identifier])) {
|
||||
$user_input[$identifier] = $default_value;
|
||||
$form_state->setUserInput($user_input);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$form_state->get('exposed')) {
|
||||
// Retain the helper option
|
||||
$this->helper->buildOptionsForm($form, $form_state);
|
||||
|
||||
// Show help text if not exposed to end users.
|
||||
$form['value']['#description'] = t('Leave blank for all. Otherwise, the first selected term will be the default instead of "Any".');
|
||||
}
|
||||
}
|
||||
|
||||
protected function valueValidate($form, FormStateInterface $form_state) {
|
||||
// We only validate if they've chosen the text field style.
|
||||
if ($this->options['type'] != 'textfield') {
|
||||
return;
|
||||
}
|
||||
|
||||
$tids = [];
|
||||
if ($values = $form_state->getValue(['options', 'value'])) {
|
||||
foreach ($values as $value) {
|
||||
$tids[] = $value['target_id'];
|
||||
}
|
||||
}
|
||||
$form_state->setValue(['options', 'value'], $tids);
|
||||
}
|
||||
|
||||
public function acceptExposedInput($input) {
|
||||
if (empty($this->options['exposed'])) {
|
||||
return TRUE;
|
||||
}
|
||||
// We need to know the operator, which is normally set in
|
||||
// \Drupal\views\Plugin\views\filter\FilterPluginBase::acceptExposedInput(),
|
||||
// before we actually call the parent version of ourselves.
|
||||
if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) {
|
||||
$this->operator = $input[$this->options['expose']['operator_id']];
|
||||
}
|
||||
|
||||
// If view is an attachment and is inheriting exposed filters, then assume
|
||||
// exposed input has already been validated
|
||||
if (!empty($this->view->is_attachment) && $this->view->display_handler->usesExposed()) {
|
||||
$this->validated_exposed_input = (array) $this->view->exposed_raw_input[$this->options['expose']['identifier']];
|
||||
}
|
||||
|
||||
// If we're checking for EMPTY or NOT, we don't need any input, and we can
|
||||
// say that our input conditions are met by just having the right operator.
|
||||
if ($this->operator == 'empty' || $this->operator == 'not empty') {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// If it's non-required and there's no value don't bother filtering.
|
||||
if (!$this->options['expose']['required'] && empty($this->validated_exposed_input)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$rc = parent::acceptExposedInput($input);
|
||||
if ($rc) {
|
||||
// If we have previously validated input, override.
|
||||
if (isset($this->validated_exposed_input)) {
|
||||
$this->value = $this->validated_exposed_input;
|
||||
}
|
||||
}
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
||||
public function validateExposed(&$form, FormStateInterface $form_state) {
|
||||
if (empty($this->options['exposed'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$identifier = $this->options['expose']['identifier'];
|
||||
|
||||
// We only validate if they've chosen the text field style.
|
||||
if ($this->options['type'] != 'textfield') {
|
||||
if ($form_state->getValue($identifier) != 'All') {
|
||||
$this->validated_exposed_input = (array) $form_state->getValue($identifier);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($this->options['expose']['identifier'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($values = $form_state->getValue($identifier)) {
|
||||
foreach ($values as $value) {
|
||||
$this->validated_exposed_input[] = $value['target_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function valueSubmit($form, FormStateInterface $form_state) {
|
||||
// prevent array_filter from messing up our arrays in parent submit.
|
||||
}
|
||||
|
||||
public function buildExposeForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildExposeForm($form, $form_state);
|
||||
if ($this->options['type'] != 'select') {
|
||||
unset($form['expose']['reduce']);
|
||||
}
|
||||
$form['error_message'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Display error message'),
|
||||
'#default_value' => !empty($this->options['error_message']),
|
||||
];
|
||||
}
|
||||
|
||||
public function adminSummary() {
|
||||
// set up $this->valueOptions for the parent summary
|
||||
$this->valueOptions = [];
|
||||
|
||||
if ($this->value) {
|
||||
$this->value = array_filter($this->value);
|
||||
$terms = Term::loadMultiple($this->value);
|
||||
foreach ($terms as $term) {
|
||||
$this->valueOptions[$term->id()] = \Drupal::service('entity.repository')->getTranslationFromContext($term)->label();
|
||||
}
|
||||
}
|
||||
return parent::adminSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
$contexts = parent::getCacheContexts();
|
||||
// The result potentially depends on term access and so is just cacheable
|
||||
// per user.
|
||||
// @todo See https://www.drupal.org/node/2352175.
|
||||
$contexts[] = 'user';
|
||||
|
||||
return $contexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
$vocabulary = $this->vocabularyStorage->load($this->options['vid']);
|
||||
$dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
|
||||
|
||||
foreach ($this->termStorage->loadMultiple($this->options['value']) as $term) {
|
||||
$dependencies[$term->getConfigDependencyKey()][] = $term->getConfigDependencyName();
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\filter;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Filter handler for taxonomy terms with depth.
|
||||
*
|
||||
* This handler is actually part of the node table and has some restrictions,
|
||||
* because it uses a subquery to find nodes with.
|
||||
*
|
||||
* @ingroup views_filter_handlers
|
||||
*
|
||||
* @ViewsFilter("taxonomy_index_tid_depth")
|
||||
*/
|
||||
class TaxonomyIndexTidDepth extends TaxonomyIndexTid {
|
||||
|
||||
public function operatorOptions($which = 'title') {
|
||||
return [
|
||||
'or' => $this->t('Is one of'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
|
||||
$options['depth'] = ['default' => 0];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
parent::buildExtraOptionsForm($form, $form_state);
|
||||
|
||||
$form['depth'] = [
|
||||
'#type' => 'weight',
|
||||
'#title' => $this->t('Depth'),
|
||||
'#default_value' => $this->options['depth'],
|
||||
'#description' => $this->t('The depth will match nodes tagged with terms in the hierarchy. For example, if you have the term "fruit" and a child term "apple", with a depth of 1 (or higher) then filtering for the term "fruit" will get nodes that are tagged with "apple" as well as "fruit". If negative, the reverse is true; searching for "apple" will also pick up nodes tagged with "fruit" if depth is -1 (or lower).'),
|
||||
];
|
||||
}
|
||||
|
||||
public function query() {
|
||||
// If no filter values are present, then do nothing.
|
||||
if (count($this->value) == 0) {
|
||||
return;
|
||||
}
|
||||
elseif (count($this->value) == 1) {
|
||||
// Sometimes $this->value is an array with a single element so convert it.
|
||||
if (is_array($this->value)) {
|
||||
$this->value = current($this->value);
|
||||
}
|
||||
$operator = '=';
|
||||
}
|
||||
else {
|
||||
$operator = 'IN';
|
||||
}
|
||||
|
||||
// The normal use of ensureMyTable() here breaks Views.
|
||||
// So instead we trick the filter into using the alias of the base table.
|
||||
// See https://www.drupal.org/node/271833.
|
||||
// If a relationship is set, we must use the alias it provides.
|
||||
if (!empty($this->relationship)) {
|
||||
$this->tableAlias = $this->relationship;
|
||||
}
|
||||
// If no relationship, then use the alias of the base table.
|
||||
else {
|
||||
$this->tableAlias = $this->query->ensureTable($this->view->storage->get('base_table'));
|
||||
}
|
||||
|
||||
// Now build the subqueries.
|
||||
$subquery = Database::getConnection()->select('taxonomy_index', 'tn');
|
||||
$subquery->addField('tn', 'nid');
|
||||
$where = (new Condition('OR'))->condition('tn.tid', $this->value, $operator);
|
||||
$last = "tn";
|
||||
|
||||
if ($this->options['depth'] > 0) {
|
||||
$subquery->leftJoin('taxonomy_term__parent', 'th', "th.entity_id = tn.tid");
|
||||
$last = "th";
|
||||
foreach (range(1, abs($this->options['depth'])) as $count) {
|
||||
$subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.parent_target_id = th$count.entity_id");
|
||||
$where->condition("th$count.entity_id", $this->value, $operator);
|
||||
$last = "th$count";
|
||||
}
|
||||
}
|
||||
elseif ($this->options['depth'] < 0) {
|
||||
foreach (range(1, abs($this->options['depth'])) as $count) {
|
||||
$field = $count == 1 ? 'tid' : 'entity_id';
|
||||
$subquery->leftJoin('taxonomy_term__parent', "th$count", "$last.$field = th$count.parent_target_id");
|
||||
$where->condition("th$count.entity_id", $this->value, $operator);
|
||||
$last = "th$count";
|
||||
}
|
||||
}
|
||||
|
||||
$subquery->condition($where);
|
||||
$this->query->addWhere($this->options['group'], "$this->tableAlias.$this->realField", $subquery, 'IN');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\relationship;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\taxonomy\VocabularyStorageInterface;
|
||||
use Drupal\views\ViewExecutable;
|
||||
use Drupal\views\Plugin\views\display\DisplayPluginBase;
|
||||
use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Relationship handler to return the taxonomy terms of nodes.
|
||||
*
|
||||
* @ingroup views_relationship_handlers
|
||||
*
|
||||
* @ViewsRelationship("node_term_data")
|
||||
*/
|
||||
class NodeTermData extends RelationshipPluginBase {
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyStorageInterface
|
||||
*/
|
||||
protected $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* Constructs a NodeTermData object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
|
||||
* The vocabulary storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, VocabularyStorageInterface $vocabulary_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->vocabularyStorage = $vocabulary_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
|
||||
parent::init($view, $display, $options);
|
||||
|
||||
// @todo Remove the legacy code.
|
||||
// Convert legacy vids option to machine name vocabularies.
|
||||
if (!empty($this->options['vids'])) {
|
||||
$vocabularies = taxonomy_vocabulary_get_names();
|
||||
foreach ($this->options['vids'] as $vid) {
|
||||
if (isset($vocabularies[$vid], $vocabularies[$vid]->machine_name)) {
|
||||
$this->options['vocabularies'][$vocabularies[$vid]->machine_name] = $vocabularies[$vid]->machine_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['vids'] = ['default' => []];
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
$vocabularies = $this->vocabularyStorage->loadMultiple();
|
||||
$options = [];
|
||||
foreach ($vocabularies as $voc) {
|
||||
$options[$voc->id()] = $voc->label();
|
||||
}
|
||||
|
||||
$form['vids'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Vocabularies'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $this->options['vids'],
|
||||
'#description' => $this->t('Choose which vocabularies you wish to relate. Remember that every term found will create a new record, so this relationship is best used on just one vocabulary that has only one term per node.'),
|
||||
];
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
|
||||
// Transform the #type = checkboxes value to a numerically indexed array,
|
||||
// because the config schema expects a sequence, not a mapping.
|
||||
$vids = $form_state->getValue(['options', 'vids']);
|
||||
$form_state->setValue(['options', 'vids'], array_values(array_filter($vids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to implement a relationship in a query.
|
||||
*/
|
||||
public function query() {
|
||||
$this->ensureMyTable();
|
||||
|
||||
$def = $this->definition;
|
||||
$def['table'] = 'taxonomy_term_field_data';
|
||||
|
||||
if (!array_filter($this->options['vids'])) {
|
||||
$taxonomy_index = $this->query->addTable('taxonomy_index', $this->relationship);
|
||||
$def['left_table'] = $taxonomy_index;
|
||||
$def['left_field'] = 'tid';
|
||||
$def['field'] = 'tid';
|
||||
$def['type'] = empty($this->options['required']) ? 'LEFT' : 'INNER';
|
||||
}
|
||||
else {
|
||||
// If vocabularies are supplied join a subselect instead
|
||||
$def['left_table'] = $this->tableAlias;
|
||||
$def['left_field'] = 'nid';
|
||||
$def['field'] = 'nid';
|
||||
$def['type'] = empty($this->options['required']) ? 'LEFT' : 'INNER';
|
||||
$def['adjusted'] = TRUE;
|
||||
|
||||
$query = Database::getConnection()->select('taxonomy_term_field_data', 'td');
|
||||
$query->addJoin($def['type'], 'taxonomy_index', 'tn', 'tn.tid = td.tid');
|
||||
$query->condition('td.vid', array_filter($this->options['vids']), 'IN');
|
||||
$query->addTag('taxonomy_term_access');
|
||||
$query->fields('td');
|
||||
$query->fields('tn', ['nid']);
|
||||
$def['table formula'] = $query;
|
||||
}
|
||||
|
||||
$join = \Drupal::service('plugin.manager.views.join')->createInstance('standard', $def);
|
||||
|
||||
// use a short alias for this:
|
||||
$alias = $def['table'] . '_' . $this->table;
|
||||
|
||||
$this->alias = $this->query->addRelationship($alias, $join, 'taxonomy_term_field_data', $this->relationship);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$dependencies = parent::calculateDependencies();
|
||||
|
||||
foreach ($this->options['vids'] as $vocabulary_id) {
|
||||
if ($vocabulary = $this->vocabularyStorage->load($vocabulary_id)) {
|
||||
$dependencies[$vocabulary->getConfigDependencyKey()][] = $vocabulary->getConfigDependencyName();
|
||||
}
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Plugin\views\wizard;
|
||||
|
||||
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
|
||||
|
||||
/**
|
||||
* Tests creating taxonomy views with the wizard.
|
||||
*
|
||||
* @ViewsWizard(
|
||||
* id = "taxonomy_term",
|
||||
* base_table = "taxonomy_term_field_data",
|
||||
* title = @Translation("Taxonomy terms")
|
||||
* )
|
||||
*/
|
||||
class TaxonomyTerm extends WizardPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defaultDisplayOptions() {
|
||||
$display_options = parent::defaultDisplayOptions();
|
||||
|
||||
// Add permission-based access control.
|
||||
$display_options['access']['type'] = 'perm';
|
||||
$display_options['access']['options']['perm'] = 'access content';
|
||||
|
||||
// Remove the default fields, since we are customizing them here.
|
||||
unset($display_options['fields']);
|
||||
|
||||
/* Field: Taxonomy: Term */
|
||||
$display_options['fields']['name']['id'] = 'name';
|
||||
$display_options['fields']['name']['table'] = 'taxonomy_term_field_data';
|
||||
$display_options['fields']['name']['field'] = 'name';
|
||||
$display_options['fields']['name']['entity_type'] = 'taxonomy_term';
|
||||
$display_options['fields']['name']['entity_field'] = 'name';
|
||||
$display_options['fields']['name']['label'] = '';
|
||||
$display_options['fields']['name']['alter']['alter_text'] = 0;
|
||||
$display_options['fields']['name']['alter']['make_link'] = 0;
|
||||
$display_options['fields']['name']['alter']['absolute'] = 0;
|
||||
$display_options['fields']['name']['alter']['trim'] = 0;
|
||||
$display_options['fields']['name']['alter']['word_boundary'] = 0;
|
||||
$display_options['fields']['name']['alter']['ellipsis'] = 0;
|
||||
$display_options['fields']['name']['alter']['strip_tags'] = 0;
|
||||
$display_options['fields']['name']['alter']['html'] = 0;
|
||||
$display_options['fields']['name']['hide_empty'] = 0;
|
||||
$display_options['fields']['name']['empty_zero'] = 0;
|
||||
$display_options['fields']['name']['type'] = 'string';
|
||||
$display_options['fields']['name']['settings']['link_to_entity'] = 1;
|
||||
$display_options['fields']['name']['plugin_id'] = 'term_name';
|
||||
|
||||
return $display_options;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides dynamic permissions of the taxonomy module.
|
||||
*
|
||||
* @see taxonomy.permissions.yml
|
||||
*/
|
||||
class TaxonomyPermissions implements ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
use DeprecatedServicePropertyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a TaxonomyPermissions instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('entity_type.manager'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get taxonomy permissions.
|
||||
*
|
||||
* @return array
|
||||
* Permissions array.
|
||||
*/
|
||||
public function permissions() {
|
||||
$permissions = [];
|
||||
foreach (Vocabulary::loadMultiple() as $vocabulary) {
|
||||
$permissions += $this->buildPermissions($vocabulary);
|
||||
}
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a standard list of taxonomy term permissions for a given vocabulary.
|
||||
*
|
||||
* @param \Drupal\taxonomy\VocabularyInterface $vocabulary
|
||||
* The vocabulary.
|
||||
*
|
||||
* @return array
|
||||
* An array of permission names and descriptions.
|
||||
*/
|
||||
protected function buildPermissions(VocabularyInterface $vocabulary) {
|
||||
$id = $vocabulary->id();
|
||||
$args = ['%vocabulary' => $vocabulary->label()];
|
||||
|
||||
return [
|
||||
"create terms in $id" => ['title' => $this->t('%vocabulary: Create terms', $args)],
|
||||
"delete terms in $id" => ['title' => $this->t('%vocabulary: Delete terms', $args)],
|
||||
"edit terms in $id" => ['title' => $this->t('%vocabulary: Edit terms', $args)],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines the access control handler for the taxonomy term entity type.
|
||||
*
|
||||
* @see \Drupal\taxonomy\Entity\Term
|
||||
*/
|
||||
class TermAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
if ($account->hasPermission('administer taxonomy')) {
|
||||
return AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
$access_result = AccessResult::allowedIf($account->hasPermission('access content') && $entity->isPublished())
|
||||
->cachePerPermissions()
|
||||
->addCacheableDependency($entity);
|
||||
if (!$access_result->isAllowed()) {
|
||||
$access_result->setReason("The 'access content' permission is required and the taxonomy term must be published.");
|
||||
}
|
||||
return $access_result;
|
||||
|
||||
case 'update':
|
||||
if ($account->hasPermission("edit terms in {$entity->bundle()}")) {
|
||||
return AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
|
||||
return AccessResult::neutral()->setReason("The following permissions are required: 'edit terms in {$entity->bundle()}' OR 'administer taxonomy'.");
|
||||
|
||||
case 'delete':
|
||||
if ($account->hasPermission("delete terms in {$entity->bundle()}")) {
|
||||
return AccessResult::allowed()->cachePerPermissions();
|
||||
}
|
||||
|
||||
return AccessResult::neutral()->setReason("The following permissions are required: 'delete terms in {$entity->bundle()}' OR 'administer taxonomy'.");
|
||||
|
||||
default:
|
||||
// No opinion.
|
||||
return AccessResult::neutral()->cachePerPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
return AccessResult::allowedIfHasPermissions($account, ["create terms in $entity_bundle", 'administer taxonomy'], 'OR');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
|
||||
use Drupal\Core\Breadcrumb\Breadcrumb;
|
||||
use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
|
||||
use Drupal\Core\Entity\EntityRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Provides a custom taxonomy breadcrumb builder that uses the term hierarchy.
|
||||
*/
|
||||
class TermBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||
use StringTranslationTrait;
|
||||
use DeprecatedServicePropertyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
|
||||
|
||||
/**
|
||||
* The entity repository manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityRepositoryInterface
|
||||
*/
|
||||
protected $entityRepository;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The taxonomy storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermStorageInterface
|
||||
*/
|
||||
protected $termStorage;
|
||||
|
||||
/**
|
||||
* Constructs the TermBreadcrumbBuilder.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
|
||||
* The entity repository.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository = NULL) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->termStorage = $entity_type_manager->getStorage('taxonomy_term');
|
||||
if (!$entity_repository) {
|
||||
@trigger_error('The entity.repository service must be passed to TermBreadcrumbBuilder::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/2549139.', E_USER_DEPRECATED);
|
||||
$entity_repository = \Drupal::service('entity.repository');
|
||||
}
|
||||
$this->entityRepository = $entity_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(RouteMatchInterface $route_match) {
|
||||
return $route_match->getRouteName() == 'entity.taxonomy_term.canonical'
|
||||
&& $route_match->getParameter('taxonomy_term') instanceof TermInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(RouteMatchInterface $route_match) {
|
||||
$breadcrumb = new Breadcrumb();
|
||||
$breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>'));
|
||||
$term = $route_match->getParameter('taxonomy_term');
|
||||
// Breadcrumb needs to have terms cacheable metadata as a cacheable
|
||||
// dependency even though it is not shown in the breadcrumb because e.g. its
|
||||
// parent might have changed.
|
||||
$breadcrumb->addCacheableDependency($term);
|
||||
// @todo This overrides any other possible breadcrumb and is a pure
|
||||
// hard-coded presumption. Make this behavior configurable per
|
||||
// vocabulary or term.
|
||||
$parents = $this->termStorage->loadAllParents($term->id());
|
||||
// Remove current term being accessed.
|
||||
array_shift($parents);
|
||||
foreach (array_reverse($parents) as $term) {
|
||||
$term = $this->entityRepository->getTranslationFromContext($term);
|
||||
$breadcrumb->addCacheableDependency($term);
|
||||
$breadcrumb->addLink(Link::createFromRoute($term->getName(), 'entity.taxonomy_term.canonical', ['taxonomy_term' => $term->id()]));
|
||||
}
|
||||
|
||||
// This breadcrumb builder is based on a route parameter, and hence it
|
||||
// depends on the 'route' cache context.
|
||||
$breadcrumb->addCacheContexts(['route']);
|
||||
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Base for handler for taxonomy term edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TermForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$term = $this->entity;
|
||||
$vocab_storage = $this->entityTypeManager->getStorage('taxonomy_vocabulary');
|
||||
/** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */
|
||||
$taxonomy_storage = $this->entityTypeManager->getStorage('taxonomy_term');
|
||||
$vocabulary = $vocab_storage->load($term->bundle());
|
||||
|
||||
$parent = array_keys($taxonomy_storage->loadParents($term->id()));
|
||||
$form_state->set(['taxonomy', 'parent'], $parent);
|
||||
$form_state->set(['taxonomy', 'vocabulary'], $vocabulary);
|
||||
|
||||
$form['relations'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Relations'),
|
||||
'#open' => $taxonomy_storage->getVocabularyHierarchyType($vocabulary->id()) == VocabularyInterface::HIERARCHY_MULTIPLE,
|
||||
'#weight' => 10,
|
||||
];
|
||||
|
||||
// \Drupal\taxonomy\TermStorageInterface::loadTree() and
|
||||
// \Drupal\taxonomy\TermStorageInterface::loadParents() may contain large
|
||||
// numbers of items so we check for taxonomy.settings:override_selector
|
||||
// before loading the full vocabulary. Contrib modules can then intercept
|
||||
// before hook_form_alter to provide scalable alternatives.
|
||||
if (!$this->config('taxonomy.settings')->get('override_selector')) {
|
||||
$exclude = [];
|
||||
if (!$term->isNew()) {
|
||||
$parent = array_keys($taxonomy_storage->loadParents($term->id()));
|
||||
$children = $taxonomy_storage->loadTree($vocabulary->id(), $term->id());
|
||||
|
||||
// A term can't be the child of itself, nor of its children.
|
||||
foreach ($children as $child) {
|
||||
$exclude[] = $child->tid;
|
||||
}
|
||||
$exclude[] = $term->id();
|
||||
}
|
||||
|
||||
$tree = $taxonomy_storage->loadTree($vocabulary->id());
|
||||
$options = ['<' . $this->t('root') . '>'];
|
||||
if (empty($parent)) {
|
||||
$parent = [0];
|
||||
}
|
||||
|
||||
foreach ($tree as $item) {
|
||||
if (!in_array($item->tid, $exclude)) {
|
||||
$options[$item->tid] = str_repeat('-', $item->depth) . $item->name;
|
||||
}
|
||||
}
|
||||
|
||||
$form['relations']['parent'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Parent terms'),
|
||||
'#options' => $options,
|
||||
'#default_value' => $parent,
|
||||
'#multiple' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
$form['relations']['weight'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Weight'),
|
||||
'#size' => 6,
|
||||
'#default_value' => $term->getWeight(),
|
||||
'#description' => $this->t('Terms are displayed in ascending order by weight.'),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
|
||||
$form['vid'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $vocabulary->id(),
|
||||
];
|
||||
|
||||
$form['tid'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $term->id(),
|
||||
];
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
|
||||
// Ensure numeric values.
|
||||
if ($form_state->hasValue('weight') && !is_numeric($form_state->getValue('weight'))) {
|
||||
$form_state->setErrorByName('weight', $this->t('Weight value must be numeric.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildEntity(array $form, FormStateInterface $form_state) {
|
||||
$term = parent::buildEntity($form, $form_state);
|
||||
|
||||
// Prevent leading and trailing spaces in term names.
|
||||
$term->setName(trim($term->getName()));
|
||||
|
||||
// Assign parents with proper delta values starting from 0.
|
||||
$term->parent = array_values($form_state->getValue('parent'));
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditedFieldNames(FormStateInterface $form_state) {
|
||||
return array_merge(['parent', 'weight'], parent::getEditedFieldNames($form_state));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
|
||||
// Manually flag violations of fields not handled by the form display. This
|
||||
// is necessary as entity form displays only flag violations for fields
|
||||
// contained in the display.
|
||||
// @see ::form()
|
||||
foreach ($violations->getByField('parent') as $violation) {
|
||||
$form_state->setErrorByName('parent', $violation->getMessage());
|
||||
}
|
||||
foreach ($violations->getByField('weight') as $violation) {
|
||||
$form_state->setErrorByName('weight', $violation->getMessage());
|
||||
}
|
||||
|
||||
parent::flagViolations($violations, $form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$term = $this->entity;
|
||||
|
||||
$result = $term->save();
|
||||
|
||||
$edit_link = $term->toLink($this->t('Edit'), 'edit-form')->toString();
|
||||
$view_link = $term->toLink()->toString();
|
||||
switch ($result) {
|
||||
case SAVED_NEW:
|
||||
$this->messenger()->addStatus($this->t('Created new term %term.', ['%term' => $view_link]));
|
||||
$this->logger('taxonomy')->notice('Created new term %term.', ['%term' => $term->getName(), 'link' => $edit_link]);
|
||||
break;
|
||||
|
||||
case SAVED_UPDATED:
|
||||
$this->messenger()->addStatus($this->t('Updated term %term.', ['%term' => $view_link]));
|
||||
$this->logger('taxonomy')->notice('Updated term %term.', ['%term' => $term->getName(), 'link' => $edit_link]);
|
||||
break;
|
||||
}
|
||||
|
||||
$current_parent_count = count($form_state->getValue('parent'));
|
||||
// Root doesn't count if it's the only parent.
|
||||
if ($current_parent_count == 1 && $form_state->hasValue(['parent', 0])) {
|
||||
$form_state->setValue('parent', []);
|
||||
}
|
||||
|
||||
$form_state->setValue('tid', $term->id());
|
||||
$form_state->set('tid', $term->id());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\RevisionLogInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a taxonomy term entity.
|
||||
*/
|
||||
interface TermInterface extends ContentEntityInterface, EntityChangedInterface, EntityPublishedInterface, RevisionLogInterface {
|
||||
|
||||
/**
|
||||
* Gets the term description.
|
||||
*
|
||||
* @return string
|
||||
* The term description.
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Sets the term description.
|
||||
*
|
||||
* @param string $description
|
||||
* The term description.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescription($description);
|
||||
|
||||
/**
|
||||
* Gets the text format name for the term description.
|
||||
*
|
||||
* @return string
|
||||
* The text format name.
|
||||
*/
|
||||
public function getFormat();
|
||||
|
||||
/**
|
||||
* Sets the text format name for the term description.
|
||||
*
|
||||
* @param string $format
|
||||
* The text format name.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFormat($format);
|
||||
|
||||
/**
|
||||
* Gets the term name.
|
||||
*
|
||||
* @return string
|
||||
* The term name.
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Sets the term name.
|
||||
*
|
||||
* @param string $name
|
||||
* The term name.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* Gets the term weight.
|
||||
*
|
||||
* @return int
|
||||
* The term weight.
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
/**
|
||||
* Sets the term weight.
|
||||
*
|
||||
* @param int $weight
|
||||
* The term weight.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setWeight($weight);
|
||||
|
||||
/**
|
||||
* Gets the ID of the vocabulary that owns the term.
|
||||
*
|
||||
* @return string
|
||||
* The vocabulary ID.
|
||||
*
|
||||
* @deprecated in drupal:8.?.? and is removed from drupal:9.0.0. Use
|
||||
* TermInterface::bundle() instead.
|
||||
*/
|
||||
public function getVocabularyId();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
use Drupal\Core\Entity\Sql\TableMappingInterface;
|
||||
|
||||
/**
|
||||
* Defines a Controller class for taxonomy terms.
|
||||
*/
|
||||
class TermStorage extends SqlContentEntityStorage implements TermStorageInterface {
|
||||
|
||||
/**
|
||||
* Array of term parents keyed by vocabulary ID and child term ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $treeParents = [];
|
||||
|
||||
/**
|
||||
* Array of term ancestors keyed by vocabulary ID and parent term ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $treeChildren = [];
|
||||
|
||||
/**
|
||||
* Array of terms in a tree keyed by vocabulary ID and term ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $treeTerms = [];
|
||||
|
||||
/**
|
||||
* Array of loaded trees keyed by a cache id matching tree arguments.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $trees = [];
|
||||
|
||||
/**
|
||||
* Array of all loaded term ancestry keyed by ancestor term ID, keyed by term
|
||||
* ID.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermInterface[][]
|
||||
*/
|
||||
protected $ancestors;
|
||||
|
||||
/**
|
||||
* The type of hierarchy allowed within a vocabulary.
|
||||
*
|
||||
* Possible values:
|
||||
* - VocabularyInterface::HIERARCHY_DISABLED: No parents.
|
||||
* - VocabularyInterface::HIERARCHY_SINGLE: Single parent.
|
||||
* - VocabularyInterface::HIERARCHY_MULTIPLE: Multiple parents.
|
||||
*
|
||||
* @var int[]
|
||||
* An array of one the possible values above, keyed by vocabulary ID.
|
||||
*/
|
||||
protected $vocabularyHierarchyType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $values
|
||||
* An array of values to set, keyed by property name. A value for the
|
||||
* vocabulary ID ('vid') is required.
|
||||
*/
|
||||
public function create(array $values = []) {
|
||||
// Save new terms with no parents by default.
|
||||
if (empty($values['parent'])) {
|
||||
$values['parent'] = [0];
|
||||
}
|
||||
$entity = parent::create($values);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetCache(array $ids = NULL) {
|
||||
drupal_static_reset('taxonomy_term_count_nodes');
|
||||
$this->ancestors = [];
|
||||
$this->treeChildren = [];
|
||||
$this->treeParents = [];
|
||||
$this->treeTerms = [];
|
||||
$this->trees = [];
|
||||
$this->vocabularyHierarchyType = [];
|
||||
parent::resetCache($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteTermHierarchy($tids) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateTermHierarchy(EntityInterface $term) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadParents($tid) {
|
||||
$terms = [];
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
if ($tid && $term = $this->load($tid)) {
|
||||
foreach ($this->getParents($term) as $id => $parent) {
|
||||
// This method currently doesn't return the <root> parent.
|
||||
// @see https://www.drupal.org/node/2019905
|
||||
if (!empty($id)) {
|
||||
$terms[$id] = $parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of parents of this term.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* The parent taxonomy term entities keyed by term ID. If this term has a
|
||||
* <root> parent, that item is keyed with 0 and will have NULL as value.
|
||||
*
|
||||
* @internal
|
||||
* @todo Refactor away when TreeInterface is introduced.
|
||||
*/
|
||||
protected function getParents(TermInterface $term) {
|
||||
$parents = $ids = [];
|
||||
// Cannot use $this->get('parent')->referencedEntities() here because that
|
||||
// strips out the '0' reference.
|
||||
foreach ($term->get('parent') as $item) {
|
||||
if ($item->target_id == 0) {
|
||||
// The <root> parent.
|
||||
$parents[0] = NULL;
|
||||
continue;
|
||||
}
|
||||
$ids[] = $item->target_id;
|
||||
}
|
||||
|
||||
// @todo Better way to do this? AND handle the NULL/0 parent?
|
||||
// Querying the terms again so that the same access checks are run when
|
||||
// getParents() is called as in Drupal version prior to 8.3.
|
||||
$loaded_parents = [];
|
||||
|
||||
if ($ids) {
|
||||
$query = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('tid', $ids, 'IN');
|
||||
|
||||
$loaded_parents = static::loadMultiple($query->execute());
|
||||
}
|
||||
|
||||
return $parents + $loaded_parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadAllParents($tid) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
return (!empty($tid) && $term = $this->load($tid)) ? $this->getAncestors($term) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all ancestors of this term.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* A list of ancestor taxonomy term entities keyed by term ID.
|
||||
*
|
||||
* @internal
|
||||
* @todo Refactor away when TreeInterface is introduced.
|
||||
*/
|
||||
protected function getAncestors(TermInterface $term) {
|
||||
if (!isset($this->ancestors[$term->id()])) {
|
||||
$this->ancestors[$term->id()] = [$term->id() => $term];
|
||||
$search[] = $term->id();
|
||||
|
||||
while ($tid = array_shift($search)) {
|
||||
foreach ($this->getParents(static::load($tid)) as $id => $parent) {
|
||||
if ($parent && !isset($this->ancestors[$term->id()][$id])) {
|
||||
$this->ancestors[$term->id()][$id] = $parent;
|
||||
$search[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->ancestors[$term->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadChildren($tid, $vid = NULL) {
|
||||
/** @var \Drupal\taxonomy\TermInterface $term */
|
||||
return (!empty($tid) && $term = $this->load($tid)) ? $this->getChildren($term) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all children terms of this term.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* A list of children taxonomy term entities keyed by term ID.
|
||||
*
|
||||
* @internal
|
||||
* @todo Refactor away when TreeInterface is introduced.
|
||||
*/
|
||||
public function getChildren(TermInterface $term) {
|
||||
$query = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('parent', $term->id());
|
||||
return static::loadMultiple($query->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
|
||||
$cache_key = implode(':', func_get_args());
|
||||
if (!isset($this->trees[$cache_key])) {
|
||||
// We cache trees, so it's not CPU-intensive to call on a term and its
|
||||
// children, too.
|
||||
if (!isset($this->treeChildren[$vid])) {
|
||||
$this->treeChildren[$vid] = [];
|
||||
$this->treeParents[$vid] = [];
|
||||
$this->treeTerms[$vid] = [];
|
||||
$query = $this->database->select($this->getDataTable(), 't');
|
||||
$query->join('taxonomy_term__parent', 'p', 't.tid = p.entity_id');
|
||||
$query->addExpression('parent_target_id', 'parent');
|
||||
$result = $query
|
||||
->addTag('taxonomy_term_access')
|
||||
->fields('t')
|
||||
->condition('t.vid', $vid)
|
||||
->condition('t.default_langcode', 1)
|
||||
->orderBy('t.weight')
|
||||
->orderBy('t.name')
|
||||
->execute();
|
||||
foreach ($result as $term) {
|
||||
$this->treeChildren[$vid][$term->parent][] = $term->tid;
|
||||
$this->treeParents[$vid][$term->tid][] = $term->parent;
|
||||
$this->treeTerms[$vid][$term->tid] = $term;
|
||||
}
|
||||
}
|
||||
|
||||
// Load full entities, if necessary. The entity controller statically
|
||||
// caches the results.
|
||||
$term_entities = [];
|
||||
if ($load_entities) {
|
||||
$term_entities = $this->loadMultiple(array_keys($this->treeTerms[$vid]));
|
||||
}
|
||||
|
||||
$max_depth = (!isset($max_depth)) ? count($this->treeChildren[$vid]) : $max_depth;
|
||||
$tree = [];
|
||||
|
||||
// Keeps track of the parents we have to process, the last entry is used
|
||||
// for the next processing step.
|
||||
$process_parents = [];
|
||||
$process_parents[] = $parent;
|
||||
|
||||
// Loops over the parent terms and adds its children to the tree array.
|
||||
// Uses a loop instead of a recursion, because it's more efficient.
|
||||
while (count($process_parents)) {
|
||||
$parent = array_pop($process_parents);
|
||||
// The number of parents determines the current depth.
|
||||
$depth = count($process_parents);
|
||||
if ($max_depth > $depth && !empty($this->treeChildren[$vid][$parent])) {
|
||||
$has_children = FALSE;
|
||||
$child = current($this->treeChildren[$vid][$parent]);
|
||||
do {
|
||||
if (empty($child)) {
|
||||
break;
|
||||
}
|
||||
$term = $load_entities ? $term_entities[$child] : $this->treeTerms[$vid][$child];
|
||||
if (isset($this->treeParents[$vid][$load_entities ? $term->id() : $term->tid])) {
|
||||
// Clone the term so that the depth attribute remains correct
|
||||
// in the event of multiple parents.
|
||||
$term = clone $term;
|
||||
}
|
||||
$term->depth = $depth;
|
||||
if (!$load_entities) {
|
||||
unset($term->parent);
|
||||
}
|
||||
$tid = $load_entities ? $term->id() : $term->tid;
|
||||
$term->parents = $this->treeParents[$vid][$tid];
|
||||
$tree[] = $term;
|
||||
if (!empty($this->treeChildren[$vid][$tid])) {
|
||||
$has_children = TRUE;
|
||||
|
||||
// We have to continue with this parent later.
|
||||
$process_parents[] = $parent;
|
||||
// Use the current term as parent for the next iteration.
|
||||
$process_parents[] = $tid;
|
||||
|
||||
// Reset pointers for child lists because we step in there more
|
||||
// often with multi parents.
|
||||
reset($this->treeChildren[$vid][$tid]);
|
||||
// Move pointer so that we get the correct term the next time.
|
||||
next($this->treeChildren[$vid][$parent]);
|
||||
break;
|
||||
}
|
||||
} while ($child = next($this->treeChildren[$vid][$parent]));
|
||||
|
||||
if (!$has_children) {
|
||||
// We processed all terms in this hierarchy-level, reset pointer
|
||||
// so that this function works the next time it gets called.
|
||||
reset($this->treeChildren[$vid][$parent]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->trees[$cache_key] = $tree;
|
||||
}
|
||||
return $this->trees[$cache_key];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function nodeCount($vid) {
|
||||
$query = $this->database->select('taxonomy_index', 'ti');
|
||||
$query->addExpression('COUNT(DISTINCT ti.nid)');
|
||||
$query->leftJoin($this->getBaseTable(), 'td', 'ti.tid = td.tid');
|
||||
$query->condition('td.vid', $vid);
|
||||
$query->addTag('vocabulary_node_count');
|
||||
return $query->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetWeights($vid) {
|
||||
$this->database->update($this->getDataTable())
|
||||
->fields(['weight' => 0])
|
||||
->condition('vid', $vid)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodeTerms(array $nids, array $vocabs = [], $langcode = NULL) {
|
||||
$query = $this->database->select($this->getDataTable(), 'td');
|
||||
$query->innerJoin('taxonomy_index', 'tn', 'td.tid = tn.tid');
|
||||
$query->fields('td', ['tid']);
|
||||
$query->addField('tn', 'nid', 'node_nid');
|
||||
$query->orderby('td.weight');
|
||||
$query->orderby('td.name');
|
||||
$query->condition('tn.nid', $nids, 'IN');
|
||||
$query->addTag('taxonomy_term_access');
|
||||
if (!empty($vocabs)) {
|
||||
$query->condition('td.vid', $vocabs, 'IN');
|
||||
}
|
||||
if (!empty($langcode)) {
|
||||
$query->condition('td.langcode', $langcode);
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$all_tids = [];
|
||||
foreach ($query->execute() as $term_record) {
|
||||
$results[$term_record->node_nid][] = $term_record->tid;
|
||||
$all_tids[] = $term_record->tid;
|
||||
}
|
||||
|
||||
$all_terms = $this->loadMultiple($all_tids);
|
||||
$terms = [];
|
||||
foreach ($results as $nid => $tids) {
|
||||
foreach ($tids as $tid) {
|
||||
$terms[$nid][$tid] = $all_terms[$tid];
|
||||
}
|
||||
}
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTermIdsWithPendingRevisions() {
|
||||
$table_mapping = $this->getTableMapping();
|
||||
$id_field = $table_mapping->getColumnNames($this->entityType->getKey('id'))['value'];
|
||||
$revision_field = $table_mapping->getColumnNames($this->entityType->getKey('revision'))['value'];
|
||||
$rta_field = $table_mapping->getColumnNames($this->entityType->getKey('revision_translation_affected'))['value'];
|
||||
$langcode_field = $table_mapping->getColumnNames($this->entityType->getKey('langcode'))['value'];
|
||||
$revision_default_field = $table_mapping->getColumnNames($this->entityType->getRevisionMetadataKey('revision_default'))['value'];
|
||||
|
||||
$query = $this->database->select($this->getRevisionDataTable(), 'tfr');
|
||||
$query->fields('tfr', [$id_field]);
|
||||
$query->addExpression("MAX(tfr.$revision_field)", $revision_field);
|
||||
|
||||
$query->join($this->getRevisionTable(), 'tr', "tfr.$revision_field = tr.$revision_field AND tr.$revision_default_field = 0");
|
||||
|
||||
$inner_select = $this->database->select($this->getRevisionDataTable(), 't');
|
||||
$inner_select->condition("t.$rta_field", '1');
|
||||
$inner_select->fields('t', [$id_field, $langcode_field]);
|
||||
$inner_select->addExpression("MAX(t.$revision_field)", $revision_field);
|
||||
$inner_select
|
||||
->groupBy("t.$id_field")
|
||||
->groupBy("t.$langcode_field");
|
||||
|
||||
$query->join($inner_select, 'mr', "tfr.$revision_field = mr.$revision_field AND tfr.$langcode_field = mr.$langcode_field");
|
||||
|
||||
$query->groupBy("tfr.$id_field");
|
||||
|
||||
return $query->execute()->fetchAllKeyed(1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVocabularyHierarchyType($vid) {
|
||||
// Return early if we already computed this value.
|
||||
if (isset($this->vocabularyHierarchyType[$vid])) {
|
||||
return $this->vocabularyHierarchyType[$vid];
|
||||
}
|
||||
|
||||
$parent_field_storage = $this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId)['parent'];
|
||||
$table_mapping = $this->getTableMapping();
|
||||
|
||||
$target_id_column = $table_mapping->getFieldColumnName($parent_field_storage, 'target_id');
|
||||
$delta_column = $table_mapping->getFieldColumnName($parent_field_storage, TableMappingInterface::DELTA);
|
||||
|
||||
$query = $this->database->select($table_mapping->getFieldTableName('parent'), 'p');
|
||||
$query->addExpression("MAX($target_id_column)", 'max_parent_id');
|
||||
$query->addExpression("MAX($delta_column)", 'max_delta');
|
||||
$query->condition('bundle', $vid);
|
||||
|
||||
$result = $query->execute()->fetchAll();
|
||||
|
||||
// If all the terms have the same parent, the parent can only be root (0).
|
||||
if ((int) $result[0]->max_parent_id === 0) {
|
||||
$this->vocabularyHierarchyType[$vid] = VocabularyInterface::HIERARCHY_DISABLED;
|
||||
}
|
||||
// If no term has a delta higher than 0, no term has multiple parents.
|
||||
elseif ((int) $result[0]->max_delta === 0) {
|
||||
$this->vocabularyHierarchyType[$vid] = VocabularyInterface::HIERARCHY_SINGLE;
|
||||
}
|
||||
else {
|
||||
$this->vocabularyHierarchyType[$vid] = VocabularyInterface::HIERARCHY_MULTIPLE;
|
||||
}
|
||||
|
||||
return $this->vocabularyHierarchyType[$vid];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __sleep() {
|
||||
$vars = parent::__sleep();
|
||||
// Do not serialize static cache.
|
||||
unset($vars['ancestors'], $vars['treeChildren'], $vars['treeParents'], $vars['treeTerms'], $vars['trees'], $vars['vocabularyHierarchyType']);
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __wakeup() {
|
||||
parent::__wakeup();
|
||||
// Initialize static caches.
|
||||
$this->ancestors = [];
|
||||
$this->treeChildren = [];
|
||||
$this->treeParents = [];
|
||||
$this->treeTerms = [];
|
||||
$this->trees = [];
|
||||
$this->vocabularyHierarchyType = [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for taxonomy_term entity storage classes.
|
||||
*/
|
||||
interface TermStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Removed reference to terms from term_hierarchy.
|
||||
*
|
||||
* @param array $tids
|
||||
* Array of terms that need to be removed from hierarchy.
|
||||
*
|
||||
* @todo Remove this method in Drupal 9.0.x. Now the parent references are
|
||||
* automatically cleared when deleting a taxonomy term.
|
||||
* https://www.drupal.org/node/2785693
|
||||
*/
|
||||
public function deleteTermHierarchy($tids);
|
||||
|
||||
/**
|
||||
* Updates terms hierarchy information with the hierarchy trail of it.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $term
|
||||
* Term entity that needs to be added to term hierarchy information.
|
||||
*
|
||||
* @todo remove this method Drupal 9.0.x. Now the parent references are
|
||||
* automatically updates when when a taxonomy term is added/updated.
|
||||
* https://www.drupal.org/node/2785693
|
||||
*/
|
||||
public function updateTermHierarchy(EntityInterface $term);
|
||||
|
||||
/**
|
||||
* Finds all parents of a given term ID.
|
||||
*
|
||||
* @param int $tid
|
||||
* Term ID to retrieve parents for.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* An array of term objects which are the parents of the term $tid.
|
||||
*/
|
||||
public function loadParents($tid);
|
||||
|
||||
/**
|
||||
* Finds all ancestors of a given term ID.
|
||||
*
|
||||
* @param int $tid
|
||||
* Term ID to retrieve ancestors for.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* An array of term objects which are the ancestors of the term $tid.
|
||||
*/
|
||||
public function loadAllParents($tid);
|
||||
|
||||
/**
|
||||
* Finds all children of a term ID.
|
||||
*
|
||||
* @param int $tid
|
||||
* Term ID to retrieve children for.
|
||||
* @param string $vid
|
||||
* An optional vocabulary ID to restrict the child search.
|
||||
*
|
||||
* @return \Drupal\taxonomy\TermInterface[]
|
||||
* An array of term objects that are the children of the term $tid.
|
||||
*/
|
||||
public function loadChildren($tid, $vid = NULL);
|
||||
|
||||
/**
|
||||
* Finds all terms in a given vocabulary ID.
|
||||
*
|
||||
* @param string $vid
|
||||
* Vocabulary ID to retrieve terms for.
|
||||
* @param int $parent
|
||||
* The term ID under which to generate the tree. If 0, generate the tree
|
||||
* for the entire vocabulary.
|
||||
* @param int $max_depth
|
||||
* The number of levels of the tree to return. Leave NULL to return all
|
||||
* levels.
|
||||
* @param bool $load_entities
|
||||
* If TRUE, a full entity load will occur on the term objects. Otherwise
|
||||
* they are partial objects queried directly from the {taxonomy_term_data}
|
||||
* table to save execution time and memory consumption when listing large
|
||||
* numbers of terms. Defaults to FALSE.
|
||||
*
|
||||
* @return object[]|\Drupal\taxonomy\TermInterface[]
|
||||
* An array of term objects that are the children of the vocabulary $vid.
|
||||
*/
|
||||
public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE);
|
||||
|
||||
/**
|
||||
* Count the number of nodes in a given vocabulary ID.
|
||||
*
|
||||
* @param string $vid
|
||||
* Vocabulary ID to retrieve terms for.
|
||||
*
|
||||
* @return int
|
||||
* A count of the nodes in a given vocabulary ID.
|
||||
*/
|
||||
public function nodeCount($vid);
|
||||
|
||||
/**
|
||||
* Reset the weights for a given vocabulary ID.
|
||||
*
|
||||
* @param string $vid
|
||||
* Vocabulary ID to retrieve terms for.
|
||||
*/
|
||||
public function resetWeights($vid);
|
||||
|
||||
/**
|
||||
* Returns all terms used to tag some given nodes.
|
||||
*
|
||||
* @param array $nids
|
||||
* Node IDs to retrieve terms for.
|
||||
* @param array $vocabs
|
||||
* (optional) A vocabularies array to restrict the term search. Defaults to
|
||||
* empty array.
|
||||
* @param string $langcode
|
||||
* (optional) A language code to restrict the term search. Defaults to NULL.
|
||||
*
|
||||
* @return array
|
||||
* An array of nids and the term entities they were tagged with.
|
||||
*/
|
||||
public function getNodeTerms(array $nids, array $vocabs = [], $langcode = NULL);
|
||||
|
||||
/**
|
||||
* Returns the hierarchy type for a specific vocabulary ID.
|
||||
*
|
||||
* @param string $vid
|
||||
* Vocabulary ID to retrieve the hierarchy type for.
|
||||
*
|
||||
* @return int
|
||||
* The vocabulary hierarchy.
|
||||
* Possible values:
|
||||
* - VocabularyInterface::HIERARCHY_DISABLED: No parents.
|
||||
* - VocabularyInterface::HIERARCHY_SINGLE: Single parent.
|
||||
* - VocabularyInterface::HIERARCHY_MULTIPLE: Multiple parents.
|
||||
*/
|
||||
public function getVocabularyHierarchyType($vid);
|
||||
|
||||
/**
|
||||
* Gets a list of term IDs with pending revisions.
|
||||
*
|
||||
* @return int[]
|
||||
* An array of term IDs which have pending revisions, keyed by their
|
||||
* revision IDs.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getTermIdsWithPendingRevisions();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Defines the term schema handler.
|
||||
*/
|
||||
class TermStorageSchema extends SqlContentEntityStorageSchema {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
|
||||
$schema = parent::getEntitySchema($entity_type, $reset);
|
||||
|
||||
if ($data_table = $this->storage->getDataTable()) {
|
||||
$schema[$data_table]['indexes'] += [
|
||||
'taxonomy_term__tree' => ['vid', 'weight', 'name'],
|
||||
'taxonomy_term__vid_name' => ['vid', 'name'],
|
||||
];
|
||||
}
|
||||
|
||||
$schema['taxonomy_index'] = [
|
||||
'description' => 'Maintains denormalized information about node/term relationships.',
|
||||
'fields' => [
|
||||
'nid' => [
|
||||
'description' => 'The {node}.nid this record tracks.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
'tid' => [
|
||||
'description' => 'The term ID.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
'status' => [
|
||||
'description' => 'Boolean indicating whether the node is published (visible to non-administrators).',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
],
|
||||
'sticky' => [
|
||||
'description' => 'Boolean indicating whether the node is sticky.',
|
||||
'type' => 'int',
|
||||
'not null' => FALSE,
|
||||
'default' => 0,
|
||||
'size' => 'tiny',
|
||||
],
|
||||
'created' => [
|
||||
'description' => 'The Unix timestamp when the node was created.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
],
|
||||
],
|
||||
'primary key' => ['nid', 'tid'],
|
||||
'indexes' => [
|
||||
'term_node' => ['tid', 'status', 'sticky', 'created'],
|
||||
],
|
||||
'foreign keys' => [
|
||||
'tracked_node' => [
|
||||
'table' => 'node',
|
||||
'columns' => ['nid' => 'nid'],
|
||||
],
|
||||
'term' => [
|
||||
'table' => 'taxonomy_term_data',
|
||||
'columns' => ['tid' => 'tid'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
|
||||
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
|
||||
$field_name = $storage_definition->getName();
|
||||
|
||||
if ($table_name == 'taxonomy_term_field_data') {
|
||||
// Remove unneeded indexes.
|
||||
unset($schema['indexes']['taxonomy_term_field__vid__target_id']);
|
||||
unset($schema['indexes']['taxonomy_term_field__description__format']);
|
||||
|
||||
switch ($field_name) {
|
||||
case 'weight':
|
||||
// Improves the performance of the taxonomy_term__tree index defined
|
||||
// in getEntitySchema().
|
||||
$schema['fields'][$field_name]['not null'] = TRUE;
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDedicatedTableSchema(FieldStorageDefinitionInterface $storage_definition, ContentEntityTypeInterface $entity_type = NULL) {
|
||||
$dedicated_table_schema = parent::getDedicatedTableSchema($storage_definition, $entity_type);
|
||||
|
||||
// Add an index on 'bundle', 'delta' and 'parent_target_id' columns to
|
||||
// increase the performance of the query from
|
||||
// \Drupal\taxonomy\TermStorage::getVocabularyHierarchyType().
|
||||
if ($storage_definition->getName() === 'parent') {
|
||||
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
|
||||
$table_mapping = $this->storage->getTableMapping();
|
||||
$dedicated_table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
|
||||
|
||||
unset($dedicated_table_schema[$dedicated_table_name]['indexes']['bundle']);
|
||||
$dedicated_table_schema[$dedicated_table_name]['indexes']['bundle_delta_target_id'] = [
|
||||
'bundle',
|
||||
'delta',
|
||||
$table_mapping->getFieldColumnName($storage_definition, 'target_id'),
|
||||
];
|
||||
}
|
||||
|
||||
return $dedicated_table_schema;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\content_translation\ContentTranslationHandler;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Defines the translation handler for terms.
|
||||
*/
|
||||
class TermTranslationHandler extends ContentTranslationHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
|
||||
parent::entityFormAlter($form, $form_state, $entity);
|
||||
|
||||
$form['content_translation']['status']['#access'] = !isset($form['content_translation']);
|
||||
|
||||
$form['actions']['submit']['#submit'][] = [$this, 'entityFormSave'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for TermTranslationHandler::entityFormAlter().
|
||||
*
|
||||
* This handles the save action.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityForm::build()
|
||||
*/
|
||||
public function entityFormSave(array $form, FormStateInterface $form_state) {
|
||||
if ($this->getSourceLangcode($form_state)) {
|
||||
$entity = $form_state->getFormObject()->getEntity();
|
||||
// We need a redirect here, otherwise we would get an access denied page,
|
||||
// since the current URL would be preserved and we would try to add a
|
||||
// translation for a language that already has a translation.
|
||||
$form_state->setRedirectUrl($entity->toUrl('edit-form'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
if ($form_state->hasValue('content_translation')) {
|
||||
$translation = &$form_state->getValue('content_translation');
|
||||
$translation['status'] = $entity->isPublished();
|
||||
}
|
||||
parent::entityFormEntityBuild($entity_type, $entity, $form, $form_state);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\TermViewBuilder is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\Entity\EntityViewBuilder instead. See https://www.drupal.org/node/2924233.', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
|
||||
/**
|
||||
* View builder handler for taxonomy terms.
|
||||
*
|
||||
* @deprecated in drupal:8.5.0 and is removed from drupal:9.0.0.
|
||||
* Use \Drupal\Core\Entity\EntityViewBuilder instead.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityViewBuilder
|
||||
* @see https://www.drupal.org/node/2924233
|
||||
*/
|
||||
class TermViewBuilder extends EntityViewBuilder {}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\views\EntityViewsData;
|
||||
|
||||
/**
|
||||
* Provides the views data for the taxonomy entity type.
|
||||
*/
|
||||
class TermViewsData extends EntityViewsData {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewsData() {
|
||||
$data = parent::getViewsData();
|
||||
|
||||
$data['taxonomy_term_field_data']['table']['base']['help'] = $this->t('Taxonomy terms are attached to nodes.');
|
||||
$data['taxonomy_term_field_data']['table']['base']['access query tag'] = 'taxonomy_term_access';
|
||||
$data['taxonomy_term_field_data']['table']['wizard_id'] = 'taxonomy_term';
|
||||
|
||||
$data['taxonomy_term_field_data']['table']['join'] = [
|
||||
// This is provided for the many_to_one argument.
|
||||
'taxonomy_index' => [
|
||||
'field' => 'tid',
|
||||
'left_field' => 'tid',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['tid']['help'] = $this->t('The tid of a taxonomy term.');
|
||||
|
||||
$data['taxonomy_term_field_data']['tid']['argument']['id'] = 'taxonomy';
|
||||
$data['taxonomy_term_field_data']['tid']['argument']['name field'] = 'name';
|
||||
$data['taxonomy_term_field_data']['tid']['argument']['zero is null'] = TRUE;
|
||||
|
||||
$data['taxonomy_term_field_data']['tid']['filter']['id'] = 'taxonomy_index_tid';
|
||||
$data['taxonomy_term_field_data']['tid']['filter']['title'] = $this->t('Term');
|
||||
$data['taxonomy_term_field_data']['tid']['filter']['help'] = $this->t('Taxonomy term chosen from autocomplete or select widget.');
|
||||
$data['taxonomy_term_field_data']['tid']['filter']['hierarchy table'] = 'taxonomy_term__parent';
|
||||
$data['taxonomy_term_field_data']['tid']['filter']['numeric'] = TRUE;
|
||||
|
||||
$data['taxonomy_term_field_data']['tid_raw'] = [
|
||||
'title' => $this->t('Term ID'),
|
||||
'help' => $this->t('The tid of a taxonomy term.'),
|
||||
'real field' => 'tid',
|
||||
'filter' => [
|
||||
'id' => 'numeric',
|
||||
'allow empty' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['tid_representative'] = [
|
||||
'relationship' => [
|
||||
'title' => $this->t('Representative node'),
|
||||
'label' => $this->t('Representative node'),
|
||||
'help' => $this->t('Obtains a single representative node for each term, according to a chosen sort criterion.'),
|
||||
'id' => 'groupwise_max',
|
||||
'relationship field' => 'tid',
|
||||
'outer field' => 'taxonomy_term_field_data.tid',
|
||||
'argument table' => 'taxonomy_term_field_data',
|
||||
'argument field' => 'tid',
|
||||
'base' => 'node_field_data',
|
||||
'field' => 'nid',
|
||||
'relationship' => 'node_field_data:term_node_tid',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['vid']['help'] = $this->t('Filter the results of "Taxonomy: Term" to a particular vocabulary.');
|
||||
$data['taxonomy_term_field_data']['vid']['field']['help'] = t('The vocabulary name.');
|
||||
$data['taxonomy_term_field_data']['vid']['argument']['id'] = 'vocabulary_vid';
|
||||
unset($data['taxonomy_term_field_data']['vid']['sort']);
|
||||
|
||||
$data['taxonomy_term_field_data']['name']['field']['id'] = 'term_name';
|
||||
$data['taxonomy_term_field_data']['name']['argument']['many to one'] = TRUE;
|
||||
$data['taxonomy_term_field_data']['name']['argument']['empty field name'] = $this->t('Uncategorized');
|
||||
|
||||
$data['taxonomy_term_field_data']['description__value']['field']['click sortable'] = FALSE;
|
||||
|
||||
$data['taxonomy_term_field_data']['changed']['title'] = $this->t('Updated date');
|
||||
$data['taxonomy_term_field_data']['changed']['help'] = $this->t('The date the term was last updated.');
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_fulldate'] = [
|
||||
'title' => $this->t('Updated date'),
|
||||
'help' => $this->t('Date in the form of CCYYMMDD.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_fulldate',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_year_month'] = [
|
||||
'title' => $this->t('Updated year + month'),
|
||||
'help' => $this->t('Date in the form of YYYYMM.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_year_month',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_year'] = [
|
||||
'title' => $this->t('Updated year'),
|
||||
'help' => $this->t('Date in the form of YYYY.'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_year',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_month'] = [
|
||||
'title' => $this->t('Updated month'),
|
||||
'help' => $this->t('Date in the form of MM (01 - 12).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_month',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_day'] = [
|
||||
'title' => $this->t('Updated day'),
|
||||
'help' => $this->t('Date in the form of DD (01 - 31).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_day',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_term_field_data']['changed_week'] = [
|
||||
'title' => $this->t('Updated week'),
|
||||
'help' => $this->t('Date in the form of WW (01 - 53).'),
|
||||
'argument' => [
|
||||
'field' => 'changed',
|
||||
'id' => 'date_week',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_index']['table']['group'] = $this->t('Taxonomy term');
|
||||
|
||||
$data['taxonomy_index']['table']['join'] = [
|
||||
'taxonomy_term_field_data' => [
|
||||
// links directly to taxonomy_term_field_data via tid
|
||||
'left_field' => 'tid',
|
||||
'field' => 'tid',
|
||||
],
|
||||
'node_field_data' => [
|
||||
// links directly to node via nid
|
||||
'left_field' => 'nid',
|
||||
'field' => 'nid',
|
||||
],
|
||||
'taxonomy_term__parent' => [
|
||||
'left_field' => 'entity_id',
|
||||
'field' => 'tid',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_index']['nid'] = [
|
||||
'title' => $this->t('Content with term'),
|
||||
'help' => $this->t('Relate all content tagged with a term.'),
|
||||
'relationship' => [
|
||||
'id' => 'standard',
|
||||
'base' => 'node_field_data',
|
||||
'base field' => 'nid',
|
||||
'label' => $this->t('node'),
|
||||
'skip base' => 'node_field_data',
|
||||
],
|
||||
];
|
||||
|
||||
// @todo This stuff needs to move to a node field since really it's all
|
||||
// about nodes.
|
||||
$data['taxonomy_index']['tid'] = [
|
||||
'group' => $this->t('Content'),
|
||||
'title' => $this->t('Has taxonomy term ID'),
|
||||
'help' => $this->t('Display content if it has the selected taxonomy terms.'),
|
||||
'argument' => [
|
||||
'id' => 'taxonomy_index_tid',
|
||||
'name table' => 'taxonomy_term_field_data',
|
||||
'name field' => 'name',
|
||||
'empty field name' => $this->t('Uncategorized'),
|
||||
'numeric' => TRUE,
|
||||
'skip base' => 'taxonomy_term_field_data',
|
||||
],
|
||||
'filter' => [
|
||||
'title' => $this->t('Has taxonomy term'),
|
||||
'id' => 'taxonomy_index_tid',
|
||||
'hierarchy table' => 'taxonomy_term__parent',
|
||||
'numeric' => TRUE,
|
||||
'skip base' => 'taxonomy_term_field_data',
|
||||
'allow empty' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_index']['status'] = [
|
||||
'title' => $this->t('Publish status'),
|
||||
'help' => $this->t('Whether or not the content related to a term is published.'),
|
||||
'filter' => [
|
||||
'id' => 'boolean',
|
||||
'label' => $this->t('Published status'),
|
||||
'type' => 'yes-no',
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_index']['sticky'] = [
|
||||
'title' => $this->t('Sticky status'),
|
||||
'help' => $this->t('Whether or not the content related to a term is sticky.'),
|
||||
'filter' => [
|
||||
'id' => 'boolean',
|
||||
'label' => $this->t('Sticky status'),
|
||||
'type' => 'yes-no',
|
||||
],
|
||||
'sort' => [
|
||||
'id' => 'standard',
|
||||
'help' => $this->t('Whether or not the content related to a term is sticky. To list sticky content first, set this to descending.'),
|
||||
],
|
||||
];
|
||||
|
||||
$data['taxonomy_index']['created'] = [
|
||||
'title' => $this->t('Post date'),
|
||||
'help' => $this->t('The date the content related to a term was posted.'),
|
||||
'sort' => [
|
||||
'id' => 'date',
|
||||
],
|
||||
'filter' => [
|
||||
'id' => 'date',
|
||||
],
|
||||
];
|
||||
|
||||
// Link to self through left.parent = right.tid (going down in depth).
|
||||
$data['taxonomy_term__parent']['table']['join']['taxonomy_term__parent'] = [
|
||||
'left_field' => 'entity_id',
|
||||
'field' => 'parent_target_id',
|
||||
];
|
||||
|
||||
$data['taxonomy_term__parent']['parent_target_id']['help'] = $this->t('The parent term of the term. This can produce duplicate entries if you are using a vocabulary that allows multiple parents.');
|
||||
$data['taxonomy_term__parent']['parent_target_id']['relationship']['label'] = $this->t('Parent');
|
||||
$data['taxonomy_term__parent']['parent_target_id']['argument']['id'] = 'taxonomy';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Tests;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\TaxonomyTestBase is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTestBase', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
||||
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
|
||||
|
||||
/**
|
||||
* Provides common helper methods for Taxonomy module tests.
|
||||
*
|
||||
* @deprecated in drupal:8.?.? and is removed from drupal:9.0.0.
|
||||
* Use \Drupal\Tests\taxonomy\Functional\TaxonomyTestBase instead.
|
||||
*/
|
||||
abstract class TaxonomyTestBase extends WebTestBase {
|
||||
|
||||
use TaxonomyTestTrait;
|
||||
use EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['taxonomy', 'block'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
|
||||
// Create Basic page and Article node types.
|
||||
if ($this->profile != 'standard') {
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Tests;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\TaxonomyTestTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait instead', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
|
||||
/**
|
||||
* Provides common helper methods for Taxonomy module tests.
|
||||
*
|
||||
* @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0.
|
||||
* Use \Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait instead.
|
||||
*/
|
||||
trait TaxonomyTestTrait {
|
||||
|
||||
/**
|
||||
* Returns a new vocabulary with random properties.
|
||||
*/
|
||||
public function createVocabulary() {
|
||||
// Create a vocabulary.
|
||||
$vocabulary = Vocabulary::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'description' => $this->randomMachineName(),
|
||||
'vid' => mb_strtolower($this->randomMachineName()),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
'weight' => mt_rand(0, 10),
|
||||
]);
|
||||
$vocabulary->save();
|
||||
return $vocabulary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new term with random properties in vocabulary $vid.
|
||||
*
|
||||
* @param \Drupal\taxonomy\Entity\Vocabulary $vocabulary
|
||||
* The vocabulary object.
|
||||
* @param array $values
|
||||
* (optional) An array of values to set, keyed by property name. If the
|
||||
* entity type has bundles, the bundle key has to be specified.
|
||||
*
|
||||
* @return \Drupal\taxonomy\Entity\Term
|
||||
* The new taxonomy term object.
|
||||
*/
|
||||
public function createTerm(Vocabulary $vocabulary, $values = []) {
|
||||
$filter_formats = filter_formats();
|
||||
$format = array_pop($filter_formats);
|
||||
$term = Term::create($values + [
|
||||
'name' => $this->randomMachineName(),
|
||||
'description' => [
|
||||
'value' => $this->randomMachineName(),
|
||||
// Use the first available text format.
|
||||
'format' => $format->id(),
|
||||
],
|
||||
'vid' => $vocabulary->id(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$term->save();
|
||||
return $term;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy\Tests;
|
||||
|
||||
@trigger_error(__NAMESPACE__ . '\TaxonomyTranslationTestTrait is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\taxonomy\Functional\TaxonomyTranslationTestTrait', E_USER_DEPRECATED);
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* Provides common testing base for translated taxonomy terms.
|
||||
*
|
||||
* @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0.
|
||||
* Use \Drupal\Tests\taxonomy\Functional\TaxonomyTranslationTestTrait
|
||||
*/
|
||||
trait TaxonomyTranslationTestTrait {
|
||||
|
||||
use EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* The vocabulary.
|
||||
*
|
||||
* @var \Drupal\taxonomy\Entity\Vocabulary
|
||||
*/
|
||||
protected $vocabulary;
|
||||
|
||||
/**
|
||||
* The field name for our taxonomy term field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $termFieldName = 'field_tag';
|
||||
|
||||
/**
|
||||
* The langcode of the source language.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $baseLangcode = 'en';
|
||||
|
||||
/**
|
||||
* Target langcode for translation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $translateToLangcode = 'hu';
|
||||
|
||||
/**
|
||||
* The node to check the translated value on.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* Adds additional languages.
|
||||
*/
|
||||
protected function setupLanguages() {
|
||||
ConfigurableLanguage::createFromLangcode($this->translateToLangcode)->save();
|
||||
$this->rebuildContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables translations where it needed.
|
||||
*/
|
||||
protected function enableTranslation() {
|
||||
// Enable translation for the current entity type and ensure the change is
|
||||
// picked up.
|
||||
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
|
||||
\Drupal::service('content_translation.manager')->setEnabled('taxonomy_term', $this->vocabulary->id(), TRUE);
|
||||
\Drupal::entityManager()->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds term reference field for the article content type.
|
||||
*/
|
||||
protected function setUpTermReferenceField() {
|
||||
$handler_settings = [
|
||||
'target_bundles' => [
|
||||
$this->vocabulary->id() => $this->vocabulary->id(),
|
||||
],
|
||||
'auto_create' => TRUE,
|
||||
];
|
||||
$this->createEntityReferenceField('node', 'article', $this->termFieldName, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $this->termFieldName);
|
||||
$field_storage->setTranslatable(FALSE);
|
||||
$field_storage->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
$display_repository->getFormDisplay('node', 'article')
|
||||
->setComponent($this->termFieldName, [
|
||||
'type' => 'entity_reference_autocomplete_tags',
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay('node', 'article')
|
||||
->setComponent($this->termFieldName, [
|
||||
'type' => 'entity_reference_label',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines the access control handler for the taxonomy vocabulary entity type.
|
||||
*
|
||||
* @see \Drupal\taxonomy\Entity\Vocabulary
|
||||
*/
|
||||
class VocabularyAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
switch ($operation) {
|
||||
case 'access taxonomy overview':
|
||||
case 'view':
|
||||
return AccessResult::allowedIfHasPermissions($account, ['access taxonomy overview', 'administer taxonomy'], 'OR');
|
||||
|
||||
default:
|
||||
return parent::checkAccess($entity, $operation, $account);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\BundleEntityFormBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base form for vocabulary edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class VocabularyForm extends BundleEntityFormBase {
|
||||
|
||||
/**
|
||||
* The vocabulary storage.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyStorageInterface
|
||||
*/
|
||||
protected $vocabularyStorage;
|
||||
|
||||
/**
|
||||
* Constructs a new vocabulary form.
|
||||
*
|
||||
* @param \Drupal\taxonomy\VocabularyStorageInterface $vocabulary_storage
|
||||
* The vocabulary storage.
|
||||
*/
|
||||
public function __construct(VocabularyStorageInterface $vocabulary_storage) {
|
||||
$this->vocabularyStorage = $vocabulary_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getStorage('taxonomy_vocabulary')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$vocabulary = $this->entity;
|
||||
if ($vocabulary->isNew()) {
|
||||
$form['#title'] = $this->t('Add vocabulary');
|
||||
}
|
||||
else {
|
||||
$form['#title'] = $this->t('Edit vocabulary');
|
||||
}
|
||||
|
||||
$form['name'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Name'),
|
||||
'#default_value' => $vocabulary->label(),
|
||||
'#maxlength' => 255,
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['vid'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $vocabulary->id(),
|
||||
'#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
'source' => ['name'],
|
||||
],
|
||||
];
|
||||
$form['description'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Description'),
|
||||
'#default_value' => $vocabulary->getDescription(),
|
||||
];
|
||||
|
||||
// $form['langcode'] is not wrapped in an
|
||||
// if ($this->moduleHandler->moduleExists('language')) check because the
|
||||
// language_select form element works also without the language module being
|
||||
// installed. https://www.drupal.org/node/1749954 documents the new element.
|
||||
$form['langcode'] = [
|
||||
'#type' => 'language_select',
|
||||
'#title' => $this->t('Vocabulary language'),
|
||||
'#languages' => LanguageInterface::STATE_ALL,
|
||||
'#default_value' => $vocabulary->language()->getId(),
|
||||
];
|
||||
if ($this->moduleHandler->moduleExists('language')) {
|
||||
$form['default_terms_language'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Term language'),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['default_terms_language']['default_language'] = [
|
||||
'#type' => 'language_configuration',
|
||||
'#entity_information' => [
|
||||
'entity_type' => 'taxonomy_term',
|
||||
'bundle' => $vocabulary->id(),
|
||||
],
|
||||
'#default_value' => ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', $vocabulary->id()),
|
||||
];
|
||||
}
|
||||
// Set the hierarchy to "multiple parents" by default. This simplifies the
|
||||
// vocabulary form and standardizes the term form.
|
||||
$form['hierarchy'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => '0',
|
||||
];
|
||||
|
||||
$form = parent::form($form, $form_state);
|
||||
return $this->protectBundleIdElement($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$vocabulary = $this->entity;
|
||||
|
||||
// Prevent leading and trailing spaces in vocabulary names.
|
||||
$vocabulary->set('name', trim($vocabulary->label()));
|
||||
|
||||
$status = $vocabulary->save();
|
||||
$edit_link = $this->entity->toLink($this->t('Edit'), 'edit-form')->toString();
|
||||
switch ($status) {
|
||||
case SAVED_NEW:
|
||||
$this->messenger()->addStatus($this->t('Created new vocabulary %name.', ['%name' => $vocabulary->label()]));
|
||||
$this->logger('taxonomy')->notice('Created new vocabulary %name.', ['%name' => $vocabulary->label(), 'link' => $edit_link]);
|
||||
$form_state->setRedirectUrl($vocabulary->toUrl('overview-form'));
|
||||
break;
|
||||
|
||||
case SAVED_UPDATED:
|
||||
$this->messenger()->addStatus($this->t('Updated vocabulary %name.', ['%name' => $vocabulary->label()]));
|
||||
$this->logger('taxonomy')->notice('Updated vocabulary %name.', ['%name' => $vocabulary->label(), 'link' => $edit_link]);
|
||||
$form_state->setRedirectUrl($vocabulary->toUrl('collection'));
|
||||
break;
|
||||
}
|
||||
|
||||
$form_state->setValue('vid', $vocabulary->id());
|
||||
$form_state->set('vid', $vocabulary->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the vocabulary already exists.
|
||||
*
|
||||
* @param string $vid
|
||||
* The vocabulary ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the vocabulary exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($vid) {
|
||||
$action = $this->vocabularyStorage->load($vid);
|
||||
return !empty($action);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a taxonomy vocabulary entity.
|
||||
*/
|
||||
interface VocabularyInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* Denotes that no term in the vocabulary has a parent.
|
||||
*/
|
||||
const HIERARCHY_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* Denotes that one or more terms in the vocabulary has a single parent.
|
||||
*/
|
||||
const HIERARCHY_SINGLE = 1;
|
||||
|
||||
/**
|
||||
* Denotes that one or more terms in the vocabulary have multiple parents.
|
||||
*/
|
||||
const HIERARCHY_MULTIPLE = 2;
|
||||
|
||||
/**
|
||||
* Returns the vocabulary hierarchy.
|
||||
*
|
||||
* @return int
|
||||
* The vocabulary hierarchy.
|
||||
*
|
||||
* @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. Use
|
||||
* \Drupal\taxonomy\TermStorage::getVocabularyHierarchyType() instead.
|
||||
*/
|
||||
public function getHierarchy();
|
||||
|
||||
/**
|
||||
* Sets the vocabulary hierarchy.
|
||||
*
|
||||
* @param int $hierarchy
|
||||
* The hierarchy type of vocabulary.
|
||||
* Possible values:
|
||||
* - VocabularyInterface::HIERARCHY_DISABLED: No parents.
|
||||
* - VocabularyInterface::HIERARCHY_SINGLE: Single parent.
|
||||
* - VocabularyInterface::HIERARCHY_MULTIPLE: Multiple parents.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. Reset
|
||||
* the cache of the taxonomy_term storage handler instead.
|
||||
*/
|
||||
public function setHierarchy($hierarchy);
|
||||
|
||||
/**
|
||||
* Returns the vocabulary description.
|
||||
*
|
||||
* @return string
|
||||
* The vocabulary description.
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Config\Entity\DraggableListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of taxonomy vocabulary entities.
|
||||
*
|
||||
* @see \Drupal\taxonomy\Entity\Vocabulary
|
||||
*/
|
||||
class VocabularyListBuilder extends DraggableListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $entitiesKey = 'vocabularies';
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The messenger.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* Constructs a new VocabularyListBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type,
|
||||
AccountInterface $current_user,
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
RendererInterface $renderer = NULL,
|
||||
MessengerInterface $messenger) {
|
||||
parent::__construct($entity_type, $entity_type_manager->getStorage($entity_type->id()));
|
||||
|
||||
$this->currentUser = $current_user;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->renderer = $renderer;
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('current_user'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('renderer'),
|
||||
$container->get('messenger')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'taxonomy_overview_vocabularies';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = parent::getDefaultOperations($entity);
|
||||
|
||||
if (isset($operations['edit'])) {
|
||||
$operations['edit']['title'] = t('Edit vocabulary');
|
||||
}
|
||||
|
||||
if ($entity->access('access taxonomy overview')) {
|
||||
$operations['list'] = [
|
||||
'title' => t('List terms'),
|
||||
'weight' => 0,
|
||||
'url' => $entity->toUrl('overview-form'),
|
||||
];
|
||||
}
|
||||
|
||||
$taxonomy_term_access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_term');
|
||||
if ($taxonomy_term_access_control_handler->createAccess($entity->id())) {
|
||||
$operations['add'] = [
|
||||
'title' => t('Add terms'),
|
||||
'weight' => 10,
|
||||
'url' => Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => $entity->id()]),
|
||||
];
|
||||
}
|
||||
|
||||
unset($operations['delete']);
|
||||
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = t('Vocabulary name');
|
||||
$header['description'] = t('Description');
|
||||
|
||||
if ($this->currentUser->hasPermission('administer vocabularies') && !empty($this->weightKey)) {
|
||||
$header['weight'] = t('Weight');
|
||||
}
|
||||
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['label'] = $entity->label();
|
||||
$row['description']['data'] = ['#markup' => $entity->getDescription()];
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$entities = $this->load();
|
||||
// If there are not multiple vocabularies, disable dragging by unsetting the
|
||||
// weight key.
|
||||
if (count($entities) <= 1) {
|
||||
unset($this->weightKey);
|
||||
}
|
||||
$build = parent::render();
|
||||
|
||||
// If the weight key was unset then the table is in the 'table' key,
|
||||
// otherwise in vocabularies. The empty message is only needed if the table
|
||||
// is possibly empty, so there is no need to support the vocabularies key
|
||||
// here.
|
||||
if (isset($build['table'])) {
|
||||
$access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_vocabulary');
|
||||
$create_access = $access_control_handler->createAccess(NULL, NULL, [], TRUE);
|
||||
$this->renderer->addCacheableDependency($build['table'], $create_access);
|
||||
if ($create_access->isAllowed()) {
|
||||
$build['table']['#empty'] = t('No vocabularies available. <a href=":link">Add vocabulary</a>.', [
|
||||
':link' => Url::fromRoute('entity.taxonomy_vocabulary.add_form')->toString(),
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$build['table']['#empty'] = t('No vocabularies available.');
|
||||
}
|
||||
}
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::buildForm($form, $form_state);
|
||||
$form['vocabularies']['#attributes'] = ['id' => 'taxonomy'];
|
||||
$form['actions']['submit']['#value'] = t('Save');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
|
||||
$this->messenger->addStatus($this->t('The configuration options have been saved.'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorage;
|
||||
|
||||
/**
|
||||
* Defines a storage handler class for taxonomy vocabularies.
|
||||
*/
|
||||
class VocabularyStorage extends ConfigEntityStorage implements VocabularyStorageInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetCache(array $ids = NULL) {
|
||||
drupal_static_reset('taxonomy_vocabulary_get_names');
|
||||
parent::resetCache($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getToplevelTids($vids) {
|
||||
$tids = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('vid', $vids, 'IN')
|
||||
->condition('parent.target_id', 0)
|
||||
->execute();
|
||||
|
||||
return array_values($tids);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for vocabulary entity storage classes.
|
||||
*/
|
||||
interface VocabularyStorageInterface extends ConfigEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Gets top-level term IDs of vocabularies.
|
||||
*
|
||||
* @param array $vids
|
||||
* Array of vocabulary IDs.
|
||||
*
|
||||
* @return array
|
||||
* Array of top-level term IDs.
|
||||
*/
|
||||
public function getToplevelTids($vids);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user