views.install 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. <?php
  2. /**
  3. * @file
  4. * Contains install and update functions for Views.
  5. */
  6. use Drupal\Core\Config\Schema\ArrayElement;
  7. use Drupal\views\Views;
  8. /**
  9. * Implements hook_install().
  10. */
  11. function views_install() {
  12. module_set_weight('views', 10);
  13. }
  14. /**
  15. * Update views field plugins.
  16. */
  17. function views_update_8001(&$sandbox) {
  18. $config_factory = \Drupal::configFactory();
  19. $ids = [];
  20. $message = NULL;
  21. $ago_formats = [
  22. 'time ago',
  23. 'time hence',
  24. 'time span',
  25. 'raw time ago',
  26. 'raw time hence',
  27. 'raw time span',
  28. 'inverse time span',
  29. ];
  30. foreach ($config_factory->listAll('views.view.') as $view_config_name) {
  31. $view = $config_factory->getEditable($view_config_name);
  32. $displays = $view->get('display');
  33. foreach ($displays as $display_name => $display) {
  34. if (!empty($display['display_options']['fields'])) {
  35. foreach ($display['display_options']['fields'] as $field_name => $field) {
  36. if (isset($field['entity_type']) && $field['plugin_id'] === 'date') {
  37. $ids[] = $view->get('id');
  38. // Grab the settings we need to move to a different place in the
  39. // config schema.
  40. $date_format = !empty($field['date_format']) ? $field['date_format'] : 'medium';
  41. $custom_date_format = !empty($field['custom_date_format']) ? $field['custom_date_format'] : '';
  42. $timezone = !empty($field['timezone']) ? $field['timezone'] : '';
  43. // Save off the base part of the config path we are updating.
  44. $base = "display.$display_name.display_options.fields.$field_name";
  45. if (in_array($date_format, $ago_formats)) {
  46. // Update the field to use the Field API formatter.
  47. $view->set($base . '.plugin_id', 'field');
  48. $view->set($base . '.type', 'timestamp_ago');
  49. // Ensure the granularity is an integer, which is defined in the
  50. // field.formatter.settings.timestamp_ago schema.
  51. $granularity = is_numeric($custom_date_format) ? (int) $custom_date_format : 2;
  52. // Add the new settings.
  53. if ($date_format === 'time ago' || $date_format === 'time hence' || $date_format === 'time span') {
  54. $view->set($base . '.settings.future_format', '@interval hence');
  55. $view->set($base . '.settings.past_format', '@interval ago');
  56. $view->set($base . '.settings.granularity', $granularity);
  57. }
  58. elseif ($date_format === 'raw time ago' || $date_format === 'raw time hence') {
  59. $view->set($base . '.settings.future_format', '@interval');
  60. $view->set($base . '.settings.past_format', '@interval');
  61. $view->set($base . '.settings.granularity', $granularity);
  62. }
  63. elseif ($date_format === 'raw time span') {
  64. $view->set($base . '.settings.future_format', '@interval');
  65. $view->set($base . '.settings.past_format', '-@interval');
  66. $view->set($base . '.settings.granularity', $granularity);
  67. }
  68. elseif ($date_format === 'inverse time span') {
  69. $view->set($base . '.settings.future_format', '-@interval');
  70. $view->set($base . '.settings.past_format', '@interval');
  71. $view->set($base . '.settings.granularity', $granularity);
  72. }
  73. }
  74. else {
  75. // Update the field to use the Field API formatter.
  76. $view->set($base . '.plugin_id', 'field');
  77. $view->set($base . '.type', 'timestamp');
  78. // Add the new settings, and make sure everything is a string
  79. // to conform with the field.formatter.settings.timestamp schema.
  80. $view->set($base . '.settings.date_format', (string) $date_format);
  81. $view->set($base . '.settings.custom_date_format', (string) $custom_date_format);
  82. $view->set($base . '.settings.timezone', (string) $timezone);
  83. }
  84. // Remove the old settings.
  85. $view->clear($base . '.date_format');
  86. $view->clear($base . '.custom_date_format');
  87. $view->clear($base . '.timezone');
  88. }
  89. }
  90. }
  91. }
  92. $view->save(TRUE);
  93. }
  94. if (!empty($ids)) {
  95. $message = \Drupal::translation()->translate('Updated field plugins for views: @ids', ['@ids' => implode(', ', array_unique($ids))]);
  96. }
  97. return $message;
  98. }
  99. /**
  100. * Updates %1 and !1 tokens to argument tokens.
  101. */
  102. function views_update_8002() {
  103. $config_factory = \Drupal::configFactory();
  104. foreach ($config_factory->listAll('views.view.') as $view_config_name) {
  105. $view = $config_factory->getEditable($view_config_name);
  106. $displays = $view->get('display');
  107. $argument_map_per_display = _views_update_argument_map($displays);
  108. $changed = FALSE;
  109. // Update all the field settings, which support tokens.
  110. foreach ($displays as $display_name => &$display) {
  111. if (!empty($display['display_options']['fields'])) {
  112. $token_values = [
  113. 'path',
  114. 'alt',
  115. 'link_class',
  116. 'rel',
  117. 'target',
  118. 'query',
  119. 'fragment',
  120. 'prefix',
  121. 'suffix',
  122. 'more_link_text',
  123. 'more_link_path',
  124. 'link_attributes',
  125. 'text',
  126. ];
  127. foreach ($display['display_options']['fields'] as $field_name => &$field) {
  128. foreach ($token_values as $token_name) {
  129. if (!empty($field['alter'][$token_name])) {
  130. if (is_array($field['alter'][$token_name])) {
  131. foreach (array_keys($field['alter'][$token_name]) as $key) {
  132. $field['alter'][$token_name][$key] = _views_update_8002_token_update($field['alter'][$token_name][$key], $argument_map_per_display[$display_name]);
  133. $changed = TRUE;
  134. }
  135. }
  136. else {
  137. $field['alter'][$token_name] = _views_update_8002_token_update($field['alter'][$token_name], $argument_map_per_display[$display_name]);
  138. $changed = TRUE;
  139. }
  140. }
  141. }
  142. }
  143. }
  144. }
  145. // Update the area handlers with tokens.
  146. foreach ($displays as $display_name => &$display) {
  147. $area_types = ['header', 'footer', 'empty'];
  148. foreach ($area_types as $area_type) {
  149. if (!empty($display['display_options'][$area_type])) {
  150. foreach ($display['display_options'][$area_type] as &$area) {
  151. switch ($area['plugin_id']) {
  152. case 'title':
  153. $area['title'] = _views_update_8002_token_update($area['title'], $argument_map_per_display[$display_name]);
  154. $changed = TRUE;
  155. break;
  156. case 'result':
  157. $area['content'] = _views_update_8002_token_update($area['content'], $argument_map_per_display[$display_name]);
  158. $changed = TRUE;
  159. break;
  160. case 'text':
  161. $area['content']['value'] = _views_update_8002_token_update($area['content']['value'], $argument_map_per_display[$display_name]);
  162. $changed = TRUE;
  163. break;
  164. case 'text_custom':
  165. $area['content'] = _views_update_8002_token_update($area['content'], $argument_map_per_display[$display_name]);
  166. $changed = TRUE;
  167. break;
  168. case 'entity':
  169. $area['target'] = _views_update_8002_token_update($area['target'], $argument_map_per_display[$display_name]);
  170. $changed = TRUE;
  171. break;
  172. }
  173. }
  174. }
  175. }
  176. }
  177. // Update the argument title settings.
  178. foreach ($displays as $display_name => &$display) {
  179. if (!empty($display['display_options']['arguments'])) {
  180. foreach ($display['display_options']['arguments'] as &$argument) {
  181. if (isset($argument['exception']['title'])) {
  182. $argument['exception']['title'] = _views_update_8002_token_update($argument['exception']['title'], $argument_map_per_display[$display_name]);
  183. $changed = TRUE;
  184. }
  185. if (isset($argument['title'])) {
  186. $argument['title'] = _views_update_8002_token_update($argument['title'], $argument_map_per_display[$display_name]);
  187. $changed = TRUE;
  188. }
  189. }
  190. }
  191. }
  192. // Update the display title settings.
  193. // Update the more link text and more link URL.
  194. foreach ($displays as $display_name => &$display) {
  195. if (!empty($display['display_options']['title'])) {
  196. $display['display_options']['title'] = _views_update_8002_token_update($display['display_options']['title'], $argument_map_per_display[$display_name]);
  197. $changed = TRUE;
  198. }
  199. if (!empty($display['display_options']['use_more_text'])) {
  200. $display['display_options']['use_more_text'] = _views_update_8002_token_update($display['display_options']['use_more_text'], $argument_map_per_display[$display_name]);
  201. $changed = TRUE;
  202. }
  203. if (!empty($display['display_options']['link_url'])) {
  204. $display['display_options']['link_url'] = _views_update_8002_token_update($display['display_options']['link_url'], $argument_map_per_display[$display_name]);
  205. $changed = TRUE;
  206. }
  207. }
  208. // Update custom classes for row class + grid classes.
  209. // Update RSS description field.
  210. foreach ($displays as $display_name => &$display) {
  211. if (!empty($display['display_options']['style'])) {
  212. if (!empty($display['display_options']['style']['options']['row_class_custom'])) {
  213. $display['display_options']['style']['options']['row_class_custom'] = _views_update_8002_token_update($display['display_options']['style']['options']['row_class_custom'], $argument_map_per_display[$display_name]);
  214. $changed = TRUE;
  215. }
  216. if (!empty($display['display_options']['style']['options']['col_class_custom'])) {
  217. $display['display_options']['style']['options']['col_class_custom'] = _views_update_8002_token_update($display['display_options']['style']['options']['col_class_custom'], $argument_map_per_display[$display_name]);
  218. $changed = TRUE;
  219. }
  220. if (!empty($display['display_options']['style']['options']['description'])) {
  221. $display['display_options']['style']['options']['description'] = _views_update_8002_token_update($display['display_options']['style']['options']['description'], $argument_map_per_display[$display_name]);
  222. $changed = TRUE;
  223. }
  224. }
  225. }
  226. if ($changed) {
  227. $view->set('display', $displays);
  228. $view->save(TRUE);
  229. }
  230. }
  231. }
  232. /**
  233. * Updates a views configuration string from using %/! to twig tokens.
  234. *
  235. * @param string $text
  236. * Text in which to search for argument tokens and replace them with their
  237. * twig representation.
  238. * @param array $argument_map
  239. * A map of argument machine names keyed by their previous index.
  240. *
  241. * @return string
  242. * The updated token.
  243. */
  244. function _views_update_8002_token_update($text, array $argument_map) {
  245. $text = preg_replace_callback('/%(\d)/', function ($match) use ($argument_map) {
  246. return "{{ arguments.{$argument_map[$match[1]]} }}";
  247. }, $text);
  248. $text = preg_replace_callback('/!(\d)/', function ($match) use ($argument_map) {
  249. return "{{ raw_arguments.{$argument_map[$match[1]]} }}";
  250. }, $text);
  251. return $text;
  252. }
  253. /**
  254. * Builds an argument map for each Views display.
  255. *
  256. * @param array $displays
  257. * A list of Views displays.
  258. *
  259. * @return array
  260. * The argument map keyed by display id.
  261. */
  262. function _views_update_argument_map($displays) {
  263. $argument_map = [];
  264. foreach ($displays as $display_id => $display) {
  265. $argument_map[$display_id] = [];
  266. if (isset($display['display_options']['arguments'])) {
  267. foreach (array_keys($display['display_options']['arguments']) as $number => $name) {
  268. $argument_map[$display_id][$number + 1] = $name;
  269. }
  270. }
  271. elseif (isset($displays['default']['display_options']['arguments'])) {
  272. foreach (array_keys($displays['default']['display_options']['arguments']) as $number => $name) {
  273. $argument_map[$display_id][$number + 1] = $name;
  274. }
  275. }
  276. }
  277. return $argument_map;
  278. }
  279. /**
  280. * Clear caches to fix entity operations field.
  281. */
  282. function views_update_8003() {
  283. // Empty update to cause a cache flush so that views data is rebuilt. Entity
  284. // types that don't implement a list builder cannot have the entity operations
  285. // field.
  286. // Use hook_post_update_NAME() instead to clear the cache.The use
  287. // of hook_update_N to clear the cache has been deprecated see
  288. // https://www.drupal.org/node/2960601 for more details.
  289. }
  290. /**
  291. * Clear caches due to updated entity views data.
  292. */
  293. function views_update_8004() {
  294. // Empty update to cause a cache flush so that views data is rebuilt.
  295. // Use hook_post_update_NAME() instead to clear the cache.The use
  296. // of hook_update_N to clear the cache has been deprecated see
  297. // https://www.drupal.org/node/2960601 for more details.
  298. }
  299. /**
  300. * Clear views data cache.
  301. */
  302. function views_update_8005() {
  303. // Empty update function to rebuild the views data.
  304. // Use hook_post_update_NAME() instead to clear the cache.The use
  305. // of hook_update_N to clear the cache has been deprecated see
  306. // https://www.drupal.org/node/2960601 for more details.
  307. }
  308. /**
  309. * Clear caches due to updated entity views data.
  310. */
  311. function views_update_8100() {
  312. // Empty update to cause a cache flush so that views data is rebuilt.
  313. // Use hook_post_update_NAME() instead to clear the cache.The use
  314. // of hook_update_N to clear the cache has been deprecated see
  315. // https://www.drupal.org/node/2960601 for more details.
  316. }
  317. /**
  318. * Set default values for enabled/expanded flag on page displays.
  319. */
  320. function views_update_8101() {
  321. $config_factory = \Drupal::configFactory();
  322. foreach ($config_factory->listAll('views.view.') as $view_config_name) {
  323. $view = $config_factory->getEditable($view_config_name);
  324. $save = FALSE;
  325. foreach ($view->get('display') as $display_id => $display) {
  326. if ($display['display_plugin'] == 'page') {
  327. $display['display_options']['menu']['enabled'] = TRUE;
  328. $display['display_options']['menu']['expanded'] = FALSE;
  329. $view->set("display.$display_id", $display);
  330. $save = TRUE;
  331. }
  332. }
  333. if ($save) {
  334. $view->save();
  335. }
  336. }
  337. }
  338. /**
  339. * Rebuild the container to add a new container parameter.
  340. */
  341. function views_update_8200() {
  342. // Empty update to cause a cache rebuild so that the container is rebuilt.
  343. // Use hook_post_update_NAME() instead to clear the cache.The use
  344. // of hook_update_N to clear the cache has been deprecated see
  345. // https://www.drupal.org/node/2960601 for more details.
  346. }
  347. /**
  348. * Rebuild cache to refresh the views config schema.
  349. */
  350. function views_update_8201() {
  351. // Empty update to cause a cache rebuild so that config schema get refreshed.
  352. // Use hook_post_update_NAME() instead to clear the cache.The use
  353. // of hook_update_N to clear the cache has been deprecated see
  354. // https://www.drupal.org/node/2960601 for more details.
  355. }
  356. /**
  357. * Update field names for multi-value base fields.
  358. */
  359. function views_update_8500() {
  360. // Find all multi-value base fields for content entities.
  361. $entity_type_manager = \Drupal::entityTypeManager();
  362. $entity_field_manager = \Drupal::service('entity_field.manager');
  363. $table_update_info = [];
  364. foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) {
  365. if ($entity_type->hasHandlerClass('views_data')) {
  366. $base_field_definitions = $entity_field_manager->getBaseFieldDefinitions($entity_type_id);
  367. $entity_storage = $entity_type_manager->getStorage($entity_type_id);
  368. $table_mapping = $entity_storage->getTableMapping($base_field_definitions);
  369. foreach ($base_field_definitions as $field_name => $base_field_definition) {
  370. $base_field_storage_definition = $base_field_definition->getFieldStorageDefinition();
  371. // Skip single value and custom storage base fields.
  372. if (!$base_field_storage_definition->isMultiple() || $base_field_storage_definition->hasCustomStorage()) {
  373. continue;
  374. }
  375. // Get the actual table, as well as the column for the main property
  376. // name so we can perform an update later on the views.
  377. $table_name = $table_mapping->getFieldTableName($field_name);
  378. $main_property_name = $base_field_storage_definition->getMainPropertyName();
  379. $table_update_info[$table_name][$field_name] = $table_mapping->getFieldColumnName($base_field_storage_definition, $main_property_name);
  380. }
  381. }
  382. }
  383. if (empty($table_update_info)) {
  384. return;
  385. }
  386. $config_factory = \Drupal::configFactory();
  387. /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
  388. $typed_config_manager = \Drupal::service('config.typed');
  389. $views_data = Views::viewsData();
  390. $handler_types = ['field', 'argument', 'sort', 'relationship', 'filter'];
  391. $required_cleanup_handlers = [];
  392. foreach ($config_factory->listAll('views.view.') as $id) {
  393. $view = $config_factory->getEditable($id);
  394. $changed = FALSE;
  395. foreach ($view->get('display') as $display_id => &$display) {
  396. foreach ($handler_types as $handler_type_singular) {
  397. $handler_type_plural = $handler_type_singular . 's';
  398. $handler_data = $view->get("display.$display_id.display_options.$handler_type_plural");
  399. if (empty($handler_data)) {
  400. continue;
  401. }
  402. foreach ($handler_data as $key => $data) {
  403. // If this handler has a table we're interested in, update the field
  404. // name.
  405. $table = $data['table'];
  406. if (isset($table_update_info[$table])) {
  407. $path_to_handler = "display.$display_id.display_options.$handler_type_plural.$key";
  408. $path_field = "{$path_to_handler}.field";
  409. $path_plugin_id = "{$path_to_handler}.plugin_id";
  410. $original_field_name = $view->get($path_field);
  411. // Only if the wrong field name is set do we change the field. It
  412. // could already be using the correct field. Like
  413. // user__roles/roles_target_id.
  414. if (isset($table_update_info[$table][$original_field_name])) {
  415. $required_cleanup_handlers[$id][] = $path_to_handler;
  416. // Set both the new table field as well as new 'plugin_id' field.
  417. $view->set($path_field, $table_update_info[$table][$original_field_name]);
  418. $view->set($path_plugin_id, $views_data->get($table)[$table_update_info[$table][$original_field_name]][$handler_type_singular]['id']);
  419. $changed = TRUE;
  420. }
  421. }
  422. }
  423. }
  424. }
  425. if ($changed) {
  426. $view->save(TRUE);
  427. }
  428. }
  429. // Beside of updating the field and plugin ID we also need to truncate orphan
  430. // keys so he configuration applies to the config schema.
  431. // We cannot do that inline in the other code, due to caching issues with
  432. // typed configuration.
  433. foreach ($required_cleanup_handlers as $id => $paths_to_handlers) {
  434. $changed = FALSE;
  435. $typed_view = $typed_config_manager->get($id);
  436. $view = $config_factory->getEditable($id);
  437. foreach ($paths_to_handlers as $path_to_handler) {
  438. /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_view */
  439. /** @var \Drupal\Core\Config\Schema\ArrayElement $typed_config */
  440. $typed_config = $typed_view->get($path_to_handler);
  441. $config = $typed_config->getValue();
  442. // Filter values we want to convert from a string to an array.
  443. if (strpos($path_to_handler, 'filters') !== FALSE && $typed_config->get('value') instanceof ArrayElement && is_string($config['value'])) {
  444. // An empty string casted to an array is an array with one
  445. // element.
  446. if ($config['value'] === '') {
  447. $config['value'] = [];
  448. }
  449. else {
  450. $config['value'] = (array) $config['value'];
  451. }
  452. }
  453. // For all the other fields we try to determine the fields using
  454. // config schema and remove everything which is not needed.
  455. foreach (array_keys($config) as $config_key) {
  456. if (!isset($typed_config->getDataDefinition()['mapping'][$config_key])) {
  457. unset($config[$config_key]);
  458. $changed = TRUE;
  459. }
  460. }
  461. $typed_config->setValue($config);
  462. $view->set($path_to_handler, $typed_config->getValue());
  463. }
  464. if ($changed) {
  465. $view->save();
  466. }
  467. }
  468. }