Completed
Push — master ( c60cf3...36f595 )
by Sebastien
07:49
created

HackedProject::identifyProject()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 20
rs 9.4286
cc 3
eloc 13
nc 3
nop 0
1
<?php
2
3
namespace Cerbere\Model\Hacked;
4
5
use Cerbere\Model\Project;
6
7
/**
8
 * Encapsulates a Hacked! project.
9
 *
10
 * This class should handle all the complexity for you, and so you should be able to do:
11
 * <code>
12
 * $project = hackedProject('context');
13
 * $project->compute_differences();
14
 * </code>
15
 *
16
 * Which is quite nice I think.
17
 */
18
class HackedProject {
19
  const STATUS_UNCHECKED = 1;
20
21
  const STATUS_PERMISSION_DENIED = 2;
22
23
  const STATUS_HACKED = 3;
24
25
  const STATUS_DELETED = 4;
26
27
  const STATUS_UNHACKED = 5;
28
29
  /** @var Project */
30
  var $project;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $project.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
31
32
  var $name = '';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $name.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
33
34
  var $project_info = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $project_info.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
35
36
  /** @var HackedProjectWebDownloader */
37
  var $remote_files_downloader;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $remote_files_downloader.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
38
39
  /* @var hackedFileGroup $remote_files */
40
  var $remote_files;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $remote_files.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
41
42
  /* @var hackedFileGroup $local_files */
43
  var $local_files;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $local_files.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
44
45
  var $local_folder;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $local_folder.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
46
47
  var $project_type = '';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $project_type.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
48
  var $existing_version = '';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $existing_version.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
49
50
  var $result = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $result.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
51
52
  var $project_identified = FALSE;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $project_identified.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
53
  var $remote_downloaded = FALSE;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $remote_downloaded.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
54
  var $remote_hashed = FALSE;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $remote_hashed.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
55
  var $local_hashed = FALSE;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $local_hashed.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
56
57
  /**
58
   * Constructor.
59
   * @param Project $project
60
   * @param string $local_folder
61
   */
62
  public function __construct(Project $project, $local_folder = null) {
63
    if (null === $local_folder) {
64
      $local_folder = getcwd();
65
    }
66
    $this->project = $project;
67
    $this->local_folder = $local_folder;
68
    $this->name = $project->getProject();
69
    $this->remote_files_downloader = new HackedProjectWebFilesDownloader($project);
70
  }
71
72
  /**
73
   * @return Project
74
   */
75
  public function getProject()
76
  {
77
    return $this->project;
78
  }
79
80
  /**
81
   * Get the Human readable title of this project.
82
   */
83
  public function getTitle() {
84
    $this->identifyProject();
85
86
    return isset($this->project_info['title']) ? $this->project_info['title'] : $this->name;
87
  }
88
89
  /**
90
   * Identify the project from the name we've been created with.
91
   *
92
   * We leverage the update (status) module to get the data we require about
93
   * projects. We just pull the information in, and make descisions about this
94
   * project being from CVS or not.
95
   */
96
  public function identifyProject() {
97
    // Only do this once, no matter how many times we're called.
98
    if (!empty($this->project_identified)) {
99
      return;
100
    }
101
102
    $data = (array) $this->project;
103
    $this->project_info = array();
104
105
    foreach ($data as $key => $value) {
106
      $key = str_replace('*', '', $key);
107
      $this->project_info[$key] = $value;
108
    }
109
110
    $this->project_info['releases'] = $this->project->getReleases();
111
    $this->project_identified = TRUE;
112
    $this->existing_version = $this->project->getExistingVersion();
113
    $this->project_type = 'module';
114
    $this->project_info['includes'] = array($this->name . '.module');
115
  }
116
117
  /**
118
   * Downloads the remote project to be hashed later.
119
   */
120
  public function downloadRemoteProject() {
121
    // Only do this once, no matter how many times we're called.
122
    if (!empty($this->remote_downloaded)) {
123
      return;
124
    }
125
126
    $this->identifyProject();
127
    $this->remote_downloaded = (bool) $this->remote_files_downloader->downloadFile();
128
  }
129
130
  /**
131
   * Hashes the remote project downloaded earlier.
132
   */
133 View Code Duplication
  public function hashRemoteProject() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134
    // Only do this once, no matter how many times we're called.
135
    if (!empty($this->remote_hashed)) {
136
      return;
137
    }
138
139
    // Ensure that the remote project has actually been downloaded.
140
    $this->downloadRemoteProject();
141
142
    // Set up the remote file group.
143
    $base_path = $this->remote_files_downloader->getFinalDestination();
144
    $this->remote_files = HackedFileGroup::fromDirectory($base_path);
145
    $this->remote_files->computeHashes();
146
147
    $this->remote_hashed = count($this->remote_files->getFiles()) > 0;
148
149
    // Logging.
150
    if (!$this->remote_hashed) {
151
      //throw new \Exception('Could not hash remote project: ' . $this->getTitle());
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
152
      echo 'Could not hash remote project: ' . $this->getTitle() . "\n";
153
    }
154
  }
155
156
  /**
157
   * Locate the base directory of the local project.
158
   */
159
  public function locateLocalProject() {
160
    // we need a remote project to do this :(
161
    $this->hashRemoteProject();
162
163
    $project_type = $this->project->getProjectType();
164
165
    if ($project_type == Project::TYPE_PROJECT_CORE || $project_type == Project::TYPE_PROJECT_DISTRIBUTION) {
166
      $folder = dirname(dirname($this->local_folder));
167
    } else {
168
      $folder = $this->local_folder;
169
    }
170
171
    return realpath($folder);
172
  }
173
174
  /**
175
   * Hash the local version of the project.
176
   */
