You are here

class UpgradeStatusForm in Upgrade Status 8.3

Same name and namespace in other branches
  1. 8 src/Form/UpgradeStatusForm.php \Drupal\upgrade_status\Form\UpgradeStatusForm
  2. 8.2 src/Form/UpgradeStatusForm.php \Drupal\upgrade_status\Form\UpgradeStatusForm

Hierarchy

Expanded class hierarchy of UpgradeStatusForm

1 string reference to 'UpgradeStatusForm'
upgrade_status.routing.yml in ./upgrade_status.routing.yml
upgrade_status.routing.yml

File

src/Form/UpgradeStatusForm.php, line 30

Namespace

Drupal\upgrade_status\Form
View source
class UpgradeStatusForm extends FormBase {

  /**
   * The project collector service.
   *
   * @var \Drupal\upgrade_status\ProjectCollector
   */
  protected $projectCollector;

  /**
   * Available releases store.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface|mixed
   */
  protected $releaseStore;

  /**
   * The scan result formatter service.
   *
   * @var \Drupal\upgrade_status\ScanResultFormatter
   */
  protected $resultFormatter;

  /**
   * The renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * The logger service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * The module handler service.
   *
   * @var \Drupal\Core\Extension\ModuleHandler
   */
  protected $moduleHandler;

  /**
   * The deprecation analyzer.
   *
   * @var \Drupal\upgrade_status\DeprecationAnalyzer
   */
  protected $deprecationAnalyzer;

  /**
   * The state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * The date formatter.
   *
   * @var \Drupal\Core\Datetime\DateFormatter
   */
  protected $dateFormatter;

  /**
   * The destination service.
   *
   * @var \Drupal\Core\Routing\RedirectDestination
   */
  protected $destination;

  /**
   * The next Drupal core major version.
   *
   * @var int
   */
  protected $nextMajor;

  /**
   * Database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * Drupal kernel.
   *
   * @var \Drupal\Core\DrupalKernelInterface
   */
  protected $kernel;

