sgd_dashboard-1.0.0-beta1/src/Plugin/SgdCompanion/SgdCompanionSgdCore.php

src/Plugin/SgdCompanion/SgdCompanionSgdCore.php
<?php

namespace Drupal\sgd_dashboard\Plugin\SgdCompanion;

use Drupal\sgd_dashboard\SgdCompanionPluginBase;

/**
 * Provides a SGD Companion plugin.
 *
 * @SgdCompanion(
 *   id = "sgd_companion_sgd_core",
 * )
 */
class SgdCompanionSgdCore extends SgdCompanionPluginBase {

  /**
   * {@inheritdoc}
   *
   * The core companion can allways process something.
   */
  public function canProcessStatus($statusData) : bool {
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function saveStatus($websiteData, $statusData, $enabledProjects = NULL) : bool {

    if ($this->canProcessStatus($statusData)) {

      // The following values are provided by the Site Guardian API module.
      $coreData = [
        'drupal_version' => $statusData['drupal']['value'] ?? NULL,
        'core_update_status' => strip_tags($statusData['update_core']['value'] ?? ''),
        'contrib_update_status' => strip_tags($statusData['update_contrib']['value'] ?? ''),
        'update_access' => $statusData['update access']['value'] ?? NULL,
        'configuration_files' => $statusData['configuration_files']['value'] ?? NULL,
        'file_system' => strip_tags($statusData['file system']['value'] ?? ''),
        'cron_last_run' => $statusData['cron']['value'] ?? NULL,
        'trusted_host_patterns' => $statusData['trusted_host_patterns']['value'] ?? NULL,
        'php_version' => strtok($statusData['php']['value'] ?? '', '('),
        'php_memory_limit' => $statusData['php_memory_limit']['value'] ?? NULL,
        'php_apcu_caching' => $statusData['php_apcu_enabled']['value'] ?? NULL,
        'php_opcache' => $statusData['php_opcache']['value'] ?? NULL,
        'db_type' => $statusData['database_system']['value'] ?? NULL,
        'db_version' => $statusData['database_system_version']['value'] ?? NULL,
        'db_updates' => $statusData['update']['value'] ?? NULL,
        'db_transaction_level' => $statusData['mysql_transaction_level']['value'] ?? NULL,
        'webserver' => $statusData['webserver']['value'] ?? NULL,
        'redis' => strip_tags($statusData['redis']['value'] ?? ''),
        'sg_notes' => $statusData['sgd_api_notes']['value'] ?? NULL,
        'updates_last_checked' => $statusData['sgd_updates_last_checked']['value'] ?? NULL,
        'twig_debug_enabled' => $statusData['twig_debug_enabled']['value'] ?? NULL,
        'render_cache_disabled' => $statusData['render_cache_disabled']['value'] ?? NULL,
      ];

      // If we have uninstalled module data from the Site Guardian API module
      // (1.0.6 onwards maybe).
      if (!empty($statusData['sgd_projects_uninstalled_count'])) {
        $coreData['projects_uninstalled'] = $statusData['sgd_projects_uninstalled_count']['value'];
      }

      // We need some module summary information displayed on the main website
      // page.
      // All of the following are derived from the enabled projects array.
      // Total number of modules.
      $moduleCount = count(array_filter($enabledProjects, function ($v) {
        return $v['project_type'] == 'module';
      }));

      // Total number of themes.
      $themeCount = count(array_filter($enabledProjects, function ($v) {
        return $v['project_type'] == 'theme';
      }));

      // Projects with security updates.
      $securityCount = count(array_filter($enabledProjects, function ($v) {
        return ($v['name'] != 'drupal' && $v['status'] == '1');
      }));

      // Projects unsupported.
      $unsupportedCount = count(array_filter($enabledProjects, function ($v) {
        return ($v['name'] != 'drupal' && ($v['status'] == '2' || $v['status'] == '3'));
      }));

      // Projects with available updates.
      $updatesCount = count(array_filter($enabledProjects, function ($v) {
        return ($v['name'] != 'drupal' && $v['status'] == '4');
      }));

      $coreData += [
        'enabled_modules_count' => $moduleCount,
        'enabled_themes_count' => $themeCount,
        'projects_security_count' => $securityCount,
        'projects_unsupported_count' => $unsupportedCount,
        'projects_updates_count' => $updatesCount,
      ];

      // Serialize the status data and save the field.
      $websiteData->set('data_core', serialize($coreData));

      // Any values derived from the status data (or project data) that we want
      // to use in views need to be stored in fields on the website data entity
      // so Views can use them.
      $websiteData->set('drupal_version', $this->getValueOrDefault($statusData['drupal'] ?? NULL));
      $websiteData->set('php_version', strtok($this->getValueOrDefault($statusData['php'] ?? NULL), '('));
      $websiteData->set('db_version', $this->getValueOrDefault($statusData['database_system_version'] ?? NULL));

      // Is core version secure?
      $coreIsSecure = $enabledProjects['drupal']['status'] != 1;

      $websiteData->set('core_secure', $coreIsSecure);

      // Are all contrib module versions secure?
      $contribIsSecure = TRUE;

      foreach ($enabledProjects as $moduleName => $moduleData) {
        if (!in_array($moduleName, ['drupal'])) {
          if ($moduleData['status'] == 1) {
            $contribIsSecure = FALSE;
            break;
          }
        }
      }

      $websiteData->set('contrib_secure', $contribIsSecure);
    }

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getStatusDefaults() : array {

    $data = [
      'drupal' => [
        'sg_notes' => [
          'title' => $this->t('Site notes'),
          'value' => 'n/a',
        ],
        'drupal_version' => [
          'title' => $this->t('Drupal version'),
          'value' => 'n/a',
        ],
        'core_update_status' => [
          'title' => $this->t('Core update status'),
          'value' => 'n/a',
        ],
        'contrib_update_status' => [
          'title' => $this->t('Contrib update status'),
          'value' => 'n/a',
        ],
        'db_updates' => [
          'title' => $this->t('Database updates'),
          'value' => 'n/a',
        ],
        'cron_last_run' => [
          'title' => $this->t('Cron last run'),
          'value' => 0,
        ],
        'configuration_files' => [
          'title' => $this->t('Configuration files'),
          'value' => 'n/a',
        ],
        'update_access' => [
          'title' => $this->t('Update access'),
          'value' => 'n/a',
        ],
        'file_system' => [
          'title' => $this->t('File system writable'),
          'value' => 'n/a',
        ],
        'trusted_host_patterns' => [
          'title' => $this->t('Trusted host patterns'),
          'value' => 'n/a',
        ],
        'twig_debug_enabled' => [
          'title' => $this->t('Twig development mode'),
          'value' => 'n/a',
        ],
        'render_cache_disabled' => [
          'title' => $this->t('Markup caching'),
          'value' => 'n/a',
        ],
        'available_updates_last_retreived' => [
          'title' => $this->t('Updates last retrieved'),
          'value' => 'n/a',
        ],
      ],
      'hosting' => [
        'webserver' => [
          'title' => $this->t('Webserver'),
          'value' => 'n/a',
        ],
        'db_type' => [
          'title' => $this->t('Database type'),
          'value' => 'n/a',
        ],
        'db_version' => [
          'title' => $this->t('Database version'),
          'value' => 'n/a',
        ],
        'db_transaction_level' => [
          'title' => $this->t('Database transaction level'),
          'value' => 'n/a',
        ],
        'redis' => [
          'title' => $this->t('Redis'),
          'value' => 'n/a',
        ],
      ],
      'projects' => [
        'enabled_modules_count' => [
          'title' => $this->t('Enabled projects count'),
          'value' => 'n/a',
        ],
        'enabled_themes_count' => [
          'title' => $this->t('Enabled themes count'),
          'value' => 'n/a',
        ],
        'updates_available_count' => [
          'title' => $this->t('Projects with updates available'),
          'value' => 'n/a',
        ],
        'uninstalled' => [
          'title' => $this->t('Projects with no enabled modules'),
          'value' => 'n/a',
        ],
        'security_count' => [
          'title' => $this->t('Projects with security updates'),
          'value' => 'n/a',
        ],
        'unsupported_count' => [
          'title' => $this->t('Projects no longer supported'),
          'value' => 'n/a',
        ],
      ],
      'php' => [
        'php_version' => [
          'title' => $this->t('PHP version'),
          'value' => 'n/a',
        ],
        'php_memory_limit' => [
          'title' => $this->t('PHP memory limit'),
          'value' => 'n/a',
        ],
        'php_apcu_caching' => [
          'title' => $this->t('PHP APCu caching'),
          'value' => 'n/a',
        ],
        'php_opcache' => [
          'title' => $this->t('PHP opcache'),
          'value' => 'n/a',
        ],
      ],
    ];

    return $data;
  }

  /**
   * {@inheritdoc}
   */
  public function getStatus($websiteData) : array | NULL {

    // Get the titles and value defaults so we can display something sensible
    // even if the website has not been queried for the core data yet.
    $status = $this->getStatusDefaults();

    // If we have core data from the website.
    if ($dataSerialized = $websiteData->get('data_core')->value) {

      $data = unserialize($dataSerialized, ['allowed_classes' => FALSE]);

      // Update the values in the status with the data returned from the API
      // Here is the oppertunity to massage/transform any data we got from the
      // website status data.
      // Do it here rather than when saving so we retain all info received as
      // it's received.
      // If no updates last checked date then set to 0 so twig date output
      // doesnt complain.
      $data['updates_last_checked'] = $data['updates_last_checked'] ?? 0;

      // Redis is returned as an empty string if not connected so update with
      // the default if it is blank.
      $data['redis'] = !empty($data['redis']) ? $data['redis'] : $status['hosting']['redis']['value'];

      // If no projects uninstalled value then use default.
      $data['projects_uninstalled'] = $data['projects_uninstalled'] ?? $status['projects']['uninstalled']['value'];

      // twig_debug_enabled and render_cache_disabled only introduced in
      // D10.1.0 - Will version_compare always be ok for Drupal version????
      if (version_compare($data['drupal_version'], '10.1.0', '>=')) {
        $twigDebug = !empty($data['twig_debug_enabled']) ? $this->t('Enabled') : $this->t('Disabled');
        $rcDisabled = empty($data['render_cache_disabled']) ? $this->t('Enabled') : $this->t('Disabled');
      }
      else {
        $twigDebug = $this->t("Not available prior to Drupal 10.1.");
        $rcDisabled = $this->t("Not available prior to Drupal 10.1.");
      }

      // Format the updates last checked date into something reaosnable.
      $availableUpdatesLastChecked = $this->dateFormatter->format($data['updates_last_checked'], 'custom', 'l jS \of F Y \a\t H:i:s');

      // Merge the data with the titles and defaults.
      $status = array_replace_recursive($status, [
        'drupal' => [
          'sg_notes' => [
            'value' => $data['sg_notes'],
          ],
          'drupal_version' => [
            'value' => $data['drupal_version'],
          ],
          'core_update_status' => [
            'value' => $data['core_update_status'],
          ],
          'contrib_update_status' => [
            'value' => $data['contrib_update_status'],
          ],
          'db_updates' => [
            'value' => $data['db_updates'],
          ],
          'cron_last_run' => [
            'value' => $data['cron_last_run'],
          ],
          'configuration_files' => [
            'value' => $data['configuration_files'],
          ],
          'update_access' => [
            'value' => $data['update_access'],
          ],
          'file_system' => [
            'value' => $data['file_system'],
          ],
          'trusted_host_patterns' => [
            'value' => $data['trusted_host_patterns'],
          ],
          'available_updates_last_retreived' => [
            'value' => $availableUpdatesLastChecked,
          ],
          'twig_debug_enabled' => [
            'value' => $twigDebug,
          ],
          'render_cache_disabled' => [
            'value' => $rcDisabled,
          ],
        ],
        'hosting' => [
          'webserver' => [
            'value' => $data['webserver'],
          ],
          'db_type' => [
            'value' => $data['db_type'],
          ],
          'db_version' => [
            'value' => $data['db_version'],
          ],
          'db_transaction_level' => [
            'value' => $data['db_transaction_level'],
          ],
          'redis' => [
            'value' => $data['redis'],
          ],
        ],
        'projects' => [
          'enabled_modules_count' => [
            'value' => $data['enabled_modules_count'],
          ],
          'enabled_themes_count' => [
            'value' => $data['enabled_themes_count'],
          ],

          'updates_available_count' => [
            'value' => $data['projects_updates_count'],
          ],

          'uninstalled' => [
            'value' => $data['projects_uninstalled'],
          ],

          'security_count' => [
            'value' => $data['projects_security_count'],
          ],

          'unsupported_count' => [
            'value' => $data['projects_unsupported_count'],
          ],
        ],
        'php' => [
          'php_version' => [
            'value' => $data['php_version'],
          ],
          'php_memory_limit' => [
            'value' => $data['php_memory_limit'],
          ],
          'php_apcu_caching' => [
            'value' => $data['php_apcu_caching'],
          ],
          'php_opcache' => [
            'value' => $data['php_opcache'],
          ],
        ],
      ]);
    }

    return $status;
  }

  /**
   * {@inheritdoc}
   */
  public function getBuildElements($websiteData) : array | NULL {

    if ($data = $this->getStatus($websiteData)) {

      // Validate the values and add validation status info so can be
      // displayed.
      $validation = $this->validate($data['drupal']);

      foreach ($data['drupal'] as $key => $value) {
        if (!empty($validation[$key])) {
          $data['drupal'][$key]['status'] = $validation[$key];
        }
      }

      $validation = $this->validate($data['hosting']);

      foreach ($data['hosting'] as $key => $value) {
        if (!empty($validation[$key])) {
          $data['hosting'][$key]['status'] = $validation[$key];
        }
      }
    }

    return $data;
  }

  /**
   * Validate data.
   *
   * Checks each item and returns a good/neutral/bad status that can be
   * displayed on page or in a report.
   */
  private function validate(&$statusData): array {

    $validation = [];

    // Validate core variables
    // Each validation is hard coded as it differs for each plugin.
    // You dont have to validate everything. Just things that it makes sense
    // to.
    foreach ($statusData as $key => $status) {

      switch ($key) {

        case 'core_update_status':
        case 'contrib_update_status':
        case 'db_updates':

          if (str_starts_with($status['value'], 'Not secure!')) {
            $validation[$key] = [
              'class' => 'error',
              'text' => $this->t('Issue'),
              'message' => $this->t('@var -  Not secure. Review and update immediately.', ['@var' => $status['title']]),
            ];
          }
          elseif ($status['value'] != 'Up to date') {
            $validation[$key] = [
              'class' => 'warning',
              'text' => $this->t('Alert'),
              'message' => $this->t('@var -  Not up-to-date. Review and update as required.', ['@var' => $status['title']]),
            ];
          }
          else {
            $validation[$key] = [
              'class' => 'ok',
              'text' => $this->t('OK'),
            ];
          }

          break;

        case 'configuration_files':

          if ($status['value'] == 'Protection disabled') {
            $validation[$key] = [
              'class' => 'error',
              'text' => $this->t('Issue'),
              'message' => $this->t('Configuration files are not protected.'),
            ];
          }
          else {
            $validation[$key] = [
              'class' => 'ok',
              'text' => $this->t('OK'),
            ];
          }

          break;

        case 'file_system_writable':

          $lastRunString = substr($status['value'], strlen('Last run '));

          if (new \DateTime($lastRunString) < new \DateTime('1 day ago')) {
            $validation[$key] = [
              'class' => 'error',
              'text' => $this->t('Issue'),
              'message' => $this->t('CRON has not run for more than a day.'),
            ];
          }

          break;

        case 'trusted_host_patterns':

          if ($status['value'] != 'Enabled') {
            $validation[$key] = [
              'class' => 'error',
              'text' => $this->t('Issue'),
              'message' => $this->t('No trusted host pattern has been specified which makes the site vulnerable to HTTP Host header spoofing.'),
            ];
          }
          else {
            $validation[$key] = [
              'class' => 'ok',
              'text' => $this->t('OK'),
            ];
          }

          break;

        case 'redis':

          if (!str_starts_with($status['value'], 'Connected')) {
            $validation[$key] = [
              'class' => 'warning',
              'text' => $this->t('Alert'),
              'message' => $this->t('No REDIS cache server is connected. Drupal performance is significantly enhanced with an in memory cache.'),
            ];
          }
          else {
            $validation[$key] = [
              'class' => 'ok',
              'text' => $this->t('OK'),
            ];
          }

          break;

        case 'cron_last_run':

          $lastRunString = substr($status['value'], strlen('Last run '));

          if (new \DateTime($lastRunString) < new \DateTime('1 day ago')) {
            $validation[$key] = [
              'class' => 'error',
              'text' => $this->t('Issue'),
              'message' => $this->t('CRON has not run for more than a day.'),
            ];
          }
          else {
            $validation[$key] = [
              'class' => 'ok',
              'text' => $this->t('OK'),
            ];
          }

          break;

        case 'update_access':

          if ($status['value'] == 'Not protected') {
            $validation[$key] = [
              'class' => 'error',
              'text' => $this->t('Issue'),
              'message' => $this->t('The update.php script is accessible to everyone without authentication, which is a security risk.'),
            ];
          }
          else {
            $validation[$key] = [
              'class' => 'ok',
              'text' => $this->t('OK'),
            ];
          }

          break;

        case 'db_transaction_level':

          if ($status['value'] != 'READ-COMMITTED') {
            $validation[$key] = [
              'class' => 'warning',
              'text' => $this->t('Alert'),
              'message' => $this->t('Recommended value for DB transation level is READ-COMMITTED.'),
            ];
          }
          else {
            $validation[$key] = [
              'class' => 'ok',
              'text' => $this->t('OK'),
            ];
          }

          break;

        case 'twig_debug_enabled':

          if (version_compare($statusData['drupal_version']['value'], '10.1.0', '>=')) {

            if ($status['value'] == 'Enabled') {
              $validation[$key] = [
                'class' => 'warning',
                'text' => $this->t('Alert'),
                'message' => $this->t('Twig debug is enabled.'),
              ];
            }
            else {
              $validation[$key] = [
                'class' => 'ok',
                'text' => $this->t('OK'),
              ];
            }
          }

          // If Drupal version is prior to 10.1 then we dont know the value as
          // it doesn't really exist.
          else {
            $statusData[$key]['value'] = $this->t('n/a');
            $validation[$key] = [
              'message' => $this->t('Not available prior to Drupal 10.1.'),
            ];
          }

          break;

        case 'render_cache_disabled':

          if (version_compare($statusData['drupal_version']['value'], '10.1.0', '>=')) {

            if ($status['value'] == 'Disabled') {
              $validation[$key] = [
                'class' => 'warning',
                'text' => $this->t('Alert'),
                'message' => $this->t('Render cache, dynamic page cache, and page cache are bypassed.'),
              ];
            }
            else {
              $validation[$key] = [
                'class' => 'ok',
                'text' => $this->t('OK'),
              ];
            }
          }

          // If Drupal version is prior to 10.1 then we dont know the value as
          // it doesn't really exist.
          else {
            $statusData[$key]['value'] = $this->t('n/a');
            $validation[$key] = [
              'message' => $this->t('Not available prior to Drupal 10.1.'),
            ];
          }

          break;

        case 'available_updates_last_retreived':

          $validation[$key] = [
            'message' => $this->t('The last time the website requested update information from Drupal.org. This data is cached by the website and only requested periodically.'),
          ];

          break;
      }
    }

    return $validation;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc