You are here

minisite.install in Mini site 8

Same filename and directory in other branches
  1. 7 minisite.install

Contains install and update functions for Minisite.

File

minisite.install
View source
<?php

/**
 * @file
 * Contains install and update functions for Minisite.
 */
use Drupal\Core\Database\Database;
use Drupal\Core\File\FileSystemInterface;
use Drupal\minisite\Minisite;
use Symfony\Component\HttpFoundation\Request;
use Drupal\minisite\LegacyWrapper;

/**
 * Implements hook_uninstall().
 */
function minisite_uninstall() {

  // Remove the minisite directory and generated files.
  if (file_exists(Minisite::getCommonArchiveDir())) {
    \Drupal::service('file_system')
      ->deleteRecursive(Minisite::getCommonArchiveDir());
  }
  if (file_exists(Minisite::getCommonAssetDir())) {
    \Drupal::service('file_system')
      ->deleteRecursive(Minisite::getCommonAssetDir());
  }
}

/**
 * Implements hook_requirements().
 */
function minisite_requirements($phase) {
  if ($phase != 'runtime') {
    return [];
  }
  $requirements = [];
  if ($phase == 'runtime') {
    $path = Minisite::getCommonArchiveDir();
    $requirements['minisite_archive'] = [
      'title' => t('Minisite archive files upload directory'),
      'severity' => REQUIREMENT_OK,
      'value' => t('Exists (%path)', [
        '%path' => $path,
      ]),
    ];
    if (!\Drupal::service('file_system')
      ->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
      $requirements['minisite_archive']['description'] = t('The Minisite archive upload directory %path could not be created due to a misconfiguration of files directory. Please ensure that the files directory is correctly configured and that the webserver has permission to create directories.', [
        '%path' => LegacyWrapper::getTarget($path),
      ]);
      $requirements['minisite_archive']['severity'] = REQUIREMENT_ERROR;
      $requirements['minisite_archive']['value'] = t('Unable to create');
    }
    $path = Minisite::getCommonAssetDir();
    $requirements['minisite_asset'] = [
      'title' => t('Minisite asset files directory'),
      'severity' => REQUIREMENT_OK,
      'value' => t('Exists (%path)', [
        '%path' => $path,
      ]),
    ];
    if (!\Drupal::service('file_system')
      ->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
      $requirements['minisite_asset']['description'] = t('The Minisite asset files directory %path could not be created due to a misconfiguration of files directory. Please ensure that the files directory is correctly configured and that the webserver has permission to create directories.', [
        '%path' => LegacyWrapper::getTarget($path),
      ]);
      $requirements['minisite_asset']['severity'] = REQUIREMENT_ERROR;
      $requirements['minisite_asset']['value'] = t('Unable to create');
    }
    $requirements['minisite_archiver'] = [
      'title' => t('Minisite archiver library'),
      'severity' => REQUIREMENT_OK,
      'value' => t('Present'),
    ];
    if (!class_exists(\ZipArchive::class)) {
      $requirements['minisite_archiver']['description'] = t('ZipArchive is required to extract Minisite assets from uploaded archives. Please install php-zip extension.');
      $requirements['minisite_asset']['severity'] = REQUIREMENT_ERROR;
      $requirements['minisite_asset']['value'] = t('Absent');
    }
  }
  return $requirements;
}

/**
 * Implements hook_schema().
 */