  /**
   * {@inheritdoc}
   */
  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'), $container
      ->get('logger.channel.upgrade_status'), $container
      ->get('module_handler'), $container
      ->get('upgrade_status.deprecation_analyzer'), $container
      ->get('state'), $container
      ->get('date.formatter'), $container
      ->get('redirect.destination'), $container
      ->get('database'), $container
      ->get('kernel'));
  }

  /**
   * Constructs a Drupal\upgrade_status\Form\UpgradeStatusForm.
   *
   * @param \Drupal\upgrade_status\ProjectCollector $project_collector
   *   The project collector service.
   * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactory $key_value_expirable
   *   The expirable key/value storage.
   * @param \Drupal\upgrade_status\ScanResultFormatter $result_formatter
   *   The scan result formatter service.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer service.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.
   * @param \Drupal\Core\Extension\ModuleHandler $module_handler
   *   The module handler.
   * @param \Drupal\upgrade_status\DeprecationAnalyzer $deprecation_analyzer
   *   The deprecation analyzer.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
   *   The date formatter.
   * @param \Drupal\Core\Routing\RedirectDestination $destination
   *   The destination service.
   * @param \Drupal\Core\Database\Connection $connection
   *   The database connection.
   * @param \Drupal\Core\DrupalKernelInterface $kernel
   *   The Drupal kernel.
   */
  public function __construct(ProjectCollector $project_collector, KeyValueExpirableFactory $key_value_expirable, ScanResultFormatter $result_formatter, RendererInterface $renderer, LoggerInterface $logger, ModuleHandler $module_handler, DeprecationAnalyzer $deprecation_analyzer, StateInterface $state, DateFormatter $date_formatter, RedirectDestination $destination, Connection $database, DrupalKernelInterface $kernel) {
    $this->projectCollector = $project_collector;
    $this->releaseStore = $key_value_expirable
      ->get('update_available_releases');
    $this->resultFormatter = $result_formatter;
    $this->renderer = $renderer;
    $this->logger = $logger;
    $this->moduleHandler = $module_handler;
    $this->deprecationAnalyzer = $deprecation_analyzer;
    $this->state = $state;
    $this->dateFormatter = $date_formatter;
    $this->destination = $destination;
    $this->nextMajor = ProjectCollector::getDrupalCoreMajorVersion() + 1;
    $this->database = $database;
    $this->kernel = $kernel;
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'drupal_upgrade_status_summary_form';
  }

  /**
   * Form constructor.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The form structure.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['#attached']['library'][] = 'upgrade_status/upgrade_status.admin';
    $analyzer_ready = TRUE;
    try {
      $this->deprecationAnalyzer
        ->initEnvironment();
    } catch (\Exception $e) {
      $analyzer_ready = FALSE;
      $this
        ->messenger()
        ->addError($e
        ->getMessage());
    }
    $environment = $this
      ->buildEnvironmentChecks();
    $form['summary'] = $this
      ->buildResultSummary($environment['status']);
    $environment_description = $environment['description'];
    unset($environment['status']);
    unset($environment['description']);
    $form['environment'] = [
      '#type' => 'details',
      '#title' => $this
        ->t('Drupal core and hosting environment'),
      '#description' => $environment_description,
      '#open' => TRUE,
      '#attributes' => [
        'class' => [
          'upgrade-status-summary-environment',
        ],
      ],
      'data' => $environment,
      '#tree' => TRUE,
    ];

    // Gather project list with metadata.
    $projects = $this->projectCollector
      ->collectProjects();
    $next_steps = $this->projectCollector
      ->getNextStepInfo();
    foreach ($next_steps as $next_step => $step_label) {
      $sublist = [];
      foreach ($projects as $name => $project) {
        if ($project->info['upgrade_status_next'] == $next_step) {
          $sublist[$name] = $project;
        }
      }
      if (!empty($sublist)) {
        $form[$next_step] = [
          '#type' => 'details',
          '#title' => $step_label[0],
          '#description' => $step_label[1],
          '#open' => TRUE,
          '#attributes' => [
            'class' => [
              'upgrade-status-summary',
              'upgrade-status-next-step-' . $next_step,
            ],
          ],
          'data' => $this
            ->buildProjectList($sublist, $next_step),
          '#tree' => TRUE,
        ];
      }
    }
    $form['drupal_upgrade_status_form']['action']['submit'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Scan selected'),
      '#weight' => 2,
      '#button_type' => 'primary',
      '#disabled' => !$analyzer_ready,
    ];
    $form['drupal_upgrade_status_form']['action']['export'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Export selected as HTML'),
      '#weight' => 5,
      '#submit' => [
        [
          $this,
          'exportReport',
        ],
      ],
      '#disabled' => !$analyzer_ready,
    ];
    $form['drupal_upgrade_status_form']['action']['export_ascii'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Export selected as text'),
      '#weight' => 6,
      '#submit' => [
        [
          $this,
          'exportReportASCII',
        ],
      ],
      '#disabled' => !$analyzer_ready,
    ];
    return $form;
  }

  /**
   * Builds a list and status summary of projects.
   *
   * @param \Drupal\Core\Extension\Extension[] $projects
   *   Array of extensions representing projects.
   * @param string $next_step
   *   The machine name of the suggested next step to take for these projects.
   *
   * @return array
   *   Build array.
   */
  protected function buildProjectList(array $projects, string $next_step) {
    $header = [
      'project' => [
        'data' => $this
          ->t('Project'),
        'class' => 'project-label',
      ],
      'type' => [
        'data' => $this
          ->t('Type'),
        'class' => 'type-label',
      ],
      'status' => [
        'data' => $this
          ->t('Status'),
        'class' => 'status-label',
      ],
      'version' => [
        'data' => $this
          ->t('Local version'),
        'class' => 'version-label',
      ],
      'ready' => [
        'data' => $this
          ->t('Local ' . $this->nextMajor . '-ready'),
        'class' => 'ready-label',
      ],
      'result' => [
        'data' => $this
          ->t('Local scan result'),
        'class' => 'scan-info',
      ],
      'updatev' => [
        'data' => $this
          ->t('Drupal.org version'),
        'class' => 'updatev-info',
      ],
      'update9' => [
        'data' => $this
          ->t('Drupal.org ' . $this->nextMajor . '-ready'),
        'class' => 'update9-info',
      ],
      'issues' => [
        'data' => $this
          ->t('Drupal.org issues'),
        'class' => 'issue-info',
      ],
      'plan' => [
        'data' => $this
          ->t('Plan'),
        'class' => 'plan-info',
      ],
    ];
    $build['list'] = [
      '#type' => 'tableselect',
      '#header' => $header,
      '#weight' => 20,
      '#options' => [],
    ];
    foreach ($projects as $name => $extension) {
      $option = [
        '#attributes' => [
          'class' => 'project-' . $name,
        ],
      ];
      $option['project'] = [
        'data' => [
          'label' => [
            '#type' => 'html_tag',
            '#tag' => 'label',
            '#value' => $extension->info['name'],
            '#attributes' => [
              'for' => 'edit-' . $next_step . '-data-list-' . str_replace('_', '-', $name),
            ],
          ],
        ],
        'class' => 'project-label',
      ];
      $option['type'] = [
        'data' => [
          'label' => [
            '#type' => 'markup',
            '#markup' => $extension->info['upgrade_status_type'] == ProjectCollector::TYPE_CUSTOM ? $this
              ->t('Custom') : $this
              ->t('Contributed'),
          ],
        ],
      ];
      $option['status'] = [
        'data' => [
          'label' => [
            '#type' => 'markup',
            '#markup' => empty($extension->status) ? $this
              ->t('Uninstalled') : $this
              ->t('Installed'),
          ],
        ],
      ];

      // Start of local version/readiness columns.
      $option['version'] = [
        'data' => [
          'label' => [
            '#type' => 'markup',
            '#markup' => !empty($extension->info['version']) ? $extension->info['version'] : $this
              ->t('N/A'),
          ],
        ],
      ];
      $option['ready'] = [
        'class' => 'status-info ' . (!empty($extension->info['upgrade_status_next_major_compatible']) ? 'status-info-compatible' : 'status-info-incompatible'),
        'data' => [
          'label' => [
            '#type' => 'markup',
            '#markup' => !empty($extension->info['upgrade_status_next_major_compatible']) ? $this
              ->t('Compatible') : $this
              ->t('Incompatible'),
          ],
        ],
      ];
      $report = $this->projectCollector
        ->getResults($name);
      $result_summary = !empty($report) ? $this
        ->t('No problems found') : $this
        ->t('N/A');
      if (!empty($report['data']['totals']['file_errors'])) {
        $result_summary = $this
          ->formatPlural($report['data']['totals']['file_errors'], '@count problem', '@count problems');
        $option['result'] = [
          'data' => [
            '#type' => 'link',
            '#title' => $result_summary,
            '#url' => Url::fromRoute('upgrade_status.project', [
              'project_machine_name' => $name,
            ]),
            '#attributes' => [
              'class' => [
                'use-ajax',
              ],
              'data-dialog-type' => 'modal',
              'data-dialog-options' => Json::encode([
                'width' => 1024,
                'height' => 568,
              ]),
            ],
          ],
          'class' => 'scan-result',
        ];
      }
      else {
        $option['result'] = [
          'data' => [
            'label' => [
              '#type' => 'markup',
              '#markup' => $result_summary,
            ],
          ],
          'class' => 'scan-result',
        ];
      }

      // Start of drupal.org data columns.
      $updatev = $this
        ->t('Not applicable');
      if (!empty($extension->info['upgrade_status_update_link'])) {
        $option['updatev'] = [
          'data' => [
            'link' => [
              '#type' => 'link',
              '#title' => $extension->info['upgrade_status_update_version'],
              '#url' => Url::fromUri($extension->info['upgrade_status_update_link']),
            ],
          ],
        ];
        unset($updatev);
      }
      elseif (!empty($extension->info['upgrade_status_update'])) {
        $updatev = $this
          ->t('Unavailable');
        if ($extension->info['upgrade_status_update'] == ProjectCollector::UPDATE_NOT_CHECKED) {
          $updatev = $this
            ->t('Unchecked');
        }
        elseif ($extension->info['upgrade_status_update'] == ProjectCollector::UPDATE_ALREADY_INSTALLED) {
          $updatev = $this
            ->t('Up to date');
        }
      }
      if (!empty($updatev)) {
        $option['updatev'] = [
          'data' => [
            'label' => [
              '#type' => 'markup',
              '#markup' => $updatev,
            ],
          ],
        ];
      }
      $update_class = 'status-info-na';
      $update_info = $this
        ->t('Not applicable');
      if (isset($extension->info['upgrade_status_update'])) {
        switch ($extension->info['upgrade_status_update']) {
          case ProjectCollector::UPDATE_NOT_AVAILABLE:
            $update_info = $this
              ->t('Unavailable');
            $update_class = 'status-info-na';
            break;
          case ProjectCollector::UPDATE_NOT_CHECKED:
            $update_info = $this
              ->t('Unchecked');
            $update_class = 'status-info-unchecked';
            break;
          case ProjectCollector::UPDATE_AVAILABLE:
          case ProjectCollector::UPDATE_ALREADY_INSTALLED:
            if ($extension->info['upgrade_status_update_compatible']) {
              $update_info = $this
                ->t('Compatible');
              $update_class = 'status-info-compatible';
            }
            else {
              $update_info = $this
                ->t('Incompatible');
              $update_class = 'status-info-incompatible';
            }
            break;
        }
      }
      $option['update9'] = [
        'class' => 'status-info ' . $update_class,
        'data' => [
          'label' => [
            '#type' => 'markup',
            '#markup' => $update_info,
          ],
        ],
      ];
      if ($extension->info['upgrade_status_type'] == ProjectCollector::TYPE_CUSTOM) {
        $option['issues'] = $option['plan'] = [
          'data' => [
            'label' => [
              '#type' => 'markup',
              '#markup' => $this
                ->t('Not applicable'),
            ],
          ],
        ];
      }
      else {
        $plan = (string) $this->projectCollector
          ->getPlan($name);
        $option['issues'] = [
          'data' => [
            'label' => [
              '#type' => 'markup',
              // Use the project name from the info array instead of $key.
              // $key is the local name, not necessarily the project name.
              '#markup' => '<a href="https://drupal.org/project/issues/' . $extension->info['project'] . '?text=Drupal+' . $this->nextMajor . '&status=All">' . $this
                ->t('Issues', [], [
                'context' => 'Drupal.org issues',
              ]) . '</a>',
            ],
          ],
        ];
        $plan = (string) $this->projectCollector
          ->getPlan($name);
        $option['plan'] = [
          'data' => [
            'label' => [
              '#type' => 'markup',
              '#markup' => !empty($plan) ? $plan : $this
                ->t('N/A'),
            ],
          ],
        ];
      }
      $build['list']['#options'][$name] = $option;
    }
    return $build;
  }

  /**
   * Build a result summary table for quick overview display to users.
   *
   * @param bool|null $environment_status
   *   The status of the environment. Whether to put it into the Fix or Relax
   *   columns or omit it.
   *
   * @return array
   *   Render array.
   */
  protected function buildResultSummary($environment_status = TRUE) {
    $projects = $this->projectCollector
      ->collectProjects();
    $next_steps = $this->projectCollector
      ->getNextStepInfo();
    $last = $this->state
      ->get('update.last_check') ?: 0;
    if ($last == 0) {
      $last_checked = $this
        ->t('Never checked');
    }
    else {
      $time = $this->dateFormatter
        ->formatTimeDiffSince($last);
      $last_checked = $this
        ->t('Last checked @time ago', [
        '@time' => $time,
      ]);
    }
    $update_time = [
      [
        '#type' => 'link',
        '#title' => $this
          ->t('Check available updates'),
        '#url' => Url::fromRoute('update.manual_status', [], [
          'query' => $this->destination
            ->getAsArray(),
        ]),
      ],
      [
        '#type' => 'markup',
        '#markup' => ' (' . $last_checked . ')',
      ],
    ];
    $header = [
      ProjectCollector::SUMMARY_ANALYZE => [
        'data' => $this
          ->t('Gather data'),
        'class' => 'summary-' . ProjectCollector::SUMMARY_ANALYZE,
      ],
      ProjectCollector::SUMMARY_ACT => [
        'data' => $this
          ->t('Fix incompatibilities'),
        'class' => 'status-' . ProjectCollector::SUMMARY_ACT,
      ],
      ProjectCollector::SUMMARY_RELAX => [
        'data' => $this
          ->t('Relax'),
        'class' => 'status-' . ProjectCollector::SUMMARY_RELAX,
      ],
    ];
    $build = [
      '#type' => 'table',
      '#attributes' => [
        'class' => [
          'upgrade-status-overview',
        ],
      ],
      '#header' => $header,
      '#rows' => [
        [
          'data' => [
            ProjectCollector::SUMMARY_ANALYZE => [
              'data' => [],
            ],
            ProjectCollector::SUMMARY_ACT => [
              'data' => [],
            ],
            ProjectCollector::SUMMARY_RELAX => [
              'data' => [],
            ],
          ],
        ],
      ],
    ];
    foreach ($header as $key => $value) {
      $cell_data = $cell_items = [];
      foreach ($next_steps as $next_step => $step_label) {

        // If this next step summary belongs in this table cell, collect it.
        if ($step_label[2] == $key) {
          foreach ($projects as $name => $project) {
            if ($project->info['upgrade_status_next'] == $next_step) {
              @$cell_data[$next_step]++;
            }
          }
        }
      }
      if ($key == ProjectCollector::SUMMARY_ANALYZE) {

        // If neither Composer Deploy nor Git Deploy are available and installed, suggest installing one.
        if (empty($projects['git_deploy']->status) && empty($projects['composer_deploy']->status)) {
          $cell_items[] = [
            '#markup' => $this
              ->t('Install <a href=":composer_deploy">Composer Deploy</a> or <a href=":git_deploy">Git Deploy</a> as appropriate for accurate update recommendations', [
              ':composer_deploy' => 'https://drupal.org/project/composer_deploy',
              ':git_deploy' => 'https://drupal.org/project/git_deploy',
            ]),
          ];
        }

        // Add available update info.
        $cell_items[] = $update_time;
      }
      if ($key == ProjectCollector::SUMMARY_ACT && !is_null($environment_status) && !$environment_status) {
        $cell_items[] = [
          '#markup' => '<a href="#edit-environment" class="upgrade-status-summary-label">' . $this
            ->t('Environment is incompatible') . '</a>',
        ];
      }
      if (count($cell_data)) {
        foreach ($cell_data as $next_step => $count) {
          $cell_items[] = [
            '#markup' => '<a href="#edit-' . $next_step . '" class="upgrade-status-summary-label upgrade-status-summary-label-' . $next_step . '">' . $this
              ->formatPlural($count, '@type: 1 project', '@type: @count projects', [
              '@type' => $next_steps[$next_step][0],
            ]) . '</a>',
          ];
        }
      }
      if ($key == ProjectCollector::SUMMARY_ANALYZE) {
        $cell_items[] = [
          '#markup' => 'Select any of the projects to rescan as needed below',
        ];
      }
      if ($key == ProjectCollector::SUMMARY_RELAX) {

        // Calculate how done is this site assuming the environment as
        // "one project" for simplicity.
        $done_count = (!empty($cell_data[ProjectCollector::NEXT_RELAX]) ? $cell_data[ProjectCollector::NEXT_RELAX] : 0) + (int) $environment_status;
        $percent = round($done_count / (count($projects) + 1) * 100);
        $build['#rows'][0]['data'][$key]['data'][] = [
          '#type' => 'markup',
          '#allowed_tags' => [
            'svg',
            'path',
            'text',
          ],
          '#markup' => <<<MARKUP
        <div class="upgrade-status-result-chart">
        <svg viewBox="0 0 36 36" class="upgrade-status-result-circle">
          <path class="circle-bg"
            d="M18 2.0845
              a 15.9155 15.9155 0 0 1 0 31.831
              a 15.9155 15.9155 0 0 1 0 -31.831"
          />
          <path class="circle"
            stroke-dasharray="{<span class="php-variable">$percent</span>}, 100"
            d="M18 2.0845
              a 15.9155 15.9155 0 0 1 0 31.831
              a 15.9155 15.9155 0 0 1 0 -31.831"
          />
          <text x="18" y="20.35" class="percentage">{<span class="php-variable">$percent</span>}%</text>
        </svg>
      </div>
MARKUP
,
        ];
        if (!empty($environment_status)) {
          $cell_items[] = [
            '#markup' => '<a href="#edit-environment" class="upgrade-status-summary-label">' . $this
              ->t('Environment checks passed') . '</a>',
          ];
        }
      }
      if (count($cell_items)) {
        $build['#rows'][0]['data'][$key]['data'][] = [
          '#theme' => 'item_list',
          '#items' => $cell_items,
        ];
      }
      else {
        $build['#rows'][0]['data'][$key]['data'][] = [
          '#type' => 'markup',
          '#markup' => $this
            ->t('N/A'),
        ];
      }
    }
    return $build;
  }

  /**
   * Builds a list of environment checks.
   *
   * @return array
   *   Build array. The overall environment status (TRUE, FALSE or NULL) is
   *   indicated in the 'status' key, while a 'description' key explains the
   *   environment requirements on a high level.
   */
  protected function buildEnvironmentChecks() {
    $status = TRUE;
    $header = [
      'requirement' => [
        'data' => $this
          ->t('Requirement'),
        'class' => 'requirement-label',
      ],
      'status' => [
        'data' => $this
          ->t('Status'),
        'class' => 'status-info',
      ],
    ];
    $build['data'] = [
      '#type' => 'table',
      '#header' => $header,
      '#rows' => [],
    ];
    if ($this->nextMajor == 10) {

      // @todo update this as the situation develops.
      $build['description'] = $this
        ->t('<a href=":environment">Drupal 10 environment requirements are still evolving</a>. Upgrades to Drupal 10 are planned to be supported from Drupal 9.3.x and Drupal 9.4.x.', [
        ':environment' => 'https://www.drupal.org/project/drupal/issues/3118147',
      ]);

      // Check PHP version.
      $version = PHP_VERSION;
      if (version_compare($version, '8.0.0') >= 0) {
        $class = 'no-known-error';
      }
      else {
        $class = 'known-error';
        $status = FALSE;
      }
      $build['data']['#rows'][] = [
        'class' => [
          $class,
        ],
        'data' => [
          'requirement' => [
            'class' => 'requirement-label',
            'data' => $this
              ->t('PHP version should be at least 8.0.0. Before updating to PHP 8, use <code>$ composer why-not php:8</code> to check if any projects need updating for compatibility. Also check custom projects manually.'),
          ],
          'status' => [
            'data' => $this
              ->t('Version @version', [
              '@version' => $version,
            ]),
            'class' => 'status-info',
          ],
        ],
      ];

      // Check JSON support in database.
      $class = 'no-known-error';
      $requirement = $this
        ->t('Supported.');
      try {
        $this->database
          ->query('SELECT JSON_TYPE(\'1\')');
      } catch (\Exception $e) {
        $class = 'known-error';
        $status = FALSE;
        $requirement = $this
          ->t('Not supported.');
      }
      $build['data']['#rows'][] = [
        'class' => [
          $class,
        ],
        'data' => [
          'requirement' => [
            'class' => 'requirement-label',
            'data' => $this
              ->t('Database JSON support required'),
          ],
          'status' => [
            'data' => $requirement,
            'class' => 'status-info',
          ],
        ],
      ];

      // Check user roles on the site for invalid permissions.
      $class = 'no-known-error';
      $requirement = [
        $this
          ->t('None found.'),
      ];
      $user_roles = Role::loadMultiple();
      $all_permissions = array_keys(\Drupal::service('user.permissions')
        ->getPermissions());
      foreach ($user_roles as $role) {
        $role_permissions = $role
          ->getPermissions();
        $valid_role_permissions = array_intersect($role_permissions, $all_permissions);
        $invalid_role_permissions = array_diff($role_permissions, $valid_role_permissions);
        if (!empty($invalid_role_permissions)) {
          $class = 'known-error';
          $status = FALSE;
          $requirement = [
            $this
              ->t('"@permissions" of user role: "@role".', [
              '@permissions' => implode('", "', $invalid_role_permissions),
              '@role' => $role
                ->label(),
            ]),
          ];
        }
      }
      $build['data']['#rows'][] = [
        'class' => [
          $class,
        ],
        'data' => [
          'requirement' => [
            'class' => 'requirement-label',
            'data' => $this
              ->t('Invalid permissions will trigger runtime exceptions in Drupal 10. Permissions should be defined in a permissions.yml file or a permission callback. See https://www.drupal.org/node/3193348'),
          ],
          'status' => [
            'data' => join(' ', $requirement),
            'class' => 'status-info',
          ],
        ],
      ];

      // Check for deprecated or obsolete core extensions.
      $class = 'no-known-error';
      $requirement = $this
        ->t('None installed.');
      $deprecated_or_obsolete = $this->projectCollector
        ->collectCoreDeprecatedAndObsoleteExtensions();
      if (!empty($deprecated_or_obsolete)) {
        $class = 'known-error';
        $status = FALSE;
        $requirement = join(', ', $deprecated_or_obsolete);
      }
      $build['data']['#rows'][] = [
        'class' => [
          $class,
        ],
        'data' => [
          'requirement' => [
            'class' => 'requirement-label',
            'data' => $this
              ->t('Deprecated or obsolete core extensions installed. These will be removed in the next major version.'),
          ],
          'status' => [
            'data' => $requirement,
            'class' => 'status-info',
          ],
        ],
      ];

      // Save the overall status indicator in the build array. It will be
      // popped off later to be used in the summary table.
      $build['status'] = $status;
      return $build;
    }
    $build['description'] = $this
      ->t('<a href=":upgrade">Upgrades to Drupal 9 are supported from Drupal 8.8.x and Drupal 8.9.x</a>. It is suggested to update to the latest Drupal 8 version available. <a href=":platform">Several hosting platform requirements have been raised for Drupal 9</a>.', [
      ':upgrade' => 'https://www.drupal.org/docs/9/how-to-prepare-your-drupal-7-or-8-site-for-drupal-9/upgrading-a-drupal-8-site-to-drupal-9',
      ':platform' => 'https://www.drupal.org/docs/9/how-drupal-9-is-made-and-what-is-included/environment-requirements-of-drupal-9',
    ]);

    // Check Drupal version. Link to update if available.
    $core_version_info = [
      '#type' => 'markup',
      '#markup' => $this
        ->t('Version @version and up to date.', [
        '@version' => \Drupal::VERSION,
      ]),
    ];
    $has_core_update = FALSE;
    $core_update_info = $this->releaseStore
      ->get('drupal');
    if (isset($core_update_info['releases']) && is_array($core_update_info['releases'])) {

      // Find the latest release that are higher than our current and is not beta/alpha/rc.
      foreach ($core_update_info['releases'] as $version => $release) {
        $major_version = explode('.', $version)[0];
        if (version_compare($version, \Drupal::VERSION) > 0 && empty($release['version_extra']) && $major_version === '8') {
          $link = $core_update_info['link'] . '/releases/' . $version;
          $core_version_info = [
            '#type' => 'link',
            '#title' => $this
              ->t('Version @current allows to upgrade but @new is available.', [
              '@current' => \Drupal::VERSION,
              '@new' => $version,
            ]),
            '#url' => Url::fromUri($link),
          ];
          $has_core_update = TRUE;
          break;
        }
      }
    }
    if (version_compare(\Drupal::VERSION, '8.8.0') >= 0) {
      if (!$has_core_update) {
        $class = 'no-known-error';
      }
      else {
        $class = 'known-warning';
      }
    }
    else {
      $status = FALSE;
      $class = 'known-error';
    }
    $build['data']['#rows'][] = [
      'class' => $class,
      'data' => [
        'requirement' => [
          'class' => 'requirement-label',
          'data' => $this
            ->t('Drupal core should be 8.8.x or 8.9.x'),
        ],
        'status' => [
          'data' => $core_version_info,
          'class' => 'status-info',
        ],
      ],
    ];

    // Check PHP version.
    $version = PHP_VERSION;
    if (version_compare($version, '7.3.0') >= 0) {
      $class = 'no-known-error';
    }
    else {
      $class = 'known-error';
      $status = FALSE;
    }
    $build['data']['#rows'][] = [
      'class' => [
        $class,
      ],
      'data' => [
        'requirement' => [
          'class' => 'requirement-label',
          'data' => $this
            ->t('PHP version should be at least 7.3.0'),
        ],
        'status' => [
          'data' => $this
            ->t('Version @version', [
            '@version' => $version,
          ]),
          'class' => 'status-info',
        ],
      ],
    ];

    // Check database version.
    $type = $this->database
      ->databaseType();
    $version = $this->database
      ->version();

    // If running on Drupal 8, the mysql driver might
    // mis-report the database version.
    if ($this->nextMajor == 9) {
      $versionFixer = new CorrectDbServerVersion($this->database);
      $version = $versionFixer
        ->getCorrectedDbServerVersion($version);
    }

    // MariaDB databases report as MySQL. Detect MariaDB separately based on code from
    // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Database%21Driver%21mysql%21Connection.php/function/Connection%3A%3AgetMariaDbVersionMatch/9.0.x
    // See also https://www.drupal.org/node/3119156 for test values.
    if ($type == 'mysql') {

      // MariaDB may prefix its version string with '5.5.5-', which should be
      // ignored.
      // @see https://github.com/MariaDB/server/blob/f6633bf058802ad7da8196d01fd19d75c53f7274/include/mysql_com.h#L42.
      $regex = '/^(?:5\\.5\\.5-)?(\\d+\\.\\d+\\.\\d+.*-mariadb.*)/i';
      preg_match($regex, $version, $matches);
      if (!empty($matches[1])) {
        $type = 'MariaDB';
        $version = $matches[1];
        $requirement = $this
          ->t('When using MariaDB, minimum version is 10.3.7');
        if (version_compare($version, '10.3.7') >= 0) {
          $class = 'no-known-error';
        }
        elseif (version_compare($version, '10.1.0') >= 0) {
          $class = 'known-warning';
          $requirement .= ' ' . $this
            ->t('Alternatively, <a href=":driver">install the MariaDB 10.1 driver for Drupal 9</a> for now.', [
            ':driver' => 'https://www.drupal.org/project/mysql56',
          ]);
        }
        else {
          $status = FALSE;
          $class = 'known-error';
          $requirement .= ' ' . $this
            ->t('Once updated to at least 10.1, you can also <a href=":driver">install the MariaDB 10.1 driver for Drupal 9</a> for now.', [
            ':driver' => 'https://www.drupal.org/project/mysql56',
          ]);
        }
      }
      else {
        $type = 'MySQL or Percona Server';
        $requirement = $this
          ->t('When using MySQL/Percona, minimum version is 5.7.8');
        if (version_compare($version, '5.7.8') >= 0) {
          $class = 'no-known-error';
        }
        elseif (version_compare($version, '5.6.0') >= 0) {
          $class = 'known-warning';
          $requirement .= ' ' . $this
            ->t('Alternatively, <a href=":driver">install the MySQL 5.6 driver for Drupal 9</a> for now.', [
            ':driver' => 'https://www.drupal.org/project/mysql56',
          ]);
        }
        else {
          $status = FALSE;
          $class = 'known-error';
          $requirement .= ' ' . $this
            ->t('Once updated to at least 5.6, you can also <a href=":driver">install the MySQL 5.6 driver for Drupal 9</a> for now.', [
            ':driver' => 'https://www.drupal.org/project/mysql56',
          ]);
        }
      }
    }
    elseif ($type == 'pgsql') {
      $type = 'PostgreSQL';
      $requirement = $this
        ->t('When using PostgreSQL, minimum version is 10 <a href=":trgm">with the pg_trgm extension</a> (The extension is not checked here).', [
        ':trgm' => 'https://www.postgresql.org/docs/10/pgtrgm.html',
      ]);
      if (version_compare($version, '10') >= 0) {
        $class = 'no-known-error';
      }
      else {
        $status = FALSE;
        $class = 'known-error';
      }
    }
    elseif ($type == 'sqlite') {
      $type = 'SQLite';
      $requirement = $this
        ->t('When using SQLite, minimum version is 3.26');
      if (version_compare($version, '3.26') >= 0) {
        $class = 'no-known-error';
      }
      else {
        $status = FALSE;
        $class = 'known-error';
      }
    }
    $build['data']['#rows'][] = [
      'class' => [
        $class,
      ],
      'data' => [
        'requirement' => [
          'class' => 'requirement-label',
          'data' => [
            '#type' => 'markup',
            '#markup' => $requirement,
          ],
        ],
        'status' => [
          'data' => $type . ' ' . $version,
          'class' => 'status-info',
        ],
      ],
    ];

    // Check Apache. Logic is based on system_requirements() code.
    $request_object = \Drupal::request();
    $software = $request_object->server
      ->get('SERVER_SOFTWARE');
    if (strpos($software, 'Apache') !== FALSE && preg_match('!^Apache/([\\d\\.]+) !', $software, $found)) {
      $version = $found[1];
      if (version_compare($version, '2.4.7') >= 0) {
        $class = 'no-known-error';
      }
      else {
        $status = FALSE;
        $class = 'known-error';
      }
      $label = $this
        ->t('Version @version', [
        '@version' => $version,
      ]);
    }
    else {
      $class = '';
      $label = $this
        ->t('Version cannot be detected or not using Apache, check manually.');
    }
    $build['data']['#rows'][] = [
      'class' => [
        $class,
      ],
      'data' => [
        'requirement' => [
          'class' => 'requirement-label',
          'data' => $this
            ->t('When using Apache, minimum version is 2.4.7'),
        ],
        'status' => [
          'data' => $label,
          'class' => 'status-info',
        ],
      ],
    ];

    // Check Drush. We only detect site-local drush for now.
    if (class_exists('\\Drush\\Drush')) {
      $version = call_user_func('\\Drush\\Drush::getMajorVersion');
      if (version_compare($version, '10') >= 0) {
        $class = 'no-known-error';
      }
      else {
        $status = FALSE;
        $class = 'known-error';
      }
      $label = $this
        ->t('Version @version', [
        '@version' => $version,
      ]);
    }
    else {
      $class = '';
      $label = $this
        ->t('Version cannot be detected, check manually.');
    }
    $build['data']['#rows'][] = [
      'class' => $class,
      'data' => [
        'requirement' => [
          'class' => 'requirement-label',
          'data' => $this
            ->t('When using Drush, minimum version is 10'),
        ],
        'status' => [
          'data' => $label,
          'class' => 'status-info',
        ],
      ],
    ];

    // Check deprecated $config_directories if after Drupal 8.8.0. On older
    // Drupal versions, the replacement is not supported and the setting may
    // be generated by platforms like ddev, leading to false positives that
    // the user should not even resolve yet before updating core.
    if (version_compare(\Drupal::VERSION, '8.8.0') >= 0) {
      $class = 'no-known-error';
      $requirement = $this
        ->t('Use of $config_directories in settings.php is deprecated.');
      $label = $this
        ->t('Not used');
      $is_deprecated = $this
        ->isDeprecatedConfigDirectorySettingUsed();
      if ($is_deprecated !== FALSE) {
        $status = FALSE;
        $class = 'known-error';
        if ($is_deprecated === TRUE) {
          $label = $this
            ->t('Deprecated configuration used');
          $requirement .= ' ' . $this
            ->t('<a href=":settings">Use $settings[\'config_sync_directory\'] instead.</a>', [
            ':settings' => 'https://www.drupal.org/node/3018145',
          ]);
        }
        else {
          $label = $this
            ->t('Deprecated and new configuration used');
          $requirement .= ' ' . $this
            ->t('<a href=":settings">Use $settings[\'config_sync_directory\'] only.</a>', [
            ':settings' => 'https://www.drupal.org/node/3018145',
          ]);
        }
      }
      $build['data']['#rows'][] = [
        'class' => $class,
        'data' => [
          'requirement' => [
            'class' => 'requirement-label',
            'data' => [
              '#type' => 'markup',
              '#markup' => $requirement,
            ],
          ],
          'status' => [
            'data' => $label,
            'class' => 'status-info',
          ],
        ],
      ];
    }

    // Save the overall status indicator in the build array. It will be
    // popped off later to be used in the summary table.
    $build['status'] = $status;
    return $build;
  }

  /**
   * Form submission handler.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {

    // Reset extension lists for better Drupal 9 compatibility info.
    $this->projectCollector
      ->resetLists();
    $operations = $list = [];
    $projects = $this->projectCollector
      ->collectProjects();
    $submitted = $form_state
      ->getValues();
    $next_steps = $this->projectCollector
      ->getNextStepInfo();
    foreach ($next_steps as $next_step => $step_label) {
      if (!empty($submitted[$next_step]['data']['list'])) {
        foreach ($submitted[$next_step]['data']['list'] as $item) {
          if (isset($projects[$item])) {
            $list[] = $projects[$item];
          }
        }
      }
    }

    // It is not possible to make an HTTP request to this same webserver
    // if the host server is PHP itself, because it is single-threaded.
    // See https://www.php.net/manual/en/features.commandline.webserver.php
    $use_http = php_sapi_name() != 'cli-server';
    $php_server = !$use_http;
    if ($php_server) {

      // Log the selected processing method for project support purposes.
      $this->logger
        ->notice('Starting Upgrade Status on @count projects without HTTP sandboxing because the built-in PHP webserver does not allow for that.', [
        '@count' => count($list),
      ]);
    }
    else {

      // Attempt to do an HTTP request to the frontpage of this Drupal instance.
      // If that does not work then we'll not be able to process projects over
      // HTTP. Processing projects directly is less safe (in case of PHP fatal
      // errors the batch process may halt), but we have no other choice here
      // but to take a chance.
      list($error, $message, $data) = static::doHttpRequest('upgrade_status_request_test', 'upgrade_status_request_test');
      if (empty($data) || !is_array($data) || $data['message'] != 'Request test success') {
        $use_http = FALSE;
        $this->logger
          ->notice('Starting Upgrade Status on @count projects without HTTP sandboxing. @error', [
          '@error' => $message,
          '@count' => count($list),
        ]);
      }
    }
    if ($use_http) {

      // Log the selected processing method for project support purposes.
      $this->logger
        ->notice('Starting Upgrade Status on @count projects with HTTP sandboxing.', [
        '@count' => count($list),
      ]);
    }
    foreach ($list as $item) {
      $operations[] = [
        static::class . '::parseProject',
        [
          $item,
          $use_http,
        ],
      ];
    }
    if (!empty($operations)) {

      // Allow other modules to alter the operations to be run.
      $this->moduleHandler
        ->alter('upgrade_status_operations', $operations, $form_state);
    }
    if (!empty($operations)) {
      $batch = [
        'title' => $this
          ->t('Scanning projects'),
        'operations' => $operations,
        'finished' => static::class . '::finishedParsing',
      ];
      batch_set($batch);
    }
    else {
      $this
        ->messenger()
        ->addError('No projects selected to scan.');
    }
  }

  /**
   * Form submission handler.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param string $format
   *   Either 'html' or 'ascii' depending on what the format should be.
   */
  public function exportReport(array &$form, FormStateInterface $form_state, string $format = 'html') {
    $extensions = [];
    $projects = $this->projectCollector
      ->collectProjects();
    $submitted = $form_state
      ->getValues();
    $next_steps = $this->projectCollector
      ->getNextStepInfo();
    foreach ($next_steps as $next_step => $step_label) {
      if (!empty($submitted[$next_step]['data']['list'])) {
        foreach ($submitted[$next_step]['data']['list'] as $item) {
          if (isset($projects[$item])) {
            $type = $projects[$item]->info['upgrade_status_type'] == ProjectCollector::TYPE_CUSTOM ? 'custom' : 'contrib';
            $extensions[$type][$item] = $format == 'html' ? $this->resultFormatter
              ->formatResult($projects[$item]) : $this->resultFormatter
              ->formatAsciiResult($projects[$item]);
          }
        }
      }
    }
    if (empty($extensions)) {
      $this
        ->messenger()
        ->addError('No projects selected to export.');
      return;
    }
    $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 . '"');
    $form_state
      ->setResponse($response);
  }

  /**
   * Form submission handler.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public function exportReportASCII(array &$form, FormStateInterface $form_state) {
    $this
      ->exportReport($form, $form_state, 'ascii');
  }

  /**
   * Batch callback to analyze a project.
   *
   * @param \Drupal\Core\Extension\Extension $extension
   *   The extension to analyze.
   * @param bool $use_http
   *   Whether to use HTTP to execute the processing or execute locally. HTTP
   *   processing could fail in some container setups. Local processing may
   *   fail due to timeout or memory limits.
   * @param array $context
   *   Batch context.
   */
  public static function parseProject(Extension $extension, $use_http, &$context) {
    $context['message'] = t('Analysis complete for @project.', [
      '@project' => $extension
        ->getName(),
    ]);
    if (!$use_http) {
      \Drupal::service('upgrade_status.deprecation_analyzer')
        ->analyze($extension);
      return;
    }

    // Do the HTTP request to run processing.
    list($error, $message, $data) = static::doHttpRequest($extension
      ->getName());
    if ($error !== FALSE) {

      /** @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value */
      $key_value = \Drupal::service('keyvalue')
        ->get('upgrade_status_scan_results');
      $result = [];
      $result['date'] = \Drupal::time()
        ->getRequestTime();
      $result['data'] = [
        'totals' => [
          'errors' => 1,
          'file_errors' => 1,
          'upgrade_status_split' => [
            'warning' => 1,
          ],
        ],
        'files' => [],
      ];
      $result['data']['files'][$error] = [
        'errors' => 1,
        'messages' => [
          [
            'message' => $message,
            'line' => 0,
          ],
        ],
      ];
      $key_value
        ->set($extension
        ->getName(), $result);
    }
  }

  /**
   * Batch callback to finish parsing.
   *
   * @param $success
   *   TRUE if the batch operation was successful; FALSE if there were errors.
   * @param $results
   *   An associative array of results from the batch operation.
   */
  public static function finishedParsing($success, $results) {
    $logger = \Drupal::logger('upgrade_status');
    if ($success) {
      $logger
        ->notice('Finished Upgrade Status processing successfully.');
    }
    else {
      $logger
        ->notice('Finished Upgrade Status processing with errors.');
    }
  }

  /**
   * Do an HTTP request with the type and machine name.
   *
   * @param string $project_machine_name
   *   The machine name of the project.
   *
   * @return array
   *   A three item array with any potential errors, the error message and the
   *   returned data as the third item. Either of them will be FALSE if they are
   *   not applicable. Data may also be NULL if response JSON decoding failed.
   */
  public static function doHttpRequest(string $project_machine_name) {
    $error = $message = $data = FALSE;

    // Prepare for a POST request to scan this project. The separate HTTP
    // request is used to separate any PHP errors found from this batch process.
    // We can store any errors and gracefully continue if there was any PHP
    // errors in parsing.
    $url = Url::fromRoute('upgrade_status.analyze', [
      'project_machine_name' => $project_machine_name,
    ]);

    // Pass over authentication information because access to this functionality
    // requires administrator privileges.

    /** @var \Drupal\Core\Session\SessionConfigurationInterface $session_config */
    $session_config = \Drupal::service('session_configuration');
    $request = \Drupal::request();
    $session_options = $session_config
      ->getOptions($request);

    // Unfortunately DrupalCI testbot does not have a domain that would normally
    // be considered valid for cookie setting, so we need to work around that
    // by manually setting the cookie domain in case there was none. What we
    // care about is we get actual results, and cookie on the host level should
    // suffice for that.
    $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 a POST request with the session cookie included. We expect valid JSON
    // back. In case there was a PHP error before that, we log that.
    try {
      $response = \Drupal::httpClient()
        ->post($url
        ->setAbsolute()
        ->toString(), $options);
      $data = json_decode((string) $response
        ->getBody(), TRUE);
      if (!$data) {
        $error = 'PHP Fatal Error';
        $message = (string) $response
          ->getBody();
      }
    } catch (\Exception $e) {
      $error = 'Scanning exception';
      $message = $e
        ->getMessage();
    }
    return [
      $error,
      $message,
      $data,
    ];
  }

  /**
   * Checks config directory settings for use of deprecated values.
   *
   * The $config_directories variable is deprecated in Drupal 8. However,
   * the Settings object obscures the fact in Settings:initialise(), where
   * it throws an error but levels the values in the deprecated location
   * and $settings. So after that, it is not possible to tell if either
   * were set in settings.php or not.
   *
   * Therefore we reproduce loading of settings and check the raw values.
   *
   * @return bool|NULL
   *   TRUE if the deprecated setting is used. FALSE if not used.
   *   NULL if both values are used.
   */
  protected function isDeprecatedConfigDirectorySettingUsed() {
    $app_root = $this->kernel
      ->getAppRoot();
    $site_path = $this->kernel
      ->getSitePath();
    if (is_readable($app_root . '/' . $site_path . '/settings.php')) {

      // Reset the "global" variables expected to exist for settings.
      $settings = [];
      $config = [];
      $databases = [];
      $class_loader = (require $app_root . '/autoload.php');
      require $app_root . '/' . $site_path . '/settings.php';
    }
    if (!empty($config_directories)) {
      if (!empty($settings['config_sync_directory'])) {

        // Both are set. The $settings copy will prevail in Settings::initialise().
        return NULL;
      }

      // Only the deprecated variable is set.
      return TRUE;
    }

    // The deprecated variable is not set.
    return FALSE;
  }

  /**
   * Dynamic page title for the form to make the status target clear.
   */
  public function getTitle() {
    return $this
      ->t('Drupal @version upgrade status', [
      '@version' => $this->nextMajor,
    ]);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
FormBase::$configFactory protected property The config factory. 1
FormBase::$requestStack protected property The request stack. 1
FormBase::$routeMatch protected property The route match.
FormBase::config protected function Retrieves a configuration object.
FormBase::configFactory protected function Gets the config factory for this form. 1
FormBase::container private function Returns the service container.
FormBase::currentUser protected function Gets the current user.
FormBase::getRequest protected function Gets the request object.
FormBase::getRouteMatch protected function Gets the route match.
FormBase::logger protected function Gets the logger for a specific channel.
FormBase::redirect protected function Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait::redirect
FormBase::resetConfigFactory public function Resets the configuration factory.
FormBase::setConfigFactory public function Sets the config factory for this form.
FormBase::setRequestStack public function Sets the request stack object to use.
FormBase::validateForm public function Form validation handler. Overrides FormInterface::validateForm 62
LinkGeneratorTrait::$linkGenerator protected property The link generator. 1
LinkGeneratorTrait::getLinkGenerator Deprecated protected function Returns the link generator.
LinkGeneratorTrait::l Deprecated protected function Renders a link to a route given a route name and its parameters.
LinkGeneratorTrait::setLinkGenerator Deprecated public function Sets the link generator service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
UpgradeStatusForm::$database protected property Database connection.
UpgradeStatusForm::$dateFormatter protected property The date formatter.
UpgradeStatusForm::$deprecationAnalyzer protected property The deprecation analyzer.
UpgradeStatusForm::$destination protected property The destination service.
UpgradeStatusForm::$kernel protected property Drupal kernel.
UpgradeStatusForm::$logger protected property The logger service.
UpgradeStatusForm::$moduleHandler protected property The module handler service.
UpgradeStatusForm::$nextMajor protected property The next Drupal core major version.
UpgradeStatusForm::$projectCollector protected property The project collector service.
UpgradeStatusForm::$releaseStore protected property Available releases store.
UpgradeStatusForm::$renderer protected property The renderer service.
UpgradeStatusForm::$resultFormatter protected property The scan result formatter service.
UpgradeStatusForm::$state protected property The state service.
UpgradeStatusForm::buildEnvironmentChecks protected function Builds a list of environment checks.
UpgradeStatusForm::buildForm public function Form constructor. Overrides FormInterface::buildForm
UpgradeStatusForm::buildProjectList protected function Builds a list and status summary of projects.
UpgradeStatusForm::buildResultSummary protected function Build a result summary table for quick overview display to users.
UpgradeStatusForm::create public static function Instantiates a new instance of this class. Overrides FormBase::create
UpgradeStatusForm::doHttpRequest public static function Do an HTTP request with the type and machine name.
UpgradeStatusForm::exportReport public function Form submission handler.
UpgradeStatusForm::exportReportASCII public function Form submission handler.
UpgradeStatusForm::finishedParsing public static function Batch callback to finish parsing.
UpgradeStatusForm::getFormId public function Returns a unique string identifying the form. Overrides FormInterface::getFormId
UpgradeStatusForm::getTitle public function Dynamic page title for the form to make the status target clear.
UpgradeStatusForm::isDeprecatedConfigDirectorySettingUsed protected function Checks config directory settings for use of deprecated values.
UpgradeStatusForm::parseProject public static function Batch callback to analyze a project.
UpgradeStatusForm::submitForm public function Form submission handler. Overrides FormInterface::submitForm
UpgradeStatusForm::__construct public function Constructs a Drupal\upgrade_status\Form\UpgradeStatusForm.
UrlGeneratorTrait::$urlGenerator protected property The url generator.
UrlGeneratorTrait::getUrlGenerator Deprecated protected function Returns the URL generator service.
UrlGeneratorTrait::setUrlGenerator Deprecated public function Sets the URL generator service.
UrlGeneratorTrait::url Deprecated protected function Generates a URL or path for a specific route based on the given parameters.