ViewsConfigUpdater.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. <?php
  2. namespace Drupal\views;
  3. use Drupal\Component\Utility\NestedArray;
  4. use Drupal\Core\Config\Schema\ArrayElement;
  5. use Drupal\Core\Config\TypedConfigManagerInterface;
  6. use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
  7. use Drupal\Core\Entity\EntityFieldManagerInterface;
  8. use Drupal\Core\Entity\EntityTypeManagerInterface;
  9. use Drupal\Core\Entity\FieldableEntityInterface;
  10. use Drupal\Core\Entity\Sql\DefaultTableMapping;
  11. use Symfony\Component\DependencyInjection\ContainerInterface;
  12. /**
  13. * Provides a BC layer for modules providing old configurations.
  14. *
  15. * @internal
  16. * This class is only meant to fix outdated views configuration and its
  17. * methods should not be invoked directly. It will be removed once all the
  18. * public methods have been deprecated and removed.
  19. */
  20. class ViewsConfigUpdater implements ContainerInjectionInterface {
  21. /**
  22. * The entity type manager.
  23. *
  24. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  25. */
  26. protected $entityTypeManager;
  27. /**
  28. * The entity field manager.
  29. *
  30. * @var \Drupal\Core\Entity\EntityFieldManagerInterface
  31. */
  32. protected $entityFieldManager;
  33. /**
  34. * The typed config manager.
  35. *
  36. * @var \Drupal\Core\Config\TypedConfigManagerInterface
  37. */
  38. protected $typedConfigManager;
  39. /**
  40. * The views data service.
  41. *
  42. * @var \Drupal\views\ViewsData
  43. */
  44. protected $viewsData;
  45. /**
  46. * An array of helper data for the multivalue base field update.
  47. *
  48. * @var array
  49. */
  50. protected $multivalueBaseFieldsUpdateTableInfo;
  51. /**
  52. * ViewsConfigUpdater constructor.
  53. *
  54. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  55. * The entity type manager.
  56. * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
  57. * The entity field manager.
  58. * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
  59. * The typed config manager.
  60. * @param \Drupal\views\ViewsData $views_data
  61. * The views data service.
  62. */
  63. public function __construct(
  64. EntityTypeManagerInterface $entity_type_manager,
  65. EntityFieldManagerInterface $entity_field_manager,
  66. TypedConfigManagerInterface $typed_config_manager,
  67. ViewsData $views_data
  68. ) {
  69. $this->entityTypeManager = $entity_type_manager;
  70. $this->entityFieldManager = $entity_field_manager;
  71. $this->typedConfigManager = $typed_config_manager;
  72. $this->viewsData = $views_data;
  73. }
  74. /**
  75. * {@inheritdoc}
  76. */
  77. public static function create(ContainerInterface $container) {
  78. return new static(
  79. $container->get('entity_type.manager'),
  80. $container->get('entity_field.manager'),
  81. $container->get('config.typed'),
  82. $container->get('views.views_data')
  83. );
  84. }
  85. /**
  86. * Performs all required updates.
  87. *
  88. * @param \Drupal\views\ViewEntityInterface $view
  89. * The View to update.
  90. *
  91. * @return bool
  92. * Whether the view was updated.
  93. */
  94. public function updateAll(ViewEntityInterface $view) {
  95. return $this->processDisplayHandlers($view, FALSE, function (&$handler, $handler_type, $key, $display_id) use ($view) {
  96. $changed = FALSE;
  97. if ($this->processEntityLinkUrlHandler($handler, $handler_type)) {
  98. $changed = TRUE;
  99. }
  100. if ($this->processOperatorDefaultsHandler($handler, $handler_type)) {
  101. $changed = TRUE;
  102. }
  103. if ($this->processMultivalueBaseFieldHandler($handler, $handler_type, $key, $display_id, $view)) {
  104. $changed = TRUE;
  105. }
  106. return $changed;
  107. });
  108. }
  109. /**
  110. * Processes all display handlers.
  111. *
  112. * @param \Drupal\views\ViewEntityInterface $view
  113. * The View to update.
  114. * @param bool $return_on_changed
  115. * Whether processing should stop after a change is detected.
  116. * @param callable $handler_processor
  117. * A callback performing the actual update.
  118. *
  119. * @return bool
  120. * Whether the view was updated.
  121. */
  122. protected function processDisplayHandlers(ViewEntityInterface $view, $return_on_changed, callable $handler_processor) {
  123. $changed = FALSE;
  124. $displays = $view->get('display');
  125. $handler_types = ['field', 'argument', 'sort', 'relationship', 'filter'];
  126. foreach ($displays as $display_id => &$display) {
  127. foreach ($handler_types as $handler_type) {
  128. $handler_type_plural = $handler_type . 's';
  129. if (!empty($display['display_options'][$handler_type_plural])) {
  130. foreach ($display['display_options'][$handler_type_plural] as $key => &$handler) {
  131. if ($handler_processor($handler, $handler_type, $key, $display_id)) {
  132. $changed = TRUE;
  133. if ($return_on_changed) {
  134. return $changed;
  135. }
  136. }
  137. }
  138. }
  139. }
  140. }
  141. if ($changed) {
  142. $view->set('display', $displays);
  143. }
  144. return $changed;
  145. }
  146. /**
  147. * Add additional settings to the entity link field.
  148. *
  149. * @param \Drupal\views\ViewEntityInterface $view
  150. * The View to update.
  151. *
  152. * @return bool
  153. * Whether the view was updated.
  154. */
  155. public function needsEntityLinkUrlUpdate(ViewEntityInterface $view) {
  156. return $this->processDisplayHandlers($view, TRUE, function (&$handler, $handler_type) {
  157. return $this->processEntityLinkUrlHandler($handler, $handler_type);
  158. });
  159. }
  160. /**
  161. * Processes entity link URL fields.
  162. *
  163. * @param array $handler
  164. * A display handler.
  165. * @param string $handler_type
  166. * The handler type.
  167. *
  168. * @return bool
  169. * Whether the handler was updated.
  170. */
  171. protected function processEntityLinkUrlHandler(array &$handler, $handler_type) {
  172. $changed = FALSE;
  173. if ($handler_type === 'field') {
  174. if (isset($handler['plugin_id']) && $handler['plugin_id'] === 'entity_link') {
  175. // Add any missing settings for entity_link.
  176. if (!isset($handler['output_url_as_text'])) {
  177. $handler['output_url_as_text'] = FALSE;
  178. $changed = TRUE;
  179. }
  180. if (!isset($handler['absolute'])) {
  181. $handler['absolute'] = FALSE;
  182. $changed = TRUE;
  183. }
  184. }
  185. elseif (isset($handler['plugin_id']) && $handler['plugin_id'] === 'node_path') {
  186. // Convert the use of node_path to entity_link.
  187. $handler['plugin_id'] = 'entity_link';
  188. $handler['field'] = 'view_node';
  189. $handler['output_url_as_text'] = TRUE;
  190. $changed = TRUE;
  191. }
  192. }
  193. return $changed;
  194. }
  195. /**
  196. * Add additional settings to the entity link field.
  197. *
  198. * @param \Drupal\views\ViewEntityInterface $view
  199. * The View to update.
  200. *
  201. * @return bool
  202. * Whether the view was updated.
  203. */
  204. public function needsOperatorDefaultsUpdate(ViewEntityInterface $view) {
  205. return $this->processDisplayHandlers($view, TRUE, function (&$handler, $handler_type) {
  206. return $this->processOperatorDefaultsHandler($handler, $handler_type);
  207. });
  208. }
  209. /**
  210. * Processes operator defaults.
  211. *
  212. * @param array $handler
  213. * A display handler.
  214. * @param string $handler_type
  215. * The handler type.
  216. *
  217. * @return bool
  218. * Whether the handler was updated.
  219. */
  220. protected function processOperatorDefaultsHandler(array &$handler, $handler_type) {
  221. $changed = FALSE;
  222. if ($handler_type === 'filter') {
  223. if (!isset($handler['expose']['operator_limit_selection'])) {
  224. $handler['expose']['operator_limit_selection'] = FALSE;
  225. $changed = TRUE;
  226. }
  227. if (!isset($handler['expose']['operator_list'])) {
  228. $handler['expose']['operator_list'] = [];
  229. $changed = TRUE;
  230. }
  231. }
  232. return $changed;
  233. }
  234. /**
  235. * Update field names for multi-value base fields.
  236. *
  237. * @param \Drupal\views\ViewEntityInterface $view
  238. * The View to update.
  239. *
  240. * @return bool
  241. * Whether the view was updated.
  242. */
  243. public function needsMultivalueBaseFieldUpdate(ViewEntityInterface $view) {
  244. if ($this->getMultivalueBaseFieldUpdateTableInfo()) {
  245. return $this->processDisplayHandlers($view, TRUE, function (&$handler, $handler_type, $key, $display_id) use ($view) {
  246. return $this->processMultivalueBaseFieldHandler($handler, $handler_type, $key, $display_id, $view);
  247. });
  248. }
  249. return FALSE;
  250. }
  251. /**
  252. * Returns the multivalue base fields update table info.
  253. *
  254. * @return array
  255. * An array of multivalue base field info.
  256. */
  257. protected function getMultivalueBaseFieldUpdateTableInfo() {
  258. $table_info = &$this->multivalueBaseFieldsUpdateTableInfo;
  259. if (!isset($table_info)) {
  260. $table_info = [];
  261. foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
  262. if ($entity_type->hasHandlerClass('views_data') && $entity_type->entityClassImplements(FieldableEntityInterface::class)) {
  263. $base_field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
  264. $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
  265. $table_mapping = $entity_storage->getTableMapping($base_field_definitions);
  266. if (!$table_mapping instanceof DefaultTableMapping) {
  267. continue;
  268. }
  269. foreach ($base_field_definitions as $field_name => $base_field_definition) {
  270. $base_field_storage_definition = $base_field_definition->getFieldStorageDefinition();
  271. // Skip single value and custom storage base fields.
  272. if (!$base_field_storage_definition->isMultiple() || $base_field_storage_definition->hasCustomStorage()) {
  273. continue;
  274. }
  275. // Get the actual table, as well as the column for the main property
  276. // name, so we can perform an update on the views in
  277. // ::updateFieldNamesForMultivalueBaseFields().
  278. $table_name = $table_mapping->getFieldTableName($field_name);
  279. $main_property_name = $base_field_storage_definition->getMainPropertyName();
  280. $table_info[$table_name][$field_name] = $table_mapping->getFieldColumnName($base_field_storage_definition, $main_property_name);
  281. }
  282. }
  283. }
  284. }
  285. return $table_info;
  286. }
  287. /**
  288. * Processes handlers affected by the multivalue base field update.
  289. *
  290. * @param array $handler
  291. * A display handler.
  292. * @param string $handler_type
  293. * The handler type.
  294. * @param string $key
  295. * The handler key.
  296. * @param string $display_id
  297. * The handler display ID.
  298. * @param \Drupal\views\ViewEntityInterface $view
  299. * The view being updated.
  300. *
  301. * @return bool
  302. * Whether the handler was updated.
  303. */
  304. protected function processMultivalueBaseFieldHandler(array &$handler, $handler_type, $key, $display_id, ViewEntityInterface $view) {
  305. $changed = FALSE;
  306. // If there are no multivalue base fields we have nothing to do.
  307. $table_info = $this->getMultivalueBaseFieldUpdateTableInfo();
  308. if (!$table_info) {
  309. return $changed;
  310. }
  311. // Only if the wrong field name is set do we process the field. It
  312. // could already be using the correct field. Like "user__roles" vs
  313. // "roles_target_id".
  314. if (isset($handler['table']) && isset($table_info[$handler['table']]) && isset($table_info[$handler['table']][$handler['field']])) {
  315. $changed = TRUE;
  316. $original_field_name = $handler['field'];
  317. $handler['field'] = $table_info[$handler['table']][$original_field_name];
  318. $handler['plugin_id'] = $this->viewsData->get($handler['table'])[$table_info[$handler['table']][$original_field_name]][$handler_type]['id'];
  319. // Retrieve type data information about the handler to clean it up
  320. // reliably. We need to manually create a typed view rather than
  321. // instantiating the current one, as the schema will be affected by the
  322. // updated values.
  323. $id = 'views.view.' . $view->id();
  324. $path_to_handler = "display.$display_id.display_options.{$handler_type}s.$key";
  325. $view_config = $view->toArray();
  326. $keys = explode('.', $path_to_handler);
  327. NestedArray::setValue($view_config, $keys, $handler);
  328. /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_view */
  329. $typed_view = $this->typedConfigManager->createFromNameAndData($id, $view_config);
  330. /** @var \Drupal\Core\Config\Schema\ArrayElement $typed_handler */
  331. $typed_handler = $typed_view->get($path_to_handler);
  332. // Filter values we want to convert from a string to an array.
  333. if ($handler_type === 'filter' && $typed_handler->get('value') instanceof ArrayElement && is_string($handler['value'])) {
  334. // An empty string cast to an array is an array with one element.
  335. if ($handler['value'] === '') {
  336. $handler['value'] = [];
  337. }
  338. else {
  339. $handler['value'] = (array) $handler['value'];
  340. }
  341. $handler['operator'] = $this->mapOperatorFromSingleToMultiple($handler['operator']);
  342. }
  343. // For all the other fields we try to determine the fields using config
  344. // schema and remove everything not being defined in the new handler.
  345. foreach (array_keys($handler) as $handler_key) {
  346. if (!isset($typed_handler->getDataDefinition()['mapping'][$handler_key])) {
  347. unset($handler[$handler_key]);
  348. }
  349. }
  350. }
  351. return $changed;
  352. }
  353. /**
  354. * Maps a single operator to a multiple one, if possible.
  355. *
  356. * @param string $single_operator
  357. * A single operator.
  358. *
  359. * @return string
  360. * A multiple operator or the original one if no mapping was available.
  361. */
  362. protected function mapOperatorFromSingleToMultiple($single_operator) {
  363. switch ($single_operator) {
  364. case '=':
  365. return 'or';
  366. case '!=':
  367. return 'not';
  368. default:
  369. return $single_operator;
  370. }
  371. }
  372. }