diff --git a/core/modules/update/src/Controller/UpdateController.php b/core/modules/update/src/Controller/UpdateController.php index f9419b3..59ef8ce 100644 --- a/core/modules/update/src/Controller/UpdateController.php +++ b/core/modules/update/src/Controller/UpdateController.php @@ -56,9 +56,6 @@ public function updateStatus() { $this->moduleHandler()->loadInclude('update', 'compare.inc'); $build['#data'] = update_calculate_project_data($available); } - else { - $build['#data'] = _update_no_data(); - } return $build; } diff --git a/core/modules/update/templates/update-project-status.html.twig b/core/modules/update/templates/update-project-status.html.twig new file mode 100644 index 0000000..dbaf783 --- /dev/null +++ b/core/modules/update/templates/update-project-status.html.twig @@ -0,0 +1,112 @@ +{# +/** + * @file + * Default theme implementation for the project status report. + * + * Available variables: + * - title: The project title. + * - url: The project url. + * - status: The project status. + * - label: The project status label. + * - attributes: HTML attributes for the project status. + * - reason: The reason you should update the project. + * - icon: The project status version indicator icon. + * - existing_version: The version of the installed project. + * - versions: The available versions of the project. + * - install_type: The type of project (e.g., dev). + * - datestamp: The date/time of a project version's release. + * - extras: HTML attributes and additional information about the project. + * - attributes: HTML attributes for the extra item. + * - label: The label for an extra item. + * - data: The data about an extra item. + * - includes: The projects within a project. + * - disabled: The disabled included projects. + * - base_themes: The base theme declared for the project. + * - sub_themes: The subthemes declared for the project. + * + * @see template_preprocess_update_project_status() + * + * @ingroup themeable + */ +#} +
+ {%- if status.label -%} + {{ status.label }} + {%- else -%} + {{ status.reason|e }} + {%- endif %} + {{ status.icon }} +
+ +
+ {%- if url -%} + {{ title|e }} + {%- else -%} + {{ title|e }} + {%- endif %} + {{ existing_version|e }} + {% if install_type == 'dev' and datestamp %} + ({{ datestamp }}) + {% endif %} +
+ +{% if versions %} +
+ {% for version in versions %} + {{ version }} + {% endfor %} +
+{% endif %} + +
+ {% if extras %} +
+ {% for extra in extras %} + + {{ extra.label|e }}: {{ extra.data }} +
+ {% endfor %} +
+ {% endif %} +
+ {% set includes = includes|join(', ') %} + {% if disabled %} + {{ 'Includes:'|t }} + + {% else %} + {% trans %} + Includes: {{ includes|placeholder }} + {% endtrans %} + {% endif %} +
+ + {% if base_themes %} + {% set basethemes = base_themes|join(', ') %} +
+ {% trans %} + Depends on: {{ basethemes|passthrough }} + {% endtrans %} +
+ {% endif %} + + {% if sub_themes %} + {% set subthemes = sub_themes|join(', ') %} +
+ {% trans %} + Required by: {{ subthemes|placeholder }} + {% endtrans %} +
+ {% endif %} + diff --git a/core/modules/update/templates/update-report.html.twig b/core/modules/update/templates/update-report.html.twig new file mode 100644 index 0000000..ae121cc --- /dev/null +++ b/core/modules/update/templates/update-report.html.twig @@ -0,0 +1,25 @@ +{# +/** + * @file + * Default theme implementation for the project status report. + * + * Available variables: + * - last_checked: Themed last time update data was checked. + * - no_updates_message: Message when there are no project updates. + * - project_types: A list of project types. + * - label: The project type label. + * - table: The project status table. + * + * @see template_preprocess_update_report() + * + * @ingroup themeable + */ +#} +{{ last_checked }} + +{% for project_type in project_types %} +

{{ project_type.label }}

+ {{ project_type.table }} +{% else %} +

{{ no_updates_message }}

+{% endfor %} diff --git a/core/modules/update/update.module b/core/modules/update/update.module index e34965e..702958d 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -180,13 +180,15 @@ function update_theme() { 'update_report' => array( 'variables' => array('data' => NULL), 'file' => 'update.report.inc', + 'template' => 'update-report', ), - 'update_version' => array( - 'variables' => array('version' => NULL, 'tag' => NULL, 'class' => array()), + 'update_project_status' => array( + 'variables' => array('project' => array(), 'includes_status' => array()), 'file' => 'update.report.inc', + 'template' => 'update-project-status', ), - 'update_status_label' => array( - 'variables' => array('status' => NULL), + 'update_version' => array( + 'variables' => array('version' => NULL, 'tag' => NULL, 'class' => array()), 'file' => 'update.report.inc', ), ); diff --git a/core/modules/update/update.report.inc b/core/modules/update/update.report.inc index a0afdb7..2b1900f 100644 --- a/core/modules/update/update.report.inc +++ b/core/modules/update/update.report.inc @@ -7,9 +7,12 @@ use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\String; +use Drupal\Core\Template\Attribute; /** - * Returns HTML for the project status report. + * Prepares variables for project status report templates. + * + * Default template: update-report.html.twig. * * @param array $variables * An associative array containing: @@ -17,26 +20,27 @@ * * @ingroup themeable */ -function theme_update_report($variables) { +function template_preprocess_update_report(&$variables) { $data = $variables['data']; $last = \Drupal::state()->get('update.last_check') ?: 0; - $update_last_check = array( + + $variables['last_checked'] = array( '#theme' => 'update_last_check', '#last' => $last, + // Attach the library to a variable that gets printed always. + '#attached' => array( + 'library' => array( + 'update/drupal.update.admin', + ), + ) ); - $output = drupal_render($update_last_check); - - if (!is_array($data)) { - // @todo When converting this theme function to Twig, double-check with the - // caller to ensure $data is not double-escaped. At present, we cannot - // guarantee within this function that it is safe. Address in: - // https://www.drupal.org/node/1898466 - $output .= '

' . $data . '

'; - return SafeMarkup::set($output); + + // For no project update data, populate no data message. + if (empty($data)) { + $variables['no_updates_message'] = _update_no_data(); } - $header = array(); $rows = array(); // Create an array of status values keyed by module or theme name, since @@ -49,298 +53,302 @@ function theme_update_report($variables) { } foreach ($data as $project) { + $project_status = array( + '#theme' => 'update_project_status', + '#project' => $project, + '#includes_status' => $status, + ); + + // Build project rows. + if (!isset($rows[$project['project_type']])) { + $rows[$project['project_type']] = array( + '#type' => 'table', + '#attributes' => array('class' => array('update')), + ); + } + $row_key = !empty($project['title']) ? drupal_strtolower($project['title']) : drupal_strtolower($project['name']); + + // Add the project status row and details. + $rows[$project['project_type']][$row_key]['status'] = $project_status; + + // Add project status class attribute to the table row. switch ($project['status']) { case UPDATE_CURRENT: - $class = 'ok'; - $uri = 'core/misc/icons/73b355/check.png'; - $text = t('Ok'); + $rows[$project['project_type']][$row_key]['#attributes'] = array('class' => array('ok')); break; case UPDATE_UNKNOWN: case UPDATE_FETCH_PENDING: case UPDATE_NOT_FETCHED: - $class = 'unknown'; - $uri = 'core/misc/icons/e29700/warning.png'; - $text = t('Warning'); + $rows[$project['project_type']][$row_key]['#attributes'] = array('class' => array('unknown')); break; case UPDATE_NOT_SECURE: case UPDATE_REVOKED: case UPDATE_NOT_SUPPORTED: - $class = 'error'; - $uri = 'core/misc/icons/ea2800/error.png'; - $text = t('Error'); + $rows[$project['project_type']][$row_key]['#attributes'] = array('class' => array('error')); break; case UPDATE_NOT_CHECKED: case UPDATE_NOT_CURRENT: default: - $class = 'warning'; - $uri = 'core/misc/icons/e29700/warning.png'; - $text = t('Warning'); + $rows[$project['project_type']][$row_key]['#attributes'] = array('class' => array('warning')); break; } + } - $icon = array( - '#theme' => 'image', - '#width' => 18, - '#height' => 18, - '#uri' => $uri, - '#alt' => $text, - '#title' => $text, - ); + $project_types = array( + 'core' => t('Drupal core'), + 'module' => t('Modules'), + 'theme' => t('Themes'), + 'module-disabled' => t('Disabled modules'), + 'theme-disabled' => t('Disabled themes'), + ); - $row = '
'; - $update_status_label = array('#theme' => 'update_status_label', '#status' => $project['status']); - $status_label = drupal_render($update_status_label); - $row .= !empty($status_label) ? $status_label : String::checkPlain($project['reason']); - $row .= '' . drupal_render($icon) . ''; - $row .= "
\n"; - - $row .= '
'; - if (isset($project['title'])) { - if (isset($project['link'])) { - $row .= l($project['title'], $project['link']); - } - else { - $row .= String::checkPlain($project['title']); - } - } - else { - $row .= String::checkPlain($project['name']); - } - $row .= ' ' . String::checkPlain($project['existing_version']); - if ($project['install_type'] == 'dev' && !empty($project['datestamp'])) { - $row .= ' (' . format_date($project['datestamp'], 'custom', 'Y-M-d') . ')'; + $variables['project_types'] = array(); + foreach ($project_types as $type_name => $type_label) { + if (!empty($rows[$type_name])) { + ksort($rows[$type_name]); + $variables['project_types'][] = array( + 'label' => $type_label, + 'table' => $rows[$type_name], + ); } - $row .= "
\n"; - - $versions_inner = ''; - $security_class = array(); - $version_class = array(); - if (isset($project['recommended'])) { - if ($project['status'] != UPDATE_CURRENT || $project['existing_version'] !== $project['recommended']) { - - // First, figure out what to recommend. - // If there's only 1 security update and it has the same version we're - // recommending, give it the same CSS class as if it was recommended, - // but don't print out a separate "Recommended" line for this project. - if (!empty($project['security updates']) && count($project['security updates']) == 1 && $project['security updates'][0]['version'] === $project['recommended']) { - $security_class[] = 'version-recommended'; - $security_class[] = 'version-recommended-strong'; - } - else { - $version_class[] = 'version-recommended'; - // Apply an extra class if we're displaying both a recommended - // version and anything else for an extra visual hint. - if ($project['recommended'] !== $project['latest_version'] - || !empty($project['also']) - || ($project['install_type'] == 'dev' - && isset($project['dev_version']) - && $project['latest_version'] !== $project['dev_version'] - && $project['recommended'] !== $project['dev_version']) - || (isset($project['security updates'][0]) - && $project['recommended'] !== $project['security updates'][0]) - ) { - $version_class[] = 'version-recommended-strong'; - } - $update_version = array( - '#theme' => 'update_version', - '#version' => $project['releases'][$project['recommended']], - '#tag' => t('Recommended version:'), - '#class' => $version_class, - ); - $versions_inner .= drupal_render($update_version); - } + } +} - // Now, print any security updates. - if (!empty($project['security updates'])) { - $security_class[] = 'version-security'; - foreach ($project['security updates'] as $security_update) { - $update_version = array( - '#theme' => 'update_version', - '#version' => $security_update, - '#tag' => t('Security update:'), - '#class' => $security_class, - ); - $versions_inner .= drupal_render($update_version); - } - } - } +/** + * Prepares variables for update project status templates. + * + * Default template: update-project-status.html.twig. + * + * @param array $variables + * An associative array containing: + * - data: An array of data about each project's status. + * - project_status: + * + * @ingroup themeable + */ +function template_preprocess_update_project_status(&$variables) { + // Storing by reference because we are sorting the project values. + $project = &$variables['project']; + $includes_status = $variables['includes_status']; + + // Set the project title and URL. + $variables['title'] = (isset($project['title'])) ? $project['title'] : $project['name']; + $variables['url'] = (isset($project['link'])) ? url($project['link']) : NULL; + + $variables['install_type'] = $project['install_type']; + if ($project['install_type'] == 'dev' && !empty($project['datestamp'])) { + $variables['datestamp'] = format_date($project['datestamp'], 'custom', 'Y-M-d'); + } - if ($project['recommended'] !== $project['latest_version']) { - $update_version = array( - '#theme' => 'update_version', - '#version' => $project['releases'][$project['latest_version']], - '#tag' => t('Latest version:'), - '#class' => array('version-latest'), - ); - $versions_inner .= drupal_render($update_version); + $variables['existing_version'] = $project['existing_version']; + + $versions_inner = array(); + $security_class = array(); + $version_class = array(); + if (isset($project['recommended'])) { + if ($project['status'] != UPDATE_CURRENT || $project['existing_version'] !== $project['recommended']) { + + // First, figure out what to recommend. + // If there's only 1 security update and it has the same version we're + // recommending, give it the same CSS class as if it was recommended, + // but don't print out a separate "Recommended" line for this project. + if (!empty($project['security updates']) + && count($project['security updates']) == 1 + && $project['security updates'][0]['version'] === $project['recommended'] + ) { + $security_class[] = 'version-recommended'; + $security_class[] = 'version-recommended-strong'; } - if ($project['install_type'] == 'dev' - && $project['status'] != UPDATE_CURRENT - && isset($project['dev_version']) - && $project['recommended'] !== $project['dev_version']) { - $update_version = array( + else { + $version_class[] = 'version-recommended'; + // Apply an extra class if we're displaying both a recommended + // version and anything else for an extra visual hint. + if ($project['recommended'] !== $project['latest_version'] + || !empty($project['also']) + || ($project['install_type'] == 'dev' + && isset($project['dev_version']) + && $project['latest_version'] !== $project['dev_version'] + && $project['recommended'] !== $project['dev_version']) + || (isset($project['security updates'][0]) + && $project['recommended'] !== $project['security updates'][0]) + ) { + $version_class[] = 'version-recommended-strong'; + } + $versions_inner[] = array( '#theme' => 'update_version', - '#version' => $project['releases'][$project['dev_version']], - '#tag' => t('Development version:'), - '#class' => array('version-latest'), + '#version' => $project['releases'][$project['recommended']], + '#title' => t('Recommended version:'), + '#attributes' => array('class' => $version_class), ); - $versions_inner .= drupal_render($update_version); } - } - if (isset($project['also'])) { - foreach ($project['also'] as $also) { - $update_version = array( - '#theme' => 'update_version', - '#version' => $project['releases'][$also], - '#tag' => t('Also available:'), - '#class' => array('version-also-available'), - ); - $versions_inner .= drupal_render($update_version); + // Now, print any security updates. + if (!empty($project['security updates'])) { + $security_class[] = 'version-security'; + foreach ($project['security updates'] as $security_update) { + $versions_inner[] = array( + '#theme' => 'update_version', + '#version' => $security_update, + '#title' => t('Security update:'), + '#attributes' => array('class' => $security_class), + ); + } } } - if (!empty($versions_inner)) { - $row .= "
\n" . $versions_inner . "
\n"; + if ($project['recommended'] !== $project['latest_version']) { + $versions_inner[] = array( + '#theme' => 'update_version', + '#version' => $project['releases'][$project['latest_version']], + '#title' => t('Latest version:'), + '#attributes' => array('class' => array('version-latest')), + ); } - $row .= "
\n"; - if (!empty($project['extra'])) { - $row .= '
' . "\n"; - foreach ($project['extra'] as $value) { - $row .= '
'; - $row .= String::checkPlain($value['label']) . ': '; - $row .= drupal_placeholder($value['data']); - $row .= "
\n"; - } - $row .= "
\n"; // extra div. + if ($project['install_type'] == 'dev' + && $project['status'] != UPDATE_CURRENT + && isset($project['dev_version']) + && $project['recommended'] !== $project['dev_version']) { + $versions_inner[] = array( + '#theme' => 'update_version', + '#version' => $project['releases'][$project['dev_version']], + '#title' => t('Development version:'), + '#attributes' => array('class' => array('version-latest')), + ); } + } - $row .= '
'; - sort($project['includes']); - if (!empty($project['disabled'])) { - sort($project['disabled']); - // Make sure we start with a clean slate for each project in the report. - $includes_items = array(); - $row .= t('Includes:'); - $includes_items[] = t('Enabled: %includes', array('%includes' => implode(', ', $project['includes']))); - $includes_items[] = t('Disabled: %disabled', array('%disabled' => implode(', ', $project['disabled']))); - $item_list = array( - '#theme' => 'item_list', - '#items' => $includes_items, + if (isset($project['also'])) { + foreach ($project['also'] as $also) { + $versions_inner[] = array( + '#theme' => 'update_version', + '#version' => $project['releases'][$also], + '#title' => t('Also available:'), + '#attributes' => array('class' => array('version-also-available')), ); - $row .= drupal_render($item_list); - } - else { - $row .= t('Includes: %includes', array('%includes' => implode(', ', $project['includes']))); - } - $row .= "
\n"; - - if (!empty($project['base_themes'])) { - $row .= '
'; - asort($project['base_themes']); - $base_themes = array(); - foreach ($project['base_themes'] as $base_key => $base_theme) { - $update_status_label = array( - '#theme' => 'update_status_label', - '#status' => $status[$base_key], - ); - switch ($status[$base_key]) { - case UPDATE_NOT_SECURE: - case UPDATE_REVOKED: - case UPDATE_NOT_SUPPORTED: - $base_themes[] = t('%base_theme (!base_label)', array('%base_theme' => $base_theme, '!base_label' => drupal_render($update_status_label))); - break; - - default: - $base_themes[] = drupal_placeholder($base_theme); - } - } - $row .= t('Depends on: !basethemes', array('!basethemes' => implode(', ', $base_themes))); - $row .= "
\n"; } + } - if (!empty($project['sub_themes'])) { - $row .= '
'; - sort($project['sub_themes']); - $row .= t('Required by: %subthemes', array('%subthemes' => implode(', ', $project['sub_themes']))); - $row .= "
\n"; - } + if (!empty($versions_inner)) { + $variables['versions'] = $versions_inner; + } - $row .= "
\n"; // info div. + if (!empty($project['disabled'])) { + sort($project['disabled']); + $variables['disabled'] = $project['disabled']; + } - if (!isset($rows[$project['project_type']])) { - $rows[$project['project_type']] = array(); + sort($project['includes']); + $variables['includes'] = $project['includes']; + + $variables['extras'] = array(); + if (!empty($project['extra'])) { + foreach ($project['extra'] as $value) { + $extra_item = array(); + $extra_item['attributes'] = new Attribute(array('class' => $value['class'])); + $extra_item['label'] = $value['label']; + $extra_item['data'] = drupal_placeholder($value['data']); + $variables['extras'][] = $extra_item; } - $row_key = isset($project['title']) ? drupal_strtolower($project['title']) : drupal_strtolower($project['name']); - $rows[$project['project_type']][$row_key] = array( - 'class' => array($class), - 'data' => array(SafeMarkup::set($row)), - ); } - $project_types = array( - 'core' => t('Drupal core'), - 'module' => t('Modules'), - 'theme' => t('Themes'), - 'module-disabled' => t('Disabled modules'), - 'theme-disabled' => t('Disabled themes'), - ); - foreach ($project_types as $type_name => $type_label) { - if (!empty($rows[$type_name])) { - ksort($rows[$type_name]); - $output .= "\n

" . $type_label . "

\n"; - $table = array( - '#type' => 'table', - '#header' => $header, - '#rows' => $rows[$type_name], - '#attributes' => array( - 'class' => array('update'), - ), - ); - $output .= drupal_render($table); + if (!empty($project['base_themes'])) { + asort($project['base_themes']); + $base_themes = array(); + foreach ($project['base_themes'] as $base_key => $base_theme) { + switch ($includes_status[$base_key]) { + case UPDATE_NOT_SECURE: + $base_status_label = t('Security update required!'); + break; + case UPDATE_REVOKED: + $base_status_label = t('Revoked!'); + break; + case UPDATE_NOT_SUPPORTED: + $base_status_label = t('Not supported!'); + break; + default: + $base_status_label = ''; + } + + if ($base_status_label) { + $base_themes[] = t('%base_theme (!base_label)', array( + '%base_theme' => $base_theme, + '!base_label' => $base_status_label, + )); + } + else { + $base_themes[] = drupal_placeholder($base_theme); + } } + $variables['base_themes'] = $base_themes; } - $assets = array( - '#attached' => array( - 'library' => array( - 'update/drupal.update.admin', - ), - ) - ); - drupal_render($assets); - - return SafeMarkup::set($output); -} + if (!empty($project['sub_themes'])) { + sort($project['sub_themes']); + $variables['sub_themes'] = $project['sub_themes']; + } -/** - * Returns HTML for a label to display for a project's update status. - * - * @param array $variables - * An associative array containing: - * - status: The integer code for a project's current update status. - * - * @see update_calculate_project_data() - * @ingroup themeable - */ -function theme_update_status_label($variables) { - switch ($variables['status']) { + // Set the project status details. + $status_attributes = array(); + $status_label = NULL; + switch ($project['status']) { case UPDATE_NOT_SECURE: - return '' . t('Security update required!') . ''; - + $status_attributes['class'][] = 'security-error'; + $status_label = t('Security update required!'); + break; case UPDATE_REVOKED: - return '' . t('Revoked!') . ''; - + $status_attributes['class'][] = 'revoked'; + $status_label = t('Revoked!'); + break; case UPDATE_NOT_SUPPORTED: - return '' . t('Not supported!') . ''; - + $status_attributes['class'][] = 'not-supported'; + $status_label = t('Not supported!'); + break; case UPDATE_NOT_CURRENT: - return '' . t('Update available') . ''; - + $status_attributes['class'][] = 'not-current'; + $status_label = t('Update available'); + break; case UPDATE_CURRENT: - return '' . t('Up to date') . ''; + $status_attributes['class'][] = 'current'; + $status_label = t('Up to date'); + break; + } + $variables['status']['label'] = $status_label; + $variables['status']['attributes'] = new Attribute($status_attributes); + $variables['status']['reason'] = (isset($project['reason'])) ? $project['reason'] : NULL; + switch ($project['status']) { + case UPDATE_CURRENT: + $uri = 'core/misc/icons/73b355/check.png'; + $text = t('Ok'); + break; + case UPDATE_UNKNOWN: + case UPDATE_FETCH_PENDING: + case UPDATE_NOT_FETCHED: + $uri = 'core/misc/icons/e29700/warning.png'; + $text = t('Warning'); + break; + case UPDATE_NOT_SECURE: + case UPDATE_REVOKED: + case UPDATE_NOT_SUPPORTED: + $uri = 'core/misc/icons/ea2800/error.png'; + $text = t('Error'); + break; + case UPDATE_NOT_CHECKED: + case UPDATE_NOT_CURRENT: + default: + $uri = 'core/misc/icons/e29700/warning.png'; + $text = t('Warning'); + break; } + + $variables['status']['icon'] = array( + '#theme' => 'image', + '#width' => 18, + '#height' => 18, + '#uri' => $uri, + '#alt' => $text, + '#title' => $text, + ); } /**