HackedProject::hashLocalProject()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 17
Ratio 100 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 0
dl 17
loc 17
rs 9.4285
c 0
b 0
f 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: ' . strip_tags($this->getTitle()));
200
        }
201
    }
202
203
    /**
204
     * Locate the base directory of the local project.
205
     */
206
    public function locateLocalProject()
207
    {
208
        // we need a remote project to do this :(
209
        $this->hashRemoteProject();
210
211
        $project_type = $this->project->getProjectType();
0 ignored issues
show
Unused Code introduced by
$project_type is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
212
213
        return realpath($this->local_folder);
214
    }
215
216
    /**
217
     * Hash the local version of the project.
218
     */
219 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...
220
    {
221
        // Only do this once, no matter how many times we're called.
222
        if (!empty($this->local_hashed)) {
223
            return;
224
        }
225
226
        $location = $this->locateLocalProject();
227
        $this->local_files = HackedFileGroup::createFromList($location, $this->remote_files->getFiles());
228
        $this->local_files->computeHashes();
229
        $this->local_hashed = count($this->local_files->getFiles()) > 0;
230
231
        // Logging.
232
        if (!$this->local_hashed) {
233
            throw new \Exception('Could not hash local project: ' . strip_tags($this->getTitle()));
234
        }
235
    }
236
237
    /**
238
     * Compute the differences between our version and the canonical version of the project.
239
     */
240
    public function computeDifferences()
241
    {
242
        // Make sure we've hashed both remote and local files.
243
        $this->hashRemoteProject();
244
        $this->hashLocalProject();
245
246
        $results = array(
247
          'same'          => array(),
248
          'different'     => array(),
249
          'missing'       => array(),
250
          'access_denied' => array(),
251
        );
252
253
        // Now compare the two file groups.
254
        foreach ($this->remote_files->getFiles() as $file) {
255
            if ($this->remote_files->getFileHash($file) == $this->local_files->getFileHash($file)) {
256
                $results['same'][] = $file;
257
            } elseif (!$this->local_files->fileExists($file)) {
258
                $results['missing'][] = $file;
259
            } elseif (!$this->local_files->isReadable($file)) {
260
                $results['access_denied'][] = $file;
261
            } else {
262
                $results['different'][] = $file;
263
            }
264
        }
265
266
        $this->result = $results;
267
    }
268
269
    /**
270
     * Return a nice report, a simple overview of the status of this project.
271
     */
272
    public function computeReport()
273
    {
274
        // Ensure we know the differences.
275
        $this->computeDifferences();
276
277
        // Do some counting
278
        $report = array(
279
          'project_name' => $this->name,
280
          'status'       => self::STATUS_UNCHECKED,
281
          'counts'       => array(
282
            'same'          => count($this->result['same']),
283
            'different'     => count($this->result['different']),
284
            'missing'       => count($this->result['missing']),
285
            'access_denied' => count($this->result['access_denied']),
286
          ),
287
          'title'        => $this->getTitle(),
288
        );
289
290
        // Add more details into the report result (if we can).
291
        $details = array(
292
          'link',
293
          'name',
294
          'existing_version',
295
          'install_type',
296
          'datestamp',
297
          'project_type',
298
          'includes',
299
        );
300
301
        foreach ($details as $item) {
302
            if (isset($this->project_info[$item])) {
303
                $report[$item] = $this->project_info[$item];
304
            }
305
        }
306
307
        if ($report['counts']['access_denied'] > 0) {
308
            $report['status'] = self::STATUS_PERMISSION_DENIED;
309
        } elseif ($report['counts']['missing'] > 0) {
310
            $report['status'] = self::STATUS_HACKED;
311
        } elseif ($report['counts']['different'] > 0) {
312
            $report['status'] = self::STATUS_HACKED;
313
        } elseif ($report['counts']['same'] > 0) {
314
            $report['status'] = self::STATUS_UNHACKED;
315
        }
316
317
        return $report;
318
    }
319
320
    /**
321
     * Return a nice detailed report.
322
     * @return array
323
     */
324
    public function computeDetails()
325
    {
326
        // Ensure we know the differences.
327
        $report = $this->computeReport();
328
329
        $report['files'] = array();
330
331
        // Add extra details about every file.
332
        $states = array(
333
          'access_denied' => self::STATUS_PERMISSION_DENIED,
334
          'missing'       => self::STATUS_DELETED,
335
          'different'     => self::STATUS_HACKED,
336
          'same'          => self::STATUS_UNHACKED,
337
        );
338
339
        foreach ($states as $state => $status) {
340
            foreach ($this->result[$state] as $file) {
341
                $report['files'][$file] = $status;
342
                $report['diffable'][$file] = $this->fileIsDiffable($file);
343
            }
344
        }
345
346
        return $report;
347
    }
348
349
    /**
350
     * @param string $file
351
     *
352
     * @return bool
353
     */
354
    public function fileIsDiffable($file)
355
    {
356
        $this->hashRemoteProject();
357
        $this->hashLocalProject();
358
359
        return $this->remote_files->isNotBinary($file) && $this->local_files->isNotBinary($file);
360
    }
361
362
    /**
363
     * @param string $storage
364
     * @param string $file
365
     *
366
     * @return string|false
367
     */
368
    public function getFileLocation($storage = 'local', $file)
369
    {
370
        switch ($storage) {
371
            case 'remote':
372
                $this->downloadRemoteProject();
373
374
                return $this->remote_files->getFileLocation($file);
375
            case 'local':
376
                $this->hashLocalProject();
377
378
                return $this->local_files->getFileLocation($file);
379
        }
380
381
        return false;
382
    }
383
384
    /**
385
     * @param string $status
386
     * @return string
387
     */
388
    public static function getStatusLabel($status)
389
    {
390
        switch ($status) {
391
            case self::STATUS_PERMISSION_DENIED:
392
                return 'Permission denied';
393
            case self::STATUS_HACKED:
394
                return 'Hacked';
395
            case self::STATUS_DELETED:
396
                return 'Deleted';
397
            case self::STATUS_UNHACKED:
398
                return 'Unhacked';
399
            case self::STATUS_UNCHECKED:
400
            default:
401
                return 'Unchecked';
402
        }
403
    }
404
}
405