patchinfo.module in PatchInfo 8
Same filename and directory in other branches
Patch Info primary module file.
File
patchinfo.moduleView source
<?php
/**
* @file
* Patch Info primary module file.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Extension\Extension;
use Drupal\Component\Utility\Html;
use Drupal\Core\Link;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
/**
* Implements hook_system_info_alter().
*/
function patchinfo_system_info_alter(array &$info, Extension $file, $type) {
// Get patch information from .info.yml files and save it to the database.
if (in_array($type, [
'module',
'theme',
])) {
_patchinfo_clear_db($file
->getName());
if (isset($info['patches']) && is_array($info['patches']) && count($info['patches']) > 0) {
_patchinfo_process_module($file
->getName(), $info['patches']);
}
}
}
/**
* Implements hook_update_projects_alter().
*/
function patchinfo_update_projects_alter(&$projects) {
// Get modules to be excluded from update check from configuration.
$excluded_projects = (array) \Drupal::config('patchinfo.settings')
->get('exclude from update check');
$projects = array_diff_key($projects, array_flip($excluded_projects));
}
/**
* Implements hook_form_FORM_ID_alter() for update_manager_update_form().
*/
function patchinfo_form_update_manager_update_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Add our CSS.
$form['#attached']['library'][] = 'patchinfo/patchinfo';
// Add a highly visible notice for patched modules.
$patch_info = _patchinfo_get_info();
if (count($patch_info) > 0) {
// Get project information from update manager service.
$projects = \Drupal::service('update.manager')
->getProjects();
// Check each project and its submodules for patches.
foreach (Element::children($form['project_downloads']) as $module) {
$patches = _patchinfo_get_patches($patch_info, $projects[$module]);
if (count($patches) > 0) {
// Determine, if the module is enabled or disabled and add the patch
// list to the appropriate section of the form.
$key = '';
if (isset($form['projects']['#options'][$module]['title'])) {
$key = 'projects';
}
elseif (isset($form['disabled_projects']['#options'][$module]['title'])) {
$key = 'disabled_projects';
}
if (!empty($key)) {
$patch_list = [
'#theme' => 'patchinfo_patches',
'#patches' => $patches,
];
$form[$key]['#options'][$module]['title']['data']['patches'] = $patch_list;
}
}
}
// If a manual update is available, check, if Drupal core has any
// patches. If so, show a warning above the update form.
if (isset($form['manual_updates'])) {
$patches_drupal = _patchinfo_get_patches($patch_info, $projects['drupal']);
if (count($patches_drupal) > 0) {
$patch_list = [
'#theme' => 'patchinfo_patches',
'#patches' => $patches_drupal,
];
$form['manual_updates']['#rows']['drupal']['data']['title']['data']['patches'] = $patch_list;
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter() for update_settings().
*/
function patchinfo_form_update_settings_alter(&$form, FormStateInterface $form_state, $form_id) {
$config = \Drupal::config('patchinfo.settings');
$form['patchinfo_exclude_from_update_check'] = [
'#type' => 'textarea',
'#title' => t('Exclude modules from update check'),
'#description' => t('Modules, which should be excluded from the update check, can be listed here. Each entry should use the machine readable name of the module and go on a separate line.'),
'#default_value' => implode("\n", (array) $config
->get('exclude from update check')),
];
$form['#submit'][] = 'patchinfo_update_settings_form_submit';
}
/**
* Submission handler for extended update settings form.
*
* @param array $form
* Drupal Form API form information.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Drupal Form API form state information.
*/
function patchinfo_update_settings_form_submit(array $form, FormStateInterface &$form_state) {
// Get value of exclude from update check setting from form state and prepare
// it for storage.
$exclude_modules = $form_state
->getValue('patchinfo_exclude_from_update_check');
$exclude_modules = array_map('trim', explode("\n", $exclude_modules));
$exclude_modules = array_filter($exclude_modules);
// Store exclude from update check setting in configuration.
$config = \Drupal::service('config.factory')
->getEditable('patchinfo.settings')
->set('exclude from update check', $exclude_modules)
->save();
}
/**
* Remove all patch information for a module from DB.
*
* @param string $module
* Machine readable module name.
*/
function _patchinfo_clear_db($module) {
if (!empty($module)) {
db_delete('patchinfo')
->condition('module', $module)
->execute();
}
}
/**
* Get all patches for a module (including its submodules).
*
* @param array $patch_info
* Patch information as returned by _patchinfo_get_info().
* @param array $project_info
* Project information for a single project, i.e. a single value from
* the array returned by \Drupal::service('update.manager')->getProjects().
*
* @return array
* Array containing all patch information for a module and its
* submodules.
*/
function _patchinfo_get_patches(array $patch_info, array $project_info) {
$return = [];
// For each module in this project (including submodules) check, if
// there are patches and if so, merge them into our array containing
// all patch information for this project.
foreach ($project_info['includes'] as $module_key => $module_name) {
if (isset($patch_info[$module_key])) {
$return = array_merge($return, $patch_info[$module_key]);
}
}
return $return;
}
/**
* Get patch information from DB.
*
* @param bool $raw
* If TRUE, uses an array containing url and info keys for each
* patch. If FALSE (default), either use info text only or, if
* a URL is available, a suitable link for each patch.
*
* @return array
* Array of patches in DB keyed by machine readable module name.
*/
function _patchinfo_get_info($raw = FALSE) {
$patch_info = [];
$result = db_select('patchinfo', 'pi')
->fields('pi', [
'module',
'id',
'url',
'info',
])
->execute();
foreach ($result as $row) {
if (!isset($patch_info[$row->module])) {
$patch_info[$row->module] = [];
}
if ($raw) {
$patch_info[$row->module][$row->id]['url'] = $row->url;
$patch_info[$row->module][$row->id]['info'] = $row->info;
}
else {
if (!empty($row->url)) {
$url = Url::fromUri($row->url);
$patch_info[$row->module][$row->id] = Link::fromTextAndUrl($row->info, $url);
}
else {
$patch_info[$row->module][$row->id] = Html::escape($row->info);
}
}
}
return $patch_info;
}
/**
* Process patch information for a module.
*
* @param string $module
* Machine readable module name.
* @param array $patch_info
* Patch info from module info.yml file.
*/
function _patchinfo_process_module($module, array $patch_info) {
if (!empty($module)) {
foreach ($patch_info as $key => $info) {
// Calculate an index for each patch, which is not 0.
$index = $key + 1;
// If a colon is used in the patch description and the user didn't enclose
// the patch entry in single quotes as shown in the README file, the entry
// is interpreted by the YAML parser as an array. To prevent possible
// database exceptions, we replace the actual information with a warning
// message instructing the user to check his info.yml file syntax.
if (is_array($info)) {
\Drupal::logger('patchinfo')
->warning(t('Malformed patch entry detected in @module.info.yml at index @key. Check the syntax or your info.yml file! In most cases, this may be fixed by enclosing the patch entry in single or double quotes.', [
'@module' => $module,
'@key' => $key,
]));
$info = t('Malformed patch entry detected. Check the syntax of your info.yml file! In most cases, this may be fixed by enclosing the patch entry in single or double quotes.');
}
// Extract URL from patch information, if any.
$info = explode(' ', $info);
$url = '';
if (filter_var($info[0], FILTER_VALIDATE_URL) !== FALSE) {
$url = $info[0];
unset($info[0]);
}
$info = implode(' ', $info);
// Write patch information to db.
db_merge('patchinfo')
->key([
'module' => $module,
'id' => $index,
])
->fields([
'url' => $url,
'info' => $info,
])
->execute();
}
}
}
/**
* Implements hook_theme().
*/
function patchinfo_theme($existing, $type, $theme, $path) {
return [
'patchinfo_excluded_modules' => [
'variables' => [
'excluded_modules' => [],
],
],
'patchinfo_patches' => [
'variables' => [
'patches' => [],
],
],
];
}
/**
* Implements hook_theme_registry_alter().
*/
function patchinfo_theme_registry_alter(&$theme_registry) {
$module_path = drupal_get_path('module', 'patchinfo');
if (isset($theme_registry['update_report'])) {
// Replace template location for update report with our own templates
// folder, so that we can alter the template.
$theme_registry['update_report']['path'] = $module_path . '/templates';
}
if (isset($theme_registry['update_project_status'])) {
// Update module will sort parts of the project information using sort().
// Unfortunately, this results in the keys being lost. Since we need these
// keys to check for patch information, we will move our preprocess function
// in front of the preprocess function provided by upate module.
$position = array_search('patchinfo_preprocess_update_project_status', $theme_registry['update_project_status']['preprocess functions']);
if ($position) {
$out = array_splice($theme_registry['update_project_status']['preprocess functions'], $position, 1);
array_splice($theme_registry['update_project_status']['preprocess functions'], 1, 0, $out);
}
// Replace template location for update project status with our own
// templates folder, so that we can alter the template.
$theme_registry['update_project_status']['path'] = $module_path . '/templates';
}
}
/**
* Implements hook_preprocess_HOOK() for update-report.html.twig.
*/
function patchinfo_preprocess_update_report(&$variables) {
$config = \Drupal::config('patchinfo.settings');
// Provide excluded modules variable.
$variables['excluded_modules'] = [
'#theme' => 'patchinfo_excluded_modules',
'#excluded_modules' => (array) $config
->get('exclude from update check'),
'#attached' => [
'library' => [
'patchinfo/patchinfo',
],
],
];
}
/**
* Implements hook_preprocess_HOOK() for update-project-status.html.twig.
*/
function patchinfo_preprocess_update_project_status(&$variables) {
$patch_info = _patchinfo_get_info();
$patches = _patchinfo_get_patches($patch_info, $variables['project']);
// Provide patches variable.
if (count($patches) > 0) {
$variables['patches'] = [
'#theme' => 'patchinfo_patches',
'#patches' => $patches,
];
}
}
/**
* Prepares variables for patchinfo excluded modules templates.
*
* Default template: patchinfo-excluded-modules.html.twig.
*
* @param array $variables
* An associative array containing:
* - excluded_modules: List of modules excluded from update check.
*/
function template_preprocess_patchinfo_excluded_modules(array &$variables) {
if (count($variables['excluded_modules']) > 0) {
// Prepare excluded modules for use in template.
$variables['excluded_modules'] = [
'#theme' => 'item_list',
'#items' => $variables['excluded_modules'],
];
// Provide URL to settings page of update module.
$variables['settings_url'] = Url::fromRoute('update.settings', [], [
'query' => \Drupal::destination()
->getAsArray(),
]);
}
}
/**
* Prepares variables for a patch listing of a module and its submodules.
*
* Default template: patchinfo-patches.html.twig.
*
* @param array $variables
* An associative array containing:
* - patches: List of patch information for a module and its submodules.
*/
function template_preprocess_patchinfo_patches(array &$variables) {
// Prepare patch list for use in template.
$variables['patches'] = [
'#theme' => 'item_list',
'#items' => $variables['patches'],
'#attached' => [
'library' => [
'patchinfo/patchinfo',
],
],
];
}