177 View Code Duplication
  public function hashLocalProject() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
    // Only do this once, no matter how many times we're called.
179
    if (!empty($this->local_hashed)) {
180
      return;
181
    }
182
183
    $location = $this->locateLocalProject();
184
    $this->local_files = hackedFileGroup::fromList($location, $this->remote_files->getFiles());
185
    $this->local_files->computeHashes();
186
    $this->local_hashed = count($this->local_files->getFiles()) > 0;
187
188
    // Logging.
189
    if (!$this->local_hashed) {
190
      //throw new \Exception('Could not hash remote project: ' . $this->getTitle());
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
191
      echo 'Could not hash local project: ' . $this->getTitle() . "\n";
192
    }
193
  }
194
195
  /**
196
   * Compute the differences between our version and the canonical version of the project.
197
   */
198
  public function computeDifferences() {
199
    // Make sure we've hashed both remote and local files.
200
    $this->hashRemoteProject();
201
    $this->hashLocalProject();
202
203
    $results = array(
204
      'same' => array(),
205
      'different' => array(),
206
      'missing' => array(),
207
      'access_denied' => array(),
208
    );
209
210
    // Now compare the two file groups.
211
    foreach ($this->remote_files->getFiles() as $file) {
212
      if ($this->remote_files->getFileHash($file) == $this->local_files->getFileHash($file)) {
213
        $results['same'][] = $file;
214
      }
215
      elseif (!$this->local_files->fileExists($file)) {
216
        $results['missing'][] = $file;
217
      }
218
      elseif (!$this->local_files->isReadable($file)) {
219
        $results['access_denied'][] = $file;
220
      }
221
      else {
222
        $results['different'][] = $file;
223
      }
224
    }
225
226
    $this->result = $results;
227
  }
228
229
  /**
230
   * Return a nice report, a simple overview of the status of this project.
231
   */
232
  public function computeReport() {
233
    // Ensure we know the differences.
234
    $this->computeDifferences();
235
236
    // Do some counting
237
    $report = array(
238
      'project_name' => $this->name,
239
      'status' => self::STATUS_UNCHECKED,
240
      'counts' => array(
241
        'same' => count($this->result['same']),
242
        'different' => count($this->result['different']),
243
        'missing' => count($this->result['missing']),
244
        'access_denied' => count($this->result['access_denied']),
245
      ),
246
      'title' => $this->getTitle(),
247
    );
248
249
    // Add more details into the report result (if we can).
250
    $details = array(
251
      'link',
252
      'name',
253
      'existing_version',
254
      'install_type',
255
      'datestamp',
256
      'project_type',
257
      'includes',
258
    );
259
260
    foreach ($details as $item) {
261
      if (isset($this->project_info[$item])) {
262
        $report[$item] = $this->project_info[$item];
263
      }
264
    }
265
266
    if ($report['counts']['access_denied'] > 0) {
267
      $report['status'] = self::STATUS_PERMISSION_DENIED;
268
    }
269
    elseif ($report['counts']['missing'] > 0) {
270
      $report['status'] = self::STATUS_HACKED;
271
    }
272
    elseif ($report['counts']['different'] > 0) {
273
      $report['status'] = self::STATUS_HACKED;
274
    }
275
    elseif ($report['counts']['same'] > 0) {
276
      $report['status'] = self::STATUS_UNHACKED;
277
    }
278
279
    return $report;
280
  }
281
282
  /**
283
   * Return a nice detailed report.
284
   * @return array
285
   */
286
  public function computeDetails() {
287
    // Ensure we know the differences.
288
    $report = $this->computeReport();
289
290
    $report['files'] = array();
291
292
    // Add extra details about every file.
293
    $states = array(
294
      'access_denied' => self::STATUS_PERMISSION_DENIED,
295
      'missing' => self::STATUS_DELETED,
296
      'different' => self::STATUS_HACKED,
297
      'same' => self::STATUS_UNHACKED,
298
    );
299
300
    foreach ($states as $state => $status) {
301
      foreach ($this->result[$state] as $file) {
302
        $report['files'][$file] = $status;
303
        $report['diffable'][$file] = $this->fileIsDiffable($file);
304
      }
305
    }
306
307
    return $report;
308
  }
309
310
  /**
311
   * @param string $file
312
   *
313
   * @return bool
314
   */
315
  public function fileIsDiffable($file) {
316
    $this->hashRemoteProject();
317
    $this->hashLocalProject();
318
319
    return $this->remote_files->isNotBinary($file) && $this->local_files->isNotBinary($file);
320
  }
321
322
  /**
323
   * @param string $storage
324
   * @param string $file
325
   *
326
   * @return bool|string
327
   */
328
  public function getFileLocation($storage = 'local', $file) {
329
    switch ($storage) {
330
      case 'remote':
331
        $this->downloadRemoteProject();
332
        return $this->remote_files->getFileLocation($file);
333
      case 'local':
334
        $this->hashLocalProject();
335
        return $this->local_files->getFileLocation($file);
336
    }
337
338
    return FALSE;
339
  }
340
341
  /**
342
   * @param string $status
343
   * @return string
344
   */
345
  public static function getStatusLabel($status) {
346
    switch ($status) {
347
      case self::STATUS_PERMISSION_DENIED:
348
        return 'Permission denied';
349
      case self::STATUS_HACKED:
350
        return 'Hacked';
351
      case self::STATUS_DELETED:
352
        return 'Deleted';
353
      case self::STATUS_UNHACKED:
354
        return 'Unhacked';
355
      case self::STATUS_UNCHECKED:
356
      default:
357
        return 'Unchecked';
358
    }
359
  }
360
}
361