Version 1
[yaffs-website] / web / modules / contrib / hacked / src / hackedProject.php
diff --git a/web/modules/contrib/hacked/src/hackedProject.php b/web/modules/contrib/hacked/src/hackedProject.php
new file mode 100644 (file)
index 0000000..dcd6841
--- /dev/null
@@ -0,0 +1,358 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\hacked\hackedProject.
+ */
+
+namespace Drupal\hacked;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Encapsulates a Hacked! project.
+ *
+ * This class should handle all the complexity for you, and so you should be able to do:
+ * <code>
+ * $project = hackedProject('context');
+ * $project->compute_differences();
+ * </code>
+ *
+ * Which is quite nice I think.
+ */
+class hackedProject {
+  use StringTranslationTrait;
+
+  var $name = '';
+
+  var $project_info = array();
+
+  var $remote_files_downloader;
+
+  /* @var hackedFileGroup $remote_files */
+  var $remote_files;
+
+  /* @var hackedFileGroup $local_files */
+  var $local_files;
+
+  var $project_type = '';
+  var $existing_version = '';
+
+  var $result = array();
+
+  var $project_identified = FALSE;
+  var $remote_downloaded = FALSE;
+  var $remote_hashed = FALSE;
+  var $local_hashed = FALSE;
+
+  /**
+   * Constructor.
+   */
+  function __construct($name) {
+    $this->name = $name;
+    $this->remote_files_downloader = new hackedProjectWebFilesDownloader($this);
+  }
+
+  /**
+   * Get the Human readable title of this project.
+   */
+  function title() {
+    $this->identify_project();
+    return isset($this->project_info['title']) ? $this->project_info['title'] : $this->name;
+  }
+
+  /**
+   * Identify the project from the name we've been created with.
+   *
+   * We leverage the update (status) module to get the data we require about
+   * projects. We just pull the information in, and make descisions about this
+   * project being from CVS or not.
+   */
+  function identify_project() {
+    // Only do this once, no matter how many times we're called.
+    if (!empty($this->project_identified)) {
+      return;
+    }
+
+    // Fetch the required data from the update (status) module.
+    // TODO: clean this up.
+    $available = update_get_available(TRUE);
+    $data = update_calculate_project_data($available);
+    $releases = \Drupal::keyValueExpirable('update_available_releases')
+      ->getAll();
+
+    foreach ($data as $key => $project) {
+      if ($key == $this->name) {
+        $this->project_info = $project;
+        if (!isset($this->project_info['releases']) || !is_array($this->project_info['releases'])) {
+          $this->project_info['releases'] = array();
+        }
+        if (isset($releases[$key]['releases']) && is_array($releases[$key]['releases'])) {
+          $this->project_info['releases'] += $releases[$key]['releases'];
+        }
+
+        // Add in the additional info that update module strips out.
+        // This is a really naff way of doing this, but update (status) module
+        // ripped out a lot of useful stuff in issue:
+        // http://drupal.org/node/669554
+
+        $this->project_identified = TRUE;
+        $this->existing_version = $this->project_info['existing_version'];
+        $this->project_type = $this->project_info['project_type'];
+        break;
+      }
+    }
+
+    // Logging.
+    if (!$this->project_identified) {
+      $message = $this->t('Could not identify project: @name', array('@name' => $this->name));
+      \Drupal::logger('hacked')->warning($message->render());
+    }
+  }
+
+  /**
+   * Downloads the remote project to be hashed later.
+   */
+  function download_remote_project() {
+    // Only do this once, no matter how many times we're called.
+    if (!empty($this->remote_downloaded)) {
+      return;
+    }
+
+    $this->identify_project();
+    $this->remote_downloaded = (bool) $this->remote_files_downloader->download();
+
+    // Logging.
+    if (!$this->remote_downloaded) {
+      $message = $this->t('Could not download project: @title', array('@title' => $this->title()));
+      \Drupal::logger('hacked')->error($message->render());
+    }
+  }
+
+  /**
+   * Hashes the remote project downloaded earlier.
+   */
+  function hash_remote_project() {
+    // Only do this once, no matter how many times we're called.
+    if (!empty($this->remote_hashed)) {
+      return;
+    }
+
+    // Ensure that the remote project has actually been downloaded.
+    $this->download_remote_project();
+
+    // Set up the remote file group.
+    $base_path = $this->remote_files_downloader->get_final_destination();
+    $this->remote_files = hackedFileGroup::fromDirectory($base_path);
+    $this->remote_files->compute_hashes();
+
+    $this->remote_hashed = !empty($this->remote_files->files);
+
+    // Logging.
+    if (!$this->remote_hashed) {
+      $message = $this->t('Could not hash remote project: @title', array('@title' => $this->title()));
+      \Drupal::logger('hacked')->error($message->render());
+    }
+  }
+
+  /**
+   * Locate the base directory of the local project.
+   */
+  function locate_local_project() {
+    // we need a remote project to do this :(
+    $this->hash_remote_project();
+
+    // Do we have at least some modules to check for:
+    if (!is_array($this->project_info['includes']) || !count($this->project_info['includes'])) {
+      return FALSE;
+    }
+
+    // If this project is drupal it, we need to handle it specially
+    if ($this->project_type != 'core') {
+      $includes = array_keys($this->project_info['includes']);
+      $include = array_shift($includes);
+      $include_type = $this->project_info['project_type'];
+    }
+    else {
+      // Just use the system module to find where we've installed drupal
+      $include = 'system';
+      $include_type = 'module';
+    }
+
+    //$include = 'image_captcha';
+
+    $path = drupal_get_path($include_type, $include);
+
+    // Now we need to find the path of the info file in the downloaded package:
+    $temp = '';
+    foreach ($this->remote_files->files as $file) {
+      if (preg_match('@(^|.*/)' . $include . '.info.yml$@', $file)) {
+        $temp = $file;
+        break;
+      }
+    }
+
+    // How many '/' were in that path:
+    $slash_count = substr_count($temp, '/');
+    $back_track = str_repeat('/..', $slash_count);
+
+    return realpath($path . $back_track);
+  }
+
+  /**
+   * Hash the local version of the project.
+   */
+  function hash_local_project() {
+    // Only do this once, no matter how many times we're called.
+    if (!empty($this->local_hashed)) {
+      return;
+    }
+
+    $location = $this->locate_local_project();
+
+    $this->local_files = hackedFileGroup::fromList($location, $this->remote_files->files);
+    $this->local_files->compute_hashes();
+
+    $this->local_hashed = !empty($this->local_files->files);
+
+    // Logging.
+    if (!$this->local_hashed) {
+      $message = $this->t('Could not hash local project: @title', ['@title' => $this->title()]);
+      \Drupal::logger('hacked')->error($message->render());
+    }
+  }
+
+  /**
+   * Compute the differences between our version and the canonical version of the project.
+   */
+  function compute_differences() {
+    // Make sure we've hashed both remote and local files.
+    $this->hash_remote_project();
+    $this->hash_local_project();
+
+    $results = [
+      'same'          => [],
+      'different'     => [],
+      'missing'       => [],
+      'access_denied' => [],
+    ];
+
+    // Now compare the two file groups.
+    foreach ($this->remote_files->files as $file) {
+      if ($this->remote_files->files_hashes[$file] == $this->local_files->files_hashes[$file]) {
+        $results['same'][] = $file;
+      }
+      elseif (!$this->local_files->file_exists($file)) {
+        $results['missing'][] = $file;
+      }
+      elseif (!$this->local_files->is_readable($file)) {
+        $results['access_denied'][] = $file;
+      }
+      else {
+        $results['different'][] = $file;
+      }
+    }
+
+    $this->result = $results;
+  }
+
+  /**
+   * Return a nice report, a simple overview of the status of this project.
+   */
+  function compute_report() {
+    // Ensure we know the differences.
+    $this->compute_differences();
+
+    // Do some counting
+
+    $report = [
+      'project_name' => $this->name,
+      'status'       => HACKED_STATUS_UNCHECKED,
+      'counts'       => [
+        'same'          => count($this->result['same']),
+        'different'     => count($this->result['different']),
+        'missing'       => count($this->result['missing']),
+        'access_denied' => count($this->result['access_denied']),
+      ],
+      'title'        => $this->title(),
+    ];
+
+    // Add more details into the report result (if we can).
+    $details = array(
+      'link',
+      'name',
+      'existing_version',
+      'install_type',
+      'datestamp',
+      'project_type',
+      'includes',
+    );
+    foreach ($details as $item) {
+      if (isset($this->project_info[$item])) {
+        $report[$item] = $this->project_info[$item];
+      }
+    }
+
+
+    if ($report['counts']['access_denied'] > 0) {
+      $report['status'] = HACKED_STATUS_PERMISSION_DENIED;
+    }
+    elseif ($report['counts']['missing'] > 0) {
+      $report['status'] = HACKED_STATUS_HACKED;
+    }
+    elseif ($report['counts']['different'] > 0) {
+      $report['status'] = HACKED_STATUS_HACKED;
+    }
+    elseif ($report['counts']['same'] > 0) {
+      $report['status'] = HACKED_STATUS_UNHACKED;
+    }
+
+    return $report;
+  }
+
+  /**
+   * Return a nice detailed report.
+   */
+  function compute_details() {
+    // Ensure we know the differences.
+    $report = $this->compute_report();
+
+    $report['files'] = array();
+
+    // Add extra details about every file.
+    $states = array(
+      'access_denied' => HACKED_STATUS_PERMISSION_DENIED,
+      'missing'       => HACKED_STATUS_DELETED,
+      'different'     => HACKED_STATUS_HACKED,
+      'same'          => HACKED_STATUS_UNHACKED,
+    );
+
+    foreach ($states as $state => $status) {
+      foreach ($this->result[$state] as $file) {
+        $report['files'][$file] = $status;
+        $report['diffable'][$file] = $this->file_is_diffable($file);
+      }
+    }
+
+    return $report;
+  }
+
+  function file_is_diffable($file) {
+    $this->hash_remote_project();
+    $this->hash_local_project();
+    return $this->remote_files->is_not_binary($file) && $this->local_files->is_not_binary($file);
+  }
+
+  function file_get_location($storage = 'local', $file) {
+    switch ($storage) {
+      case 'remote':
+        $this->download_remote_project();
+        return $this->remote_files->file_get_location($file);
+      case 'local':
+        $this->hash_local_project();
+        return $this->local_files->file_get_location($file);
+    }
+    return FALSE;
+  }
+
+}
\ No newline at end of file