Pathologic was missing because of a .git folder inside.
[yaffs-website] / web / modules / contrib / hacked / src / hackedProject.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\hacked\hackedProject.
6  */
7
8 namespace Drupal\hacked;
9
10 use Drupal\Core\StringTranslation\StringTranslationTrait;
11
12 /**
13  * Encapsulates a Hacked! project.
14  *
15  * This class should handle all the complexity for you, and so you should be able to do:
16  * <code>
17  * $project = hackedProject('context');
18  * $project->compute_differences();
19  * </code>
20  *
21  * Which is quite nice I think.
22  */
23 class hackedProject {
24   use StringTranslationTrait;
25
26   var $name = '';
27
28   var $project_info = array();
29
30   var $remote_files_downloader;
31
32   /* @var hackedFileGroup $remote_files */
33   var $remote_files;
34
35   /* @var hackedFileGroup $local_files */
36   var $local_files;
37
38   var $project_type = '';
39   var $existing_version = '';
40
41   var $result = array();
42
43   var $project_identified = FALSE;
44   var $remote_downloaded = FALSE;
45   var $remote_hashed = FALSE;
46   var $local_hashed = FALSE;
47
48   /**
49    * Constructor.
50    */
51   function __construct($name) {
52     $this->name = $name;
53     $this->remote_files_downloader = new hackedProjectWebFilesDownloader($this);
54   }
55
56   /**
57    * Get the Human readable title of this project.
58    */
59   function title() {
60     $this->identify_project();
61     return isset($this->project_info['title']) ? $this->project_info['title'] : $this->name;
62   }
63
64   /**
65    * Identify the project from the name we've been created with.
66    *
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.
70    */
71   function identify_project() {
72     // Only do this once, no matter how many times we're called.
73     if (!empty($this->project_identified)) {
74       return;
75     }
76
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')
82       ->getAll();
83
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();
89         }
90         if (isset($releases[$key]['releases']) && is_array($releases[$key]['releases'])) {
91           $this->project_info['releases'] += $releases[$key]['releases'];
92         }
93
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
98
99         $this->project_identified = TRUE;
100         $this->existing_version = $this->project_info['existing_version'];
101         $this->project_type = $this->project_info['project_type'];
102         break;
103       }
104     }
105
106     // Logging.
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());
110     }
111   }
112
113   /**
114    * Downloads the remote project to be hashed later.
115    */
116   function download_remote_project() {
117     // Only do this once, no matter how many times we're called.
118     if (!empty($this->remote_downloaded)) {
119       return;
120     }
121
122     $this->identify_project();
123     $this->remote_downloaded = (bool) $this->remote_files_downloader->download();
124
125     // Logging.
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());
129     }
130   }
131
132   /**
133    * Hashes the remote project downloaded earlier.
134    */
135   function hash_remote_project() {
136     // Only do this once, no matter how many times we're called.
137     if (!empty($this->remote_hashed)) {
138       return;
139     }
140
141     // Ensure that the remote project has actually been downloaded.
142     $this->download_remote_project();
143
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();
148
149     $this->remote_hashed = !empty($this->remote_files->files);
150
151     // Logging.
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());
155     }
156   }
157
158   /**
159    * Locate the base directory of the local project.
160    */
161   function locate_local_project() {
162     // we need a remote project to do this :(
163     $this->hash_remote_project();
164
165     // Do we have at least some modules to check for:
166     if (!is_array($this->project_info['includes']) || !count($this->project_info['includes'])) {
167       return FALSE;
168     }
169
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'];
175     }
176     else {
177       // Just use the system module to find where we've installed drupal
178       $include = 'system';
179       $include_type = 'module';
180     }
181
182     //$include = 'image_captcha';
183
184     $path = drupal_get_path($include_type, $include);
185
186     // Now we need to find the path of the info file in the downloaded package:
187     $temp = '';
188     foreach ($this->remote_files->files as $file) {
189       if (preg_match('@(^|.*/)' . $include . '.info.yml$@', $file)) {
190         $temp = $file;
191         break;
192       }
193     }
194
195     // How many '/' were in that path:
196     $slash_count = substr_count($temp, '/');
197     $back_track = str_repeat('/..', $slash_count);
198
199     return realpath($path . $back_track);
200   }
201
202   /**
203    * Hash the local version of the project.
204    */
205   function hash_local_project() {
206     // Only do this once, no matter how many times we're called.
207     if (!empty($this->local_hashed)) {
208       return;
209     }
210
211     $location = $this->locate_local_project();
212
213     $this->local_files = hackedFileGroup::fromList($location, $this->remote_files->files);
214     $this->local_files->compute_hashes();
215
216     $this->local_hashed = !empty($this->local_files->files);
217
218     // Logging.
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());
222     }
223   }
224
225   /**
226    * Compute the differences between our version and the canonical version of the project.
227    */
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();
232
233     $results = [
234       'same'          => [],
235       'different'     => [],
236       'missing'       => [],
237       'access_denied' => [],
238     ];
239
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;
244       }
245       elseif (!$this->local_files->file_exists($file)) {
246         $results['missing'][] = $file;
247       }
248       elseif (!$this->local_files->is_readable($file)) {
249         $results['access_denied'][] = $file;
250       }
251       else {
252         $results['different'][] = $file;
253       }
254     }
255
256     $this->result = $results;
257   }
258
259   /**
260    * Return a nice report, a simple overview of the status of this project.
261    */
262   function compute_report() {
263     // Ensure we know the differences.
264     $this->compute_differences();
265
266     // Do some counting
267
268     $report = [
269       'project_name' => $this->name,
270       'status'       => HACKED_STATUS_UNCHECKED,
271       'counts'       => [
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']),
276       ],
277       'title'        => $this->title(),
278     ];
279
280     // Add more details into the report result (if we can).
281     $details = array(
282       'link',
283       'name',
284       'existing_version',
285       'install_type',
286       'datestamp',
287       'project_type',
288       'includes',
289     );
290     foreach ($details as $item) {
291       if (isset($this->project_info[$item])) {
292         $report[$item] = $this->project_info[$item];
293       }
294     }
295
296
297     if ($report['counts']['access_denied'] > 0) {
298       $report['status'] = HACKED_STATUS_PERMISSION_DENIED;
299     }
300     elseif ($report['counts']['missing'] > 0) {
301       $report['status'] = HACKED_STATUS_HACKED;
302     }
303     elseif ($report['counts']['different'] > 0) {
304       $report['status'] = HACKED_STATUS_HACKED;
305     }
306     elseif ($report['counts']['same'] > 0) {
307       $report['status'] = HACKED_STATUS_UNHACKED;
308     }
309
310     return $report;
311   }
312
313   /**
314    * Return a nice detailed report.
315    */
316   function compute_details() {
317     // Ensure we know the differences.
318     $report = $this->compute_report();
319
320     $report['files'] = array();
321
322     // Add extra details about every file.
323     $states = array(
324       'access_denied' => HACKED_STATUS_PERMISSION_DENIED,
325       'missing'       => HACKED_STATUS_DELETED,
326       'different'     => HACKED_STATUS_HACKED,
327       'same'          => HACKED_STATUS_UNHACKED,
328     );
329
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);
334       }
335     }
336
337     return $report;
338   }
339
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);
344   }
345
346   function file_get_location($storage = 'local', $file) {
347     switch ($storage) {
348       case 'remote':
349         $this->download_remote_project();
350         return $this->remote_files->file_get_location($file);
351       case 'local':
352         $this->hash_local_project();
353         return $this->local_files->file_get_location($file);
354     }
355     return FALSE;
356   }
357
358 }