Completed
Push — master ( fda08e...d5e2fd )
by Sebastien
02:30
created

HackedProject::computeDetails()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 24
rs 8.9714
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
{
20
    const STATUS_UNCHECKED = 1;
21
22
    const STATUS_PERMISSION_DENIED = 2;
23
24
    const STATUS_HACKED = 3;
25
26
    const STATUS_DELETED = 4;
27
28
    const STATUS_UNHACKED = 5;
29
30
    /**
31
     * @var Project
32
     */
33
    protected $project;
34
35
    /**
36
     * @var string
37
     */
38
    protected $name = '';
39
40
    /**
41
     * @var array
42
     */
43
    protected $project_info = array();
44
45
    /**
46
     * @var HackedProjectWebDownloader
47
     */
48
    protected $remote_files_downloader;
49
50
    /**
51
     * @var HackedFileGroup
52
     */
53
    protected $remote_files;
54
55
    /**
56
     * @var HackedFileGroup
57
     */
58
    protected $local_files;
59
60
    /**
61
     * @var string
62
     */
63
    protected $local_folder;
64
65
    /**
66
     * @var string
67
     */
68
    protected $project_type = '';
69
70
    /**
71
     * @var string
72
     */
73
    protected $existing_version = '';
74
75
    /**
76
     * @var array
77
     */
78
    protected $result = array();
79
80
    /**
81
     * @var bool
82
     */
83
    protected $project_identified = false;
84
85
    /**
86
     * @var bool
87
     */
88
    protected $remote_downloaded = false;
89
90
    /**
91
     * @var bool
92
     */
93
    protected $remote_hashed = false;
94
95
    /**
96
     * @var bool
97
     */
98
    protected $local_hashed = false;
99
100
    /**
101
     * Constructor.
102
     * @param Project $project
103
     * @param string $local_folder
104
     */
105
    public function __construct(Project $project, $local_folder = null)
106
    {
107
        if (null === $local_folder) {
108
            $local_folder = getcwd();
109
        }
110
        $this->project = $project;
111
        $this->local_folder = $local_folder;
112
        $this->name = $project->getProject();
113
        $this->remote_files_downloader = new HackedProjectWebFilesDownloader($project);
114
    }
115
116
    /**
117
     * @return Project
118
     */
119
    public function getProject()
120
    {
121
        return $this->project;
122
    }
123
124
    /**
125
     * Get the Human readable title of this project.
126
     */
127
    public function getTitle()
128
    {
129
        $this->identifyProject();
130
131
        return isset($this->project_info['title']) ? $this->project_info['title'] : $this->name;
132
    }
133
134
    /**
135
     * Identify the project from the name we've been created with.
136
     *
137
     * We leverage the update (status) module to get the data we require about
138
     * projects. We just pull the information in, and make descisions about this
139
     * project being from CVS or not.
140
     */
141
    public function identifyProject()
142
    {
143
        // Only do this once, no matter how many times we're called.
144
        if (!empty($this->project_identified)) {
145
            return;
146
        }
147
148
        $data = (array) $this->project;
149
        $this->project_info = array();
150
151
        foreach ($data as $key => $value) {
152
            $key = str_replace('*', '', $key);
153
            $this->project_info[$key] = $value;
154
        }
155
156
        $this->project_info['releases'] = $this->project->getReleases();
157
        $this->project_identified = true;
158
        $this->existing_version = $this->project->getExistingVersion();
159
        $this->project_type = 'module';
160
        $this->project_info['includes'] = array($this->name . '.module');
161
    }
162
163
    /**
164
     * Downloads the remote project to be hashed later.
165
     */
166
    public function downloadRemoteProject()
167
    {
168
        // Only do this once, no matter how many times we're called.
169
        if (!empty($this->remote_downloaded)) {
170
            return;
171
        }
172
173
        $this->identifyProject();
174
        $this->remote_downloaded = (bool) $this->remote_files_downloader->downloadFile();
175
    }
176
177
    /**
178
     * Hashes the remote project downloaded earlier.
179
     */
180 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...
181
    {
182
        // Only do this once, no matter how many times we're called.
183
        if (!empty($this->remote_hashed)) {
184
            return;
185
        }
186
187
        // Ensure that the remote project has actually been downloaded.
188
        $this->downloadRemoteProject();
189
190
        // Set up the remote file group.
191
        $base_path = $this->remote_files_downloader->getFinalDestination();
192
        $this->remote_files = HackedFileGroup::createFromDirectory($base_path);
193
        $this->remote_files->computeHashes();
194
195
        $this->remote_hashed = count($this->remote_files->getFiles()) > 0;
196
197
        // Logging.
198
        if (!$this->remote_hashed) {
199
            //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...
200
            echo 'Could not hash remote project: ' . $this->getTitle() . "\n";
201
        }
202
    }
203
204
    /**
205
     * Locate the base directory of the local project.
206
     */
207
    public function locateLocalProject()
208
    {
209
        // we need a remote project to do this :(
210
        $this->hashRemoteProject();
211
212
        $project_type = $this->project->getProjectType();
213
214
        if ($project_type == Project::TYPE_PROJECT_CORE || $project_type == Project::TYPE_PROJECT_DISTRIBUTION) {
215
            $folder = dirname(dirname($this->local_folder));
216
        } else {
217
            $folder = $this->local_folder;
218
        }
219
220
        return realpath($folder);
221
    }
222
223
    /**
224
     * Hash the local version of the project.
225
     */
226 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...
227
    {
228
        // Only do this once, no matter how many times we're called.
229
        if (!empty($this->local_hashed)) {
230
            return;
231
        }
232
233
        $location = $this->locateLocalProject();
234
        $this->local_files = HackedFileGroup::createFromList($location, $this->remote_files->getFiles());
235
        $this->local_files->computeHashes();
236
        $this->local_hashed = count($this->local_files->getFiles()) > 0;
237
238
        // Logging.
239
        if (!$this->local_hashed) {
240
            //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...
241
            echo 'Could not hash local project: ' . $this->getTitle() . "\n";
242
        }
243
    }
244
245
    /**
246
     * Compute the differences between our version and the canonical version of the project.
247
     */
248
    public function computeDifferences()
249
    {
250
        // Make sure we've hashed both remote and local files.
251
        $this->hashRemoteProject();
252
        $this->hashLocalProject();
253
254
        $results = array(
255
          'same'          => array(),
256
          'different'     => array(),
257
          'missing'       => array(),
258
          'access_denied' => array(),
259
        );
260
261
        // Now compare the two file groups.
262
        foreach ($this->remote_files->getFiles() as $file) {
263
            if ($this->remote_files->getFileHash($file) == $this->local_files->getFileHash($file)) {
264
                $results['same'][] = $file;
265
            } elseif (!$this->local_files->fileExists($file)) {
266
                $results['missing'][] = $file;
267
            } elseif (!$this->local_files->isReadable($file)) {
268
                $results['access_denied'][] = $file;
269
            } else {
270
                $results['different'][] = $file;
271
            }
272
        }
273
274
        $this->result = $results;
275
    }
276
277
    /**
278
     * Return a nice report, a simple overview of the status of this project.
279
     */
280
    public function computeReport()
281
    {
282
        // Ensure we know the differences.
283
        $this->computeDifferences();
284
285
        // Do some counting
286
        $report = array(
287
          'project_name' => $this->name,
288
          'status'       => self::STATUS_UNCHECKED,
289
          'counts'       => array(
290
            'same'          => count($this->result['same']),
291
            'different'     => count($this->result['different']),
292
            'missing'       => count($this->result['missing']),
293
            'access_denied' => count($this->result['access_denied']),
294
          ),
295
          'title'        => $this->getTitle(),
296
        );
297
298
        // Add more details into the report result (if we can).
299
        $details = array(
300
          'link',
301
          'name',
302
          'existing_version',
303
          'install_type',
304
          'datestamp',
305
          'project_type',
306
          'includes',
307
        );
308
309
        foreach ($details as $item) {
310
            if (isset($this->project_info[$item])) {
311
                $report[$item] = $this->project_info[$item];
312
            }
313
        }
314
315
        if ($report['counts']['access_denied'] > 0) {
316
            $report['status'] = self::STATUS_PERMISSION_DENIED;
317
        } elseif ($report['counts']['missing'] > 0) {
318
            $report['status'] = self::STATUS_HACKED;
319
        } elseif ($report['counts']['different'] > 0) {
320
            $report['status'] = self::STATUS_HACKED;
321
        } elseif ($report['counts']['same'] > 0) {
322
            $report['status'] = self::STATUS_UNHACKED;
323
        }
324
325
        return $report;
326
    }
327
328
    /**
329
     * Return a nice detailed report.
330
     * @return array
331
     */
332
    public function computeDetails()
333
    {
334
        // Ensure we know the differences.
335
        $report = $this->computeReport();
336
337
        $report['files'] = array();
338
339
        // Add extra details about every file.
340
        $states = array(
341
          'access_denied' => self::STATUS_PERMISSION_DENIED,
342
          'missing'       => self::STATUS_DELETED,
343
          'different'     => self::STATUS_HACKED,
344
          'same'          => self::STATUS_UNHACKED,
345
        );
346
347
        foreach ($states as $state => $status) {
348
            foreach ($this->result[$state] as $file) {
349
                $report['files'][$file] = $status;
350
                $report['diffable'][$file] = $this->fileIsDiffable($file);
351
            }
352
        }
353
354
        return $report;
355
    }
356
357
    /**
358
     * @param string $file
359
     *
360
     * @return bool
361
     */
362
    public function fileIsDiffable($file)
363
    {
364
        $this->hashRemoteProject();
365
        $this->hashLocalProject();
366
367
        return $this->remote_files->isNotBinary($file) && $this->local_files->isNotBinary($file);
368
    }
369
370
    /**
371
     * @param string $storage
372
     * @param string $file
373
     *
374
     * @return bool|string
375
     */
376
    public function getFileLocation($storage = 'local', $file)
377
    {
378
        switch ($storage) {
379
            case 'remote':
380
                $this->downloadRemoteProject();
381
382
                return $this->remote_files->getFileLocation($file);
383
            case 'local':
384
                $this->hashLocalProject();
385
386
                return $this->local_files->getFileLocation($file);
387
        }
388
389
        return false;
390
    }
391
392
    /**
393
     * @param string $status
394
     * @return string
395
     */
396
    public static function getStatusLabel($status)
397
    {
398
        switch ($status) {
399
            case self::STATUS_PERMISSION_DENIED:
400
                return 'Permission denied';
401
            case self::STATUS_HACKED:
402
                return 'Hacked';
403
            case self::STATUS_DELETED:
404
                return 'Deleted';
405
            case self::STATUS_UNHACKED:
406
                return 'Unhacked';
407
            case self::STATUS_UNCHECKED:
408
            default:
409
                return 'Unchecked';
410
        }
411
    }
412
}
413