autoupdate-8.x-1.x-dev/src/ModuleManager.php

src/ModuleManager.php
<?php
namespace Drupal\autoupdate;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\node\Entity\Node;
use Drupal\Core\Url;
use Drupal\autoupdate\ModuleManagerInterface;
use Drupal\Component\Utility\Crypt;

/**
 * Provides methods for ModuleManager.
 */
class ModuleManager implements ModuleManagerInterface {

  /**
   * Config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * Module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Composer Lock data.
   */
  protected $composerLock = NULL;

  /**
   * Composer Json data.
   */
  protected $composerJson = NULL;

  /**
   * Drupal Core Version.
   */
  protected $drupalCore = NULL;

  /**
   * Site key.
   */
  protected $siteKey = NULL;

  /**
   * Creates a new ModuleManager manager.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   */
  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler) {
    $this->configFactory = $config_factory;
    $this->moduleHandler = $module_handler;
    $this->drupalCore = explode('.', \Drupal::VERSION)[0];

    if (isset($_SERVER['SERVER_NAME'])) {
      $server = $_SERVER['SERVER_NAME'];
    }
    else {
      $server = getenv('HOME');
    }
    $this->siteKey = Crypt::hmacBase64($server, '');
  }

  /**
   * Get all modules.
   *
   * @param bool $installed
   *
   * @return array
   */
  public function getModules($installed = TRUE) {
    // Get all modules.
    $all_modules = $this->getModuleData();
    $selected_modules = array();
    foreach  ($all_modules as $module) {
      // Only installed.
      if ($installed == TRUE) {
        if ($module->status == 0) {
          continue;
        }
      }

      //  Check core pathes.
      if (strpos($module->getPathname(), 'core/') === 0) {
        // Add core if dont exists.
        if (!isset($selected_modules['core'])) {
          // Add data for core.
          $version = NULL;
          $mtime = NULL;
          $install_type = 'manual';
          $composer_path = NULL;
          $composer_setting = NULL;
          $composer_data = $this->getComposerData('core');
          $composer_data_recommended = $this->getComposerData('core-recommended');
          $corename = 'core';
          if ($composer_data['composer'] == TRUE) {
            $install_type = 'composer';
            $version = $composer_data['version'];
            $mtime = $composer_data['time'];
            $composer_path = $composer_data['composer_path'];
            $composer_setting = $composer_data['composer_setting'];
          }
          if ($composer_data_recommended['composer'] == TRUE) {
            $corename = 'core-recommended';
            $install_type = 'composer';
            $version = $composer_data_recommended['version'];
            $mtime = $composer_data_recommended['time'];
            $composer_path = 'drupal/core "drupal/core-*"';
            $composer_setting = $composer_data_recommended['composer_setting'];
          }

          $selected_modules[$corename] = [
            'name' => 'drupal',
            'version' => $version,
            'install_type' => $install_type,
            'path' => $corename,
            'composer_path' => $composer_path,
            'composer_setting' => $composer_setting,
            'submodule' => FALSE,
            'time' => $mtime,
          ];
        }
        continue;
      }

      // Read data.
      $name = $module->getName();
      $path = str_replace('/' . $name . '.info.yml', '', $module->getPathname());

      // Get version.
      $version = NULL;
      if (isset($module->info['version'])) {
        $version = str_replace('x-', '', $module->info['version']);
      }

      // Get mtime.
      $mtime = NULL;
      if (isset($module->info['mtime'])) {
        $mtime = $module->info['mtime'];
      }

      // Get composer data.
      $install_type = 'manual';
      $composer_path = NULL;
      $composer_data = $this->getComposerData($name);
      $composer_setting = NULL;
      if ($composer_data['composer'] == TRUE) {
        $install_type = 'composer';
        $version = $composer_data['version'];
        $mtime = $composer_data['time'];
        $composer_path = $composer_data['composer_path'];
        $composer_setting = $composer_data['composer_setting'];
      }

      $selected_modules[$name] = array(
        'name' => $name,
        'version' => $version,
        'install_type' => $install_type,
        'path' => $path,
        'composer_path' => $composer_path,
        'composer_setting' => $composer_setting,
        'submodule' => FALSE,
        'time' => $mtime,
      );
    }

    // Search for submodules.
    foreach ($selected_modules as $module) {
      $module_name = $module['name'];

      foreach ($selected_modules as $key_search => $module_search) {
        // Ignore own entry.
        if($module_search['name'] != $module_name) {
          // Search if module exists in path.
          if (strpos($module_search['path'], '/'. $module_name . '/') !== false) {
            $selected_modules[$key_search]['submodule'] = TRUE;
          }
        }
      }

    }

    // Remove submodules.
    foreach ($selected_modules as $key => $module) {
      // Ignore submodule.
      if ($module['submodule'] == TRUE) {
        unset($selected_modules[$key]);
      }
      unset($selected_modules[$key]['submodule']);
    }

    return $selected_modules;
  }

  /**
   * Return all module updates and releases.
   *
   * @param bool $security_only
   * @param bool $force_check
   *
   * @return array
   */
  public function getUpdates($security_only = FALSE, $force_check = FALSE) {
    // Get module settings.
    $config = \Drupal::config('autoupdate.settings');
    $check_inteval = ($config->get('check_interval') == '' ? 43200 : $config->get('check_interval'));
    $email = $config->get('email');

    // Get data from state.
    $state_data = \Drupal::state()->get('autoupdate.updates', FALSE);

    // Check interval time.
    $send_notification = FALSE;
    if (isset($state_data['generate'])) {
      if ($state_data['generate'] + $check_inteval < time()) {
        $force_check = TRUE;
        $send_notification = TRUE;
      }
    }

    // Renew module data.
    if ($state_data == FALSE OR $force_check == TRUE) {
      // Get installed modules.
      $all_modules = $this->getModules(TRUE);
      foreach  ($all_modules as $key => $module) {
        // Get release.
        $all_modules[$key]['updates']['security'] = 0;
        $all_modules[$key]['updates']['insecure'] = 0;
        $all_modules[$key]['updates']['releases'] = array();
        if (strpos($module['composer_path'], 'drupal/') === 0) {
          $all_modules[$key]['updates'] = $this->getVersionHistory($module['name'], $module['version']);
        }
      }

      // Save it to state.
      $state_data = array('generate' => time(), 'data' => $all_modules);
      \Drupal::state()->setMultiple(array('autoupdate.updates' => $state_data));
    }

    // Only security updates.
    if ($security_only == TRUE) {
      foreach ($state_data['data'] as $key => $module) {
        if ($module['updates']['security'] == 1) {
          continue;
        }
        if ($module['updates']['insecure'] == 1) {
          continue;
        }

        unset($state_data['data'][$key]);
      }
    }

    // Send notification.
    if ($email != '' AND $send_notification == TRUE) {
      // Check if security updates ar available.
      $security_modules = array();
      $security_available = FALSE;
      foreach ($state_data['data'] as $key => $module) {
        if ($module['updates']['security'] == 1 OR $module['updates']['insecure'] == 1) {
          $security_available = TRUE;
          $module_text = $module['name'];

          // Check if module has fixed version.
          if (strpos($module['composer_setting'], '^') === false AND strpos($module['composer_setting'], '*') === false) {
            // Set info text.
            $module_text = $module['name'] . ' (cannot update it, you fixed the version with composer)';
          }

          $security_modules[] = $module_text;

        }
      }

      // Send email notification.
      if ($security_available == TRUE) {
        // Send mail.
        $site_mail = \Drupal::config('system.site')->get('mail_notification');
        $params['message'] = t('New security updates for modules: @modules', array('@modules' => implode(', ', $security_modules)));
        $result = \Drupal::service('plugin.manager.mail')->mail('autoupdate', 'new_security_updates', $email, 'en', $params, $site_mail);
        if ($result['result'] == true) {
          \Drupal::logger('autoupdate')->critical(t('New security updates for modules: @modules', array('@modules' => implode(', ', $security_modules))));
        }
      }
    }

    return $state_data;
  }

  /**
   * Get version history on drupal.org.
   *
   * @param $module
   * @param $module_version
   *
   * @return array
   */
  private function getVersionHistory($module, $module_version) {
    // Get module core version.
    $core_module_version = $this->drupalCore . '.x';

    $core_module_versions = explode('-', $module_version);
    if (isset($core_module_versions[0])) {
      $core_module_version = $core_module_versions[0];
    }

    $core_module_versions = explode('.', $module_version);
    if (isset($core_module_versions[0])) {
      $core_module_version = $core_module_versions[0]  . '.x';
    }

    // Get data from drupal.org.
    $url = 'https://updates.drupal.org/release-history/' . $module . '/' . $core_module_version . '?site_key=' . $this->siteKey;
    if ($module == 'drupal') {
      $url = 'https://updates.drupal.org/release-history/' . $module . '/current?site_key=' . $this->siteKey;
    }

    $history = simplexml_load_string(file_get_contents($url));
    if (!isset($history->releases)) {
      // Try with core version and last core version because some community modules dont have core affiliation.
      $url = 'https://updates.drupal.org/release-history/' . $module . '/' . $this->drupalCore . '.x' . '?site_key=' . $this->siteKey;
      $history = simplexml_load_string(file_get_contents($url));
      if (!isset($history->releases)) {
        $lastCore = (int) $this->drupalCore - 1;
        $url = 'https://updates.drupal.org/release-history/' . $module . '/' . $lastCore . '.x' . '?site_key=' . $this->siteKey;
        $history = simplexml_load_string(file_get_contents($url));
        if (!isset($history->releases)) {
          // Error no releases.
          \Drupal::logger('autoupdate')->error('Error, no version history found for module @modulename (@url)', ['@modulename' => $module, '@url' => $url]);
          return ['security' => NULL, 'insecure' => NULL, 'releases' => []];
        }
      }
    }

    // Extract local module version.
    $module_versions = explode('.', $module_version);
    $versions = '';
    if (isset($module_versions[2])) {
      $versions = $module_versions[2];
    }
    $module_versions_patch = explode('-', $versions);
    $module_major = $module_versions[0];
    $module_minor = $module_versions[1];
    $module_patch = $module_versions_patch[0];
    $module_extra = (isset($module_versions_patch[1]) ? $module_versions_patch[1] : NULL);

    // Check history data.
    $security = 0;
    $insecure = 0;
    $releases = array();

    foreach ($history->releases->release as $release) {
      // Skipp release if not compatible with our core version.
      if (isset($release->core_compatibility)) {
        if (strpos($release->core_compatibility->__toString(), '^' . $this->drupalCore) === FALSE) {
          continue;
        }
      }

      // Extract release module version.
      $release_version = str_replace($this->drupalCore . '.x-', $this->drupalCore . '.', $release->version);
      $release_versions = explode('.', $release_version);
      $release_versions_patch = explode('-', $release_versions[2]);
      $release_major = $release_versions[0];
      $release_minor = $release_versions[1];
      $release_patch = $release_versions_patch[0];
      $release_extra = (isset($release_versions_patch[1]) ? $release_versions_patch[1] : NULL);

      // Compare versions.
      if ($module_major != $release_major) {
        continue;
      }
      if ($module_minor != $release_minor) {
        continue;
      }

      // Only until installed version.
      if ($module_patch == $release_patch) {
        break;
      }

      // Terms.
      $terms = array();
      if (isset($release->terms->term)) {
        foreach ($release->terms->term as $term) {
          $terms[] = $term->value->__toString();
        }
      }
      if (isset($release->terms->value)) {
        $terms[] = $release->terms->value->__toString();
      }

      foreach ($terms as $term) {
        if ($term == 'Security update') {
          $security = 1;
        }
        if ($term == 'Insecure') {
          $insecure = 1;
        }
      }

      $version = $release_major . '.' . $release_minor . '.' . $release_patch;
      if ($release_extra != NULL) {
        $version = $release_major . '.' . $release_minor . '.' . $release_patch . '-' . $release_extra;
      }

      $releases[] = array(
        'version' => $version,
        'orig_version' => str_replace($this->drupalCore . '.x-', '', $release->version),
        'terms' => $terms,
      );
    }

    krsort($releases);

    return array('security' => $security, 'insecure' => $insecure, 'releases' => $releases);
  }

  /**
   * Get data from composer file.
   *
   * @param $module
   *
   * @return mixed|null
   */
  private function getComposerData($module) {
    $composer = array();
    $composer['composer'] = FALSE;
    $composer['composer_path'] = NULL;
    $composer['time'] = NULL;
    $composer['version'] = NULL;

    // If not exists return empty data.
    $located = FALSE;
    if (file_exists('composer.json') OR file_exists('composer.lock')) {
      $located = array(
        'composer.json' => 'composer.json',
        'composer.lock' => 'composer.lock',
      );
    }
    if ((file_exists('../composer.json') OR file_exists('../composer.lock')) AND $located == FALSE) {
      $located = array(
        'composer.json' => '../composer.json',
        'composer.lock' => '../composer.lock',
      );
    }
    if ($located == FALSE) {
      return $composer;
    }

    // Get data form composer.json
    if ($this->composerJson == NULL) {
      $this->composerJson = file_get_contents($located['composer.json']);
    }
    $composer_json = json_decode($this->composerJson);

    // Get data form composer.lock
    if ($this->composerLock == NULL) {
      $this->composerLock = file_get_contents($located['composer.lock']);
    }
    $composer_lock = json_decode($this->composerLock);
    foreach ($composer_lock->packages as $package) {
      if (strpos($package->name, '/'. $module) !== false) {
        // Set composer data.
        $composer['composer'] = TRUE;
        $composer['composer_path'] = $package->name;

        // Get time.
        $time = NULL;
        if (isset($package->time)) {
          $time = strtotime($package->time);
        }
        $composer['time'] = $time;

        // Get version form version tag.
        $version = NULL;
        if (isset($package->version)) {
          // Only add version preffix on drupal.org modules.
          if (strpos($composer['composer_path'], 'drupal/') !== false) {
            $version = str_replace('v', $this->drupalCore . '.', $package->version);
          }
          else {
            $version = str_replace('v', '', $package->version);
          }

        }

        // Get version from extra.
        if (isset($package->extra->drupal->version)) {
          $version = str_replace($this->drupalCore . '.x-', $this->drupalCore . '.', $package->extra->drupal->version);
          if (isset($package->extra->drupal->datestamp)) {
            $composer['time'] = $package->extra->drupal->datestamp;
          }
        }

        // Get version from source reference.
        if (isset($package->source->reference) AND strlen($package->source->reference) < 20) {
          $version = str_replace($this->drupalCore . '.x-', $this->drupalCore . '.', $package->source->reference);
        }

        $composer['version'] = $version;

        // Get composer json setting.
        $composer['composer_setting'] = NULL;
        if (isset($composer_json->require)) {
          foreach ($composer_json->require as $require_key => $require) {
            if ($require_key == $composer['composer_path']) {
              $composer['composer_setting'] = $require;
            }
          }
        }

        return $composer;
      }
    }

    return $composer;
  }

  /**
   * Get Drupal data (system_rebuild_module_data()).
   *
   * @return mixed
   */
  private function getModuleData() {
    $modules_cache =& drupal_static(__FUNCTION__);

    // Only rebuild once per request. $modules and $modules_cache cannot be
    // combined into one variable, because the $modules_cache variable is reset by
    // reference from system_list_reset() during the rebuild.
    if (!isset($modules_cache)) {
      $modules = \Drupal::service('extension.list.module')->reset()->getList();
      $files = array();
      ksort($modules);

      // Add status, weight, and schema version.
      $installed_modules = \Drupal::config('core.extension')
        ->get('module') ?: array();
      foreach ($modules as $name => $module) {
        $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0;
        $module->status = (int) isset($installed_modules[$name]);
        $module->schema_version = SCHEMA_UNINSTALLED;
        $files[$name] = $module
          ->getPathname();
      }
      $modules = \Drupal::moduleHandler()
        ->buildModuleDependencies($modules);
      $modules_cache = $modules;

      // Store filenames to allow drupal_get_filename() to retrieve them without
      // having to rebuild or scan the filesystem.
      \Drupal::state()
        ->set('system.module.files', $files);

      // Clear the module info cache.
      \Drupal::cache()
        ->delete('system.module.info');
      drupal_static_reset('system_get_info');
    }
    return $modules_cache;
  }
}

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

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