function minisite_schema() {
  $schema['minisite_asset'] = [
    'description' => 'Asset information for minisite.',
    'fields' => [
      'id' => [
        'description' => 'The primary identifier for a minisite asset',
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ],
      'entity_type' => [
        'description' => 'The entity type of that entity.',
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
        'default' => '',
      ],
      'entity_bundle' => [
        'description' => 'The type of this entity.',
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
        'default' => '',
      ],
      'entity_id' => [
        'description' => 'The entity id this data is attached to',
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
      ],
      'entity_language' => [
        'description' => 'The {language}.langcode of this entity.',
        'type' => 'varchar_ascii',
        'length' => 12,
        'not null' => TRUE,
        'default' => '',
      ],
      'field_name' => [
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
        'default' => '',
      ],
      'source' => [
        'description' => 'The URI of the source asset file.',
        'type' => 'varchar',
        'length' => 2048,
        'not null' => TRUE,
        'default' => '',
      ],
      'alias' => [
        'description' => 'The alias for the asset path.',
        'type' => 'varchar',
        'length' => 2048,
        'not null' => FALSE,
        'default' => '',
      ],
      'filemime' => [
        'description' => 'The file MIME of the asset.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => FALSE,
        'default' => NULL,
      ],
      'filesize' => [
        'description' => 'The file size of the asset.',
        'type' => 'int',
        'size' => 'big',
        'length' => 20,
        'unsigned' => TRUE,
        'not null' => FALSE,
        'default' => NULL,
      ],
    ],
    'primary key' => [
      'id',
    ],
    'unique keys' => [
      'id' => [
        'id',
        'entity_type',
        'entity_bundle',
        'entity_id',
        'entity_language',
        'field_name',
      ],
    ],
    'indexes' => [
      'source' => [
        [
          'source',
          170,
        ],
      ],
      'alias' => [
        [
          'alias',
          170,
        ],
      ],
    ],
  ];
  return $schema;
}

/**
 * Adds 'filemime' and 'filesize' and removes `entity_rid` columns.
 */
function minisite_update_8001() {
  $schema = Database::getConnection()
    ->schema();
  $spec = [
    'description' => 'The file MIME of the asset.',
    'type' => 'varchar',
    'length' => 255,
    'not null' => FALSE,
    'default' => NULL,
  ];
  $schema
    ->addField('minisite_asset', 'filemime', $spec);
  $spec = [
    'description' => 'The file size of the asset.',
    'type' => 'int',
    'size' => 'big',
    'length' => 20,
    'unsigned' => TRUE,
    'not null' => FALSE,
    'default' => NULL,
  ];
  $schema
    ->addField('minisite_asset', 'filesize', $spec);
  $schema
    ->dropField('minisite_asset', 'entity_rid');
}

/**
 * Re-saves all Minisite instances to populate the database with asset links.
 */
function minisite_update_8002(&$sandbox) {

  // Number of Minisite instances to process in a single batch.
  // trying to keep this as low as possible as some minisites may have
  // a lot of assets that will take time and resources to process.
  $batch_size = getenv('MINISITE_UPDATE_BATCH_SIZE') ?: 1;
  if (!isset($sandbox['info'])) {
    module_load_include('module', 'minisite');
    $info = minisite_get_info_all(TRUE);
    if (empty($info)) {
      $sandbox['#finished'] = 1;
      return t('There are no Minisite fields with content in any available entity types.');
    }
    $sandbox['info'] = $info;
    $sandbox['max'] = count($info);
    $sandbox['progress'] = 0;
    $sandbox['updated'] = 0;
  }
  $current_batch_info = array_slice($sandbox['info'], $sandbox['progress'], $batch_size);
  $stage_file_proxy_is_enabled = \Drupal::service('module_handler')
    ->moduleExists('stage_file_proxy');
  $messages = [];
  foreach ($current_batch_info as $info) {
    $sandbox['progress']++;
    list($entity_type, $field_name, $entity_id) = explode('__', $info);
    $messages[] = t('Processing Minisite for field @field_name attached to @entity_type with ID @entity_id.', [
      '@entity_type' => $entity_type,
      '@field_name' => $field_name,
      '@entity_id' => $entity_id,
    ]);
    $host_entity = \Drupal::entityTypeManager()
      ->getStorage($entity_type)
      ->load($entity_id);
    $field_item_list = $host_entity
      ->get($field_name);

    // Before proceeding with minisite instantiation, we need to check that
    // the archive file is available in this environment and do our best effort
    // to fetch files from origin location (works only on the environments with
    // stage_file_proxy enabled).

    /** @var \Drupal\file\Entity\File $archive_file */
    $archive_file = $field_item_list->entity;
    $archive_file_uri = $archive_file
      ->getFileUri();
    $archive_file_absolute_url = $archive_file
      ->createFileUrl(FALSE);
    if (!is_readable($archive_file_uri)) {
      if (!$stage_file_proxy_is_enabled) {
        $messages[] = '  ' . t('SKIPPED: Archive file is missing in this environment and stage_file_proxy module is not enabled.');
        continue;
      }
      _minisite_install_stage_file_proxy_fetch($archive_file_absolute_url);
      if (!is_readable($archive_file_uri)) {
        $messages[] = '  ' . t('SKIPPED: Unable to fetch archive file @uri.', [
          '@uri' => $archive_file_uri,
        ]);
        continue;
      }
      $messages[] = '  ' . t('Fetched archive file @uri.', [
        '@uri' => $archive_file_uri,
      ]);
    }
    $minisite = Minisite::createInstance($field_item_list);
    if ($minisite) {
      $minisite
        ->save();
      $sandbox['updated']++;
      $messages[] = '  ' . t('SUCCESS: Updated Ministe.');
    }
    else {
      $messages[] = '  ' . t('SKIPPED: Unable to process Ministe.');
    }
  }
  $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max'];
  return t('Processed @processed of @total and updated @updated Minisite instances: @messages', [
    '@total' => $sandbox['max'],
    '@processed' => $sandbox['progress'],
    '@updated' => $sandbox['updated'],
    '@messages' => PHP_EOL . implode(PHP_EOL, $messages),
  ]);
}

/**
 * Fetch file from the specified URI using stage_file_proxy fetcher.
 *
 * This is a shortened version of
 * \Drupal\stage_file_proxy\EventSubscriber\ProxySubscriber::checkFileOrigin.
 *
 * Unfortunately, it is not possible to use functionality of stage_file_proxy
 * transparently within update hooks (send request from the update hook to the
 * same server) as they can be ran via CLI, in which case it is possible that
 * the request may not reach the website (for example, if PHP runs in a Docker
 * container separate to the web-server and the internal name of the web-server
 * is not the same as site's external URI).
 *
 * It is also not possible to simply craft a stub request and pass it to the
 * stage_file_proxy's ProxySubscriber::checkFileOrigin method, since the method
 * uses 'exit' to perform a redirect (the logic is not separated from the
 * request stack handling), which will terminate the process itself.
 *
 * The only viable solution is to use stage_file_proxy's fetcher manager with
 * additionally added logic taken from ProxySubscriber::checkFileOrigin method.
 *
 * @param string $url
 *   Absolute URL to the file to download. Absolute is required in order to
 *   check that this is not an origin server.
 *
 * @return bool
 *   TRUE if file was downloaded, FALSE otherwise.
 *
 * @see \Drupal\stage_file_proxy\EventSubscriber\ProxySubscriber::checkFileOrigin
 */
function _minisite_install_stage_file_proxy_fetch($url) {
  $config = \Drupal::configFactory()
    ->get('stage_file_proxy.settings');

  // Get the origin server.
  $server = $config
    ->get('origin');

  // Quit if no origin given.
  if (!$server) {
    return FALSE;
  }
  $request = Request::create($url);

  // Quit if we are the origin, ignore http(s).
  $current_host = $request
    ->getHost();
  if (preg_replace('#^[a-z]*://#u', '', $server) === $current_host) {
    return FALSE;
  }
  $fetch_manager = \Drupal::getContainer()
    ->get('stage_file_proxy.fetch_manager');
  $file_dir = $fetch_manager
    ->filePublicPath();
  $request_path = $request
    ->getPathInfo();
  $request_path = mb_substr($request_path, 1);
  if (strpos($request_path, '' . $file_dir) !== 0) {
    return FALSE;
  }

  // Note if the origin server files location is different. This
  // must be the exact path for the remote site's public file
  // system path, and defaults to the local public file system path.
  $remote_file_dir = trim($config
    ->get('origin_dir'));
  if (!$remote_file_dir) {
    $remote_file_dir = $file_dir;
  }
  $request_path = rawurldecode($request_path);
  $relative_path = mb_substr($request_path, mb_strlen($file_dir) + 1);
  $options = [
    'verify' => $config
      ->get('verify'),
  ];
  return $fetch_manager
    ->fetch($server, $remote_file_dir, $relative_path, $options);
}

Functions

Namesort descending Description
minisite_requirements Implements hook_requirements().
minisite_schema Implements hook_schema().
minisite_uninstall Implements hook_uninstall().
minisite_update_8001 Adds 'filemime' and 'filesize' and removes `entity_rid` columns.
minisite_update_8002 Re-saves all Minisite instances to populate the database with asset links.
_minisite_install_stage_file_proxy_fetch Fetch file from the specified URI using stage_file_proxy fetcher.