5 * Contains \Drupal\hacked\hackedProject.
8 namespace Drupal\hacked;
10 use Drupal\Core\StringTranslation\StringTranslationTrait;
13 * Encapsulates a Hacked! project.
15 * This class should handle all the complexity for you, and so you should be able to do:
17 * $project = hackedProject('context');
18 * $project->compute_differences();
21 * Which is quite nice I think.
24 use StringTranslationTrait;
28 var $project_info = array();
30 var $remote_files_downloader;
32 /* @var hackedFileGroup $remote_files */
35 /* @var hackedFileGroup $local_files */
38 var $project_type = '';
39 var $existing_version = '';
41 var $result = array();
43 var $project_identified = FALSE;
44 var $remote_downloaded = FALSE;
45 var $remote_hashed = FALSE;
46 var $local_hashed = FALSE;
51 function __construct($name) {
53 $this->remote_files_downloader = new hackedProjectWebFilesDownloader($this);
57 * Get the Human readable title of this project.
60 $this->identify_project();
61 return isset($this->project_info['title']) ? $this->project_info['title'] : $this->name;
65 * Identify the project from the name we've been created with.
67 * We leverage the update (status) module to get the data we require about
68 * projects. We just pull the information in, and make descisions about this
69 * project being from CVS or not.
71 function identify_project() {
72 // Only do this once, no matter how many times we're called.
73 if (!empty($this->project_identified)) {
77 // Fetch the required data from the update (status) module.
78 // TODO: clean this up.
79 $available = update_get_available(TRUE);
80 $data = update_calculate_project_data($available);
81 $releases = \Drupal::keyValueExpirable('update_available_releases')
84 foreach ($data as $key => $project) {
85 if ($key == $this->name) {
86 $this->project_info = $project;
87 if (!isset($this->project_info['releases']) || !is_array($this->project_info['releases'])) {
88 $this->project_info['releases'] = array();
90 if (isset($releases[$key]['releases']) && is_array($releases[$key]['releases'])) {
91 $this->project_info['releases'] += $releases[$key]['releases'];
94 // Add in the additional info that update module strips out.
95 // This is a really naff way of doing this, but update (status) module
96 // ripped out a lot of useful stuff in issue:
97 // http://drupal.org/node/669554
99 $this->project_identified = TRUE;
100 $this->existing_version = $this->project_info['existing_version'];
101 $this->project_type = $this->project_info['project_type'];
107 if (!$this->project_identified) {
108 $message = $this->t('Could not identify project: @name', array('@name' => $this->name));
109 \Drupal::logger('hacked')->warning($message->render());
114 * Downloads the remote project to be hashed later.
116 function download_remote_project() {
117 // Only do this once, no matter how many times we're called.
118 if (!empty($this->remote_downloaded)) {
122 $this->identify_project();
123 $this->remote_downloaded = (bool) $this->remote_files_downloader->download();
126 if (!$this->remote_downloaded) {
127 $message = $this->t('Could not download project: @title', array('@title' => $this->title()));
128 \Drupal::logger('hacked')->error($message->render());
133 * Hashes the remote project downloaded earlier.
135 function hash_remote_project() {
136 // Only do this once, no matter how many times we're called.
137 if (!empty($this->remote_hashed)) {
141 // Ensure that the remote project has actually been downloaded.
142 $this->download_remote_project();
144 // Set up the remote file group.
145 $base_path = $this->remote_files_downloader->get_final_destination();
146 $this->remote_files = hackedFileGroup::fromDirectory($base_path);
147 $this->remote_files->compute_hashes();
149 $this->remote_hashed = !empty($this->remote_files->files);
152 if (!$this->remote_hashed) {
153 $message = $this->t('Could not hash remote project: @title', array('@title' => $this->title()));
154 \Drupal::logger('hacked')->error($message->render());
159 * Locate the base directory of the local project.
161 function locate_local_project() {
162 // we need a remote project to do this :(
163 $this->hash_remote_project();
165 // Do we have at least some modules to check for:
166 if (!is_array($this->project_info['includes']) || !count($this->project_info['includes'])) {
170 // If this project is drupal it, we need to handle it specially
171 if ($this->project_type != 'core') {
172 $includes = array_keys($this->project_info['includes']);
173 $include = array_shift($includes);
174 $include_type = $this->project_info['project_type'];
177 // Just use the system module to find where we've installed drupal
179 $include_type = 'module';
182 //$include = 'image_captcha';
184 $path = drupal_get_path($include_type, $include);
186 // Now we need to find the path of the info file in the downloaded package:
188 foreach ($this->remote_files->files as $file) {
189 if (preg_match('@(^|.*/)' . $include . '.info.yml$@', $file)) {
195 // How many '/' were in that path:
196 $slash_count = substr_count($temp, '/');
197 $back_track = str_repeat('/..', $slash_count);
199 return realpath($path . $back_track);
203 * Hash the local version of the project.
205 function hash_local_project() {
206 // Only do this once, no matter how many times we're called.
207 if (!empty($this->local_hashed)) {
211 $location = $this->locate_local_project();
213 $this->local_files = hackedFileGroup::fromList($location, $this->remote_files->files);
214 $this->local_files->compute_hashes();
216 $this->local_hashed = !empty($this->local_files->files);
219 if (!$this->local_hashed) {
220 $message = $this->t('Could not hash local project: @title', ['@title' => $this->title()]);
221 \Drupal::logger('hacked')->error($message->render());
226 * Compute the differences between our version and the canonical version of the project.
228 function compute_differences() {
229 // Make sure we've hashed both remote and local files.
230 $this->hash_remote_project();
231 $this->hash_local_project();
237 'access_denied' => [],
240 // Now compare the two file groups.
241 foreach ($this->remote_files->files as $file) {
242 if ($this->remote_files->files_hashes[$file] == $this->local_files->files_hashes[$file]) {
243 $results['same'][] = $file;
245 elseif (!$this->local_files->file_exists($file)) {
246 $results['missing'][] = $file;
248 elseif (!$this->local_files->is_readable($file)) {
249 $results['access_denied'][] = $file;
252 $results['different'][] = $file;
256 $this->result = $results;
260 * Return a nice report, a simple overview of the status of this project.
262 function compute_report() {
263 // Ensure we know the differences.
264 $this->compute_differences();
269 'project_name' => $this->name,
270 'status' => HACKED_STATUS_UNCHECKED,
272 'same' => count($this->result['same']),
273 'different' => count($this->result['different']),
274 'missing' => count($this->result['missing']),
275 'access_denied' => count($this->result['access_denied']),
277 'title' => $this->title(),
280 // Add more details into the report result (if we can).
290 foreach ($details as $item) {
291 if (isset($this->project_info[$item])) {
292 $report[$item] = $this->project_info[$item];
297 if ($report['counts']['access_denied'] > 0) {
298 $report['status'] = HACKED_STATUS_PERMISSION_DENIED;
300 elseif ($report['counts']['missing'] > 0) {
301 $report['status'] = HACKED_STATUS_HACKED;
303 elseif ($report['counts']['different'] > 0) {
304 $report['status'] = HACKED_STATUS_HACKED;
306 elseif ($report['counts']['same'] > 0) {
307 $report['status'] = HACKED_STATUS_UNHACKED;
314 * Return a nice detailed report.
316 function compute_details() {
317 // Ensure we know the differences.
318 $report = $this->compute_report();
320 $report['files'] = array();
322 // Add extra details about every file.
324 'access_denied' => HACKED_STATUS_PERMISSION_DENIED,
325 'missing' => HACKED_STATUS_DELETED,
326 'different' => HACKED_STATUS_HACKED,
327 'same' => HACKED_STATUS_UNHACKED,
330 foreach ($states as $state => $status) {
331 foreach ($this->result[$state] as $file) {
332 $report['files'][$file] = $status;
333 $report['diffable'][$file] = $this->file_is_diffable($file);
340 function file_is_diffable($file) {
341 $this->hash_remote_project();
342 $this->hash_local_project();
343 return $this->remote_files->is_not_binary($file) && $this->local_files->is_not_binary($file);
346 function file_get_location($storage = 'local', $file) {
349 $this->download_remote_project();
350 return $this->remote_files->file_get_location($file);
352 $this->hash_local_project();
353 return $this->local_files->file_get_location($file);