View source
<?php
namespace Drupal\upgrade_status\Form;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Extension\Extension;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactory;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Drupal\upgrade_status\ProjectCollector;
use Drupal\upgrade_status\ScanResultFormatter;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Cookie\SetCookie;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
class UpgradeStatusForm extends FormBase {
protected $projectCollector;
protected $releaseStore;
protected $resultFormatter;
protected $renderer;
public static function create(ContainerInterface $container) {
return new static($container
->get('upgrade_status.project_collector'), $container
->get('keyvalue.expirable'), $container
->get('upgrade_status.result_formatter'), $container
->get('renderer'));
}
public function __construct(ProjectCollector $project_collector, KeyValueExpirableFactory $key_value_expirable, ScanResultFormatter $result_formatter, RendererInterface $renderer) {
$this->projectCollector = $project_collector;
$this->releaseStore = $key_value_expirable
->get('update_available_releases');
$this->resultFormatter = $result_formatter;
$this->renderer = $renderer;
}
public function getFormId() {
return 'drupal_upgrade_status_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$form['#attached']['library'][] = 'upgrade_status/upgrade_status.admin';
$projects = $this->projectCollector
->collectProjects();
$custom = [
'#type' => 'markup',
'#markup' => '<br /><strong>' . $this
->t('No custom projects found.') . '</strong>',
];
if (count($projects['custom'])) {
$custom = $this
->buildProjectList($projects['custom']);
}
$form['custom'] = [
'#type' => 'details',
'#title' => $this
->t('Custom projects'),
'#description' => $this
->t('Custom code is specific to your site, and must be upgraded manually. <a href=":upgrade">Read more about how developers can upgrade their code to Drupal 9</a>.', [
':upgrade' => 'https://www.drupal.org/docs/9/how-drupal-9-is-made-and-what-is-included/how-and-why-we-deprecate-on-the-way-to-drupal-9',
]),
'#open' => TRUE,
'#attributes' => [
'class' => [
'upgrade-status-summary upgrade-status-summary-custom',
],
],
'data' => $custom,
'#tree' => TRUE,
];
$contrib = [
'#type' => 'markup',
'#markup' => '<br /><strong>' . $this
->t('No contributed projects found.') . '</strong>',
];
if (count($projects['contrib'])) {
$contrib = $this
->buildProjectList($projects['contrib'], TRUE);
}
$form['contrib'] = [
'#type' => 'details',
'#title' => $this
->t('Contributed projects'),
'#description' => $this
->t('Contributed code is available from drupal.org. Problems here may be partially resolved by updating to the latest version. <a href=":update">Read more about how to update contributed projects</a>.', [
':update' => 'https://www.drupal.org/docs/8/update/update-modules',
]),
'#open' => TRUE,
'#attributes' => [
'class' => [
'upgrade-status-summary upgrade-status-summary-contrib',
],
],
'data' => $contrib,
'#tree' => TRUE,
];
$form['drupal_upgrade_status_form']['action']['submit'] = [
'#type' => 'submit',
'#value' => $this
->t('Scan selected'),
'#weight' => 2,
'#button_type' => 'primary',
];
$form['drupal_upgrade_status_form']['action']['export'] = [
'#type' => 'submit',
'#value' => $this
->t('Export as HTML'),
'#weight' => 5,
'#submit' => [
[
$this,
'exportReportHTML',
],
],
];
$form['drupal_upgrade_status_form']['action']['export_ascii'] = [
'#type' => 'submit',
'#value' => $this
->t('Export as ASCII'),
'#weight' => 6,
'#submit' => [
[
$this,
'exportReportASCII',
],
],
];
return $form;
}
protected function buildProjectList(array $projects, bool $isContrib = FALSE) {
$counters = [
'not-scanned' => 0,
'no-known-error' => 0,
'known-errors' => 0,
'known-warnings' => 0,
'known-error-projects' => 0,
'known-warning-projects' => 0,
];
$header = [
'project' => [
'data' => $this
->t('Project'),
'class' => 'project-label',
],
];
if ($isContrib) {
$header['update'] = [
'data' => $this
->t('Available update'),
'class' => 'update-info',
];
}
$header['status'] = [
'data' => $this
->t('Status'),
'class' => 'status-info',
];
$build['data'] = [
'#type' => 'tableselect',
'#header' => $header,
'#weight' => 20,
'#options' => [],
];
foreach ($projects as $name => $extension) {
$scan_result = \Drupal::service('keyvalue')
->get('upgrade_status_scan_results')
->get($name);
$info = $extension->info;
$label = $info['name'] . (!empty($info['version']) ? ' ' . $info['version'] : '');
$update_cell = [
'class' => 'update-info',
'data' => $isContrib ? $this
->t('Up to date') : '',
];
$label_cell = [
'data' => [
'label' => [
'#type' => 'html_tag',
'#tag' => 'label',
'#value' => $label,
'#attributes' => [
'for' => 'edit-' . ($isContrib ? 'contrib' : 'custom') . '-data-data-' . str_replace('_', '-', $name),
],
],
],
'class' => 'project-label',
];
if ($isContrib) {
$projectUpdateData = $this->releaseStore
->get($name);
if (!isset($projectUpdateData['releases']) || is_null($projectUpdateData['releases'])) {
$update_cell = [
'class' => 'update-info',
'data' => $this
->t('N/A'),
];
}
else {
$latestRelease = reset($projectUpdateData['releases']);
$latestVersion = $latestRelease['version'];
if ($info['version'] !== $latestVersion) {
$link = $projectUpdateData['link'] . '/releases/' . $latestVersion;
$update_cell = [
'class' => 'update-info',
'data' => [
'#type' => 'link',
'#title' => $latestVersion,
'#url' => Url::fromUri($link),
],
];
}
}
}
if (empty($scan_result)) {
$build['data']['#options'][$name] = [
'#attributes' => [
'class' => [
'not-scanned',
'project-' . $name,
],
],
'project' => $label_cell,
'update' => $update_cell,
'status' => [
'class' => 'status-info',
'data' => $this
->t('Not scanned'),
],
];
$counters['not-scanned']++;
continue;
}
$report = json_decode($scan_result, TRUE);
if (!empty($report['plans'])) {
$label_cell['data']['plans'] = [
'#type' => 'markup',
'#markup' => '<div>' . $report['plans'] . '</div>',
];
}
if (isset($report['data']['totals'])) {
$project_error_count = $report['data']['totals']['file_errors'];
}
else {
$project_error_count = 0;
}
if ($project_error_count === 0) {
$build['data']['#options'][$name] = [
'#attributes' => [
'class' => [
'no-known-error',
'project-' . $name,
],
],
'project' => $label_cell,
'update' => $update_cell,
'status' => [
'class' => 'status-info',
'data' => $this
->t('No known errors'),
],
];
$counters['no-known-error']++;
continue;
}
$error_label = [];
$error_class = 'known-warnings';
if (!empty($report['data']['totals']['upgrade_status_split']['error'])) {
$counters['known-errors'] += $report['data']['totals']['upgrade_status_split']['error'];
$counters['known-error-projects']++;
$error_class = 'known-errors';
$error_label[] = $this
->formatPlural($report['data']['totals']['upgrade_status_split']['error'], '@count error', '@count errors');
}
if (!empty($report['data']['totals']['upgrade_status_split']['warning'])) {
$counters['known-warnings'] += $report['data']['totals']['upgrade_status_split']['warning'];
$counters['known-warning-projects']++;
$error_label[] = $this
->formatPlural($report['data']['totals']['upgrade_status_split']['warning'], '@count warning', '@count warnings');
}
$build['data']['#options'][$name] = [
'#attributes' => [
'class' => [
$error_class,
'project-' . $name,
],
],
'project' => $label_cell,
'update' => $update_cell,
'status' => [
'class' => 'status-info',
'data' => [
'#type' => 'link',
'#title' => join(', ', $error_label),
'#url' => Url::fromRoute('upgrade_status.project', [
'type' => $extension
->getType(),
'project_machine_name' => $name,
]),
'#attributes' => [
'class' => [
'use-ajax',
],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 1024,
'height' => 568,
]),
],
],
],
];
}
if (!$isContrib) {
foreach ($build['data']['#options'] as $name => &$row) {
if (is_array($row)) {
unset($row['update']);
}
}
}
$summary = [];
if ($counters['known-errors'] > 0) {
$summary[] = [
'type' => $this
->formatPlural($counters['known-errors'], '1 error', '@count errors'),
'class' => 'error',
'message' => $this
->formatPlural($counters['known-error-projects'], 'Found in one project.', 'Found in @count projects.'),
];
}
if ($counters['known-warnings'] > 0) {
$summary[] = [
'type' => $this
->formatPlural($counters['known-warnings'], '1 warning', '@count warnings'),
'class' => 'warning',
'message' => $this
->formatPlural($counters['known-warning-projects'], 'Found in one project.', 'Found in @count projects.'),
];
}
if ($counters['no-known-error'] > 0) {
$summary[] = [
'type' => $this
->formatPlural($counters['no-known-error'], '1 checked', '@count checked'),
'class' => 'checked',
'message' => $this
->t('No known errors found.'),
];
}
if ($counters['not-scanned'] > 0) {
$summary[] = [
'type' => $this
->formatPlural($counters['not-scanned'], '1 not scanned', '@count not scanned'),
'class' => 'not-scanned',
'message' => $this
->t('Scan to find errors.'),
];
}
$build['summary'] = [
'#theme' => 'upgrade_status_summary_counter',
'#summary' => $summary,
];
return $build;
}
public function submitForm(array &$form, FormStateInterface $form_state) {
$operations = [];
$projects = $this->projectCollector
->collectProjects();
$submitted = $form_state
->getValues();
foreach ([
'custom',
'contrib',
] as $type) {
if (!empty($submitted[$type])) {
foreach ($submitted[$type]['data']['data'] as $project => $checked) {
if ($checked !== 0) {
$operations[] = [
static::class . '::parseProject',
[
$projects[$type][$project],
],
];
}
}
}
}
if (!empty($operations)) {
$batch = [
'title' => $this
->t('Scanning projects'),
'operations' => $operations,
];
batch_set($batch);
}
}
public function exportReportHTML(array &$form, FormStateInterface $form_state) {
$selected = $form_state
->getValues();
$form_state
->setResponse($this
->exportReport($selected, 'html'));
}
public function exportReportASCII(array &$form, FormStateInterface $form_state) {
$selected = $form_state
->getValues();
$form_state
->setResponse($this
->exportReport($selected, 'ascii'));
}
public function exportReport(array $selected, string $format) {
$extensions = [];
$projects = $this->projectCollector
->collectProjects();
foreach ([
'custom',
'contrib',
] as $type) {
foreach ($selected[$type]['data']['data'] as $project => $checked) {
if ($checked !== 0) {
$extensions[$type][$project] = $format == 'html' ? $this->resultFormatter
->formatResult($projects[$type][$project]) : $this->resultFormatter
->formatAsciiResult($projects[$type][$project]);
}
}
}
$build = [
'#theme' => 'upgrade_status_' . $format . '_export',
'#projects' => $extensions,
];
$fileDate = $this->resultFormatter
->formatDateTime(0, 'html_datetime');
$extension = $format == 'html' ? '.html' : '.txt';
$filename = 'upgrade-status-export-' . $fileDate . $extension;
$response = new Response($this->renderer
->renderRoot($build));
$response->headers
->set('Content-Disposition', 'attachment; filename="' . $filename . '"');
return $response;
}
public static function parseProject(Extension $extension, &$context) {
$context['message'] = t('Completed @project.', [
'@project' => $extension
->getName(),
]);
$error = $file_name = FALSE;
$url = Url::fromRoute('upgrade_status.analyse', [
'type' => $extension
->getType(),
'project_machine_name' => $extension
->getName(),
]);
$session_config = \Drupal::service('session_configuration');
$request = \Drupal::request();
$session_options = $session_config
->getOptions($request);
$cookie_domain = empty($session_options['cookie_domain']) ? '.' . $request
->getHost() : $session_options['cookie_domain'];
$cookie_jar = new CookieJar();
$cookie = new SetCookie([
'Name' => $session_options['name'],
'Value' => $request->cookies
->get($session_options['name']),
'Domain' => $cookie_domain,
'Secure' => $session_options['cookie_secure'],
]);
$cookie_jar
->setCookie($cookie);
$options = [
'cookies' => $cookie_jar,
'timeout' => 0,
];
try {
$response = \Drupal::httpClient()
->post($url
->setAbsolute()
->toString(), $options);
$data = json_decode((string) $response
->getBody(), TRUE);
if (!$data) {
$error = (string) $response
->getBody();
$file_name = 'PHP Fatal Error';
}
} catch (\Exception $e) {
$error = $e
->getMessage();
$file_name = 'Scanning exception';
}
if ($error !== FALSE) {
$key_value = \Drupal::service('keyvalue')
->get('upgrade_status_scan_results');
$result = [];
$result['date'] = REQUEST_TIME;
$result['data'] = [
'totals' => [
'errors' => 1,
'file_errors' => 1,
'upgrade_status_split' => [
'warning' => 1,
],
],
'files' => [],
];
$result['data']['files'][$file_name] = [
'errors' => 1,
'messages' => [
[
'message' => $error,
'line' => 0,
],
],
];
$key_value
->set($extension
->getName(), json_encode($result));
}
}
}