Passed
Push — master ( e9bc10...106709 )
by Nils
07:23
created

LicenseComplianceChecker::getComplianceStatus()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 7
eloc 14
c 1
b 1
f 0
nc 9
nop 1
dl 0
loc 26
rs 8.8333
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * Teampass License Compliance Checker (Memory Optimized)
6
 * Generates a comprehensive license compliance report for all dependencies
7
 * 
8
 * @author Nils Laumaillé
9
 * @license GPL-3.0
10
 */
11
12
class LicenseComplianceChecker
13
{
14
    private const GPL_COMPATIBLE_LICENSES = [
15
        'MIT', 'BSD-2-Clause', 'BSD-3-Clause', 'Apache-2.0', 'LGPL-2.1',
16
        'LGPL-3.0', 'GPL-2.0', 'GPL-3.0', 'ISC', 'Unlicense', 'CC0-1.0'
17
    ];
18
    
19
    private int $phpCount = 0;
20
    private int $jsCount = 0;
21
    private int $errorCount = 0;
22
    private int $warningCount = 0;
23
    private $reportFile;
24
    
25
    /**
26
     * Main execution method
27
     * 
28
     * @return void
29
     */
30
    public function run(): void
31
    {
32
        echo "=== Teampass License Compliance Checker ===\n\n";
33
        
34
        // Open report file for streaming write
35
        $this->reportFile = fopen(__DIR__ . '/LICENSE_COMPLIANCE_REPORT.md', 'w');
36
        
37
        $this->writeHeader();
38
        $this->processPhpDependencies();
39
        $this->processJsDependencies();
40
        $this->writeFooter();
41
        
42
        fclose($this->reportFile);
43
        
44
        echo "\n✓ Compliance report generated: licences/LICENSE_COMPLIANCE_REPORT.md\n";
45
        echo "  PHP dependencies: {$this->phpCount}\n";
46
        echo "  JS dependencies: {$this->jsCount}\n";
47
        echo "  Errors: {$this->errorCount}\n";
48
        echo "  Warnings: {$this->warningCount}\n";
49
        
50
        if ($this->errorCount > 0) {
51
            echo "\n⚠️  CRITICAL: Incompatible licenses detected!\n";
52
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
53
        }
54
    }
55
    
56
    /**
57
     * Write report header
58
     * 
59
     * @return void
60
     */
61
    private function writeHeader(): void
62
    {
63
        $header = "# Teampass License Compliance Report\n\n";
64
        $header .= "**Generated:** " . date('Y-m-d H:i:s') . "\n";
65
        $header .= "**Project License:** GNU General Public License v3.0\n\n";
66
        
67
        fwrite($this->reportFile, $header);
68
    }
69
    
70
    /**
71
     * Process PHP dependencies from composer.lock
72
     * 
73
     * @return void
74
     */
75
    private function processPhpDependencies(): void
76
    {
77
        $lockFile = __DIR__ . '/../composer.lock';
78
        
79
        if (!file_exists($lockFile)) {
80
            $this->errorCount++;
81
            fwrite($this->reportFile, "❌ **ERROR:** composer.lock not found\n\n");
82
            return;
83
        }
84
        
85
        echo "Processing PHP dependencies...\n";
86
        
87
        // Stream parse JSON to avoid loading everything in memory
88
        $content = file_get_contents($lockFile);
89
        $data = json_decode($content, true);
90
        unset($content); // Free memory
91
        
92
        if (!isset($data['packages'])) {
93
            $this->errorCount++;
94
            fwrite($this->reportFile, "❌ **ERROR:** Invalid composer.lock format\n\n");
95
            return;
96
        }
97
        
98
        fwrite($this->reportFile, "## PHP Dependencies (Composer)\n\n");
99
        fwrite($this->reportFile, "| Package | Version | License | Status |\n");
100
        fwrite($this->reportFile, "|---------|---------|---------|--------|\n");
101
        
102
        foreach ($data['packages'] as $package) {
103
            $this->phpCount++;
104
            $this->writePhpDependency($package);
105
            
106
            // Progress indicator
107
            if ($this->phpCount % 20 === 0) {
108
                echo "  Processed {$this->phpCount} packages...\n";
109
            }
110
        }
111
        
112
        fwrite($this->reportFile, "\n");
113
        echo "✓ Processed {$this->phpCount} PHP dependencies\n";
114
    }
115
    
116
    /**
117
     * Write single PHP dependency to report
118
     * 
119
     * @param array $package Package data from composer.lock
120
     * @return void
121
     */
122
    private function writePhpDependency(array $package): void
123
    {
124
        $name = $package['name'];
125
        $version = $package['version'];
126
        $licenses = $package['license'] ?? ['Unknown'];
127
        $licenseStr = implode(', ', $licenses);
128
        
129
        $status = $this->getComplianceStatus($licenses);
130
        
131
        $line = "| {$name} | {$version} | {$licenseStr} | {$status} |\n";
132
        fwrite($this->reportFile, $line);
133
    }
134
    
135
    /**
136
     * Process JavaScript dependencies
137
     * 
138
     * @return void
139
     */
140
    private function processJsDependencies(): void
141
    {
142
        $jsFile = __DIR__ . '/javascript-dependencies.json';
143
        
144
        fwrite($this->reportFile, "## JavaScript/CSS Dependencies\n\n");
145
        
146
        if (!file_exists($jsFile)) {
147
            $this->warningCount++;
148
            fwrite($this->reportFile, "⚠️ **WARNING:** javascript-dependencies.json not found\n\n");
149
            $this->createJsTemplate();
150
            fwrite($this->reportFile, "Template created at `licences/javascript-dependencies.json`\n\n");
151
            return;
152
        }
153
        
154
        echo "Processing JavaScript dependencies...\n";
155
        
156
        $jsData = json_decode(file_get_contents($jsFile), true);
157
        
158
        if (!isset($jsData['dependencies']) || empty($jsData['dependencies'])) {
159
            fwrite($this->reportFile, "_No JavaScript dependencies registered._\n\n");
160
            return;
161
        }
162
        
163
        fwrite($this->reportFile, "| Package | Version | License | Status |\n");
164
        fwrite($this->reportFile, "|---------|---------|---------|--------|\n");
165
        
166
        foreach ($jsData['dependencies'] as $dep) {
167
            $this->jsCount++;
168
            $this->writeJsDependency($dep);
169
        }
170
        
171
        fwrite($this->reportFile, "\n");
172
        echo "✓ Processed {$this->jsCount} JavaScript dependencies\n";
173
    }
174
    
175
    /**
176
     * Write single JavaScript dependency to report
177
     * 
178
     * @param array $dep Dependency data
179
     * @return void
180
     */
181
    private function writeJsDependency(array $dep): void
182
    {
183
        $name = $dep['name'] ?? 'Unknown';
184
        $version = $dep['version'] ?? 'Unknown';
185
        $licenses = $dep['licenses'] ?? ['Unknown'];
186
        $licenseStr = implode(', ', $licenses);
187
        
188
        $status = $this->getComplianceStatus($licenses);
189
        
190
        $line = "| {$name} | {$version} | {$licenseStr} | {$status} |\n";
191
        fwrite($this->reportFile, $line);
192
    }
193
    
194
    /**
195
     * Get compliance status for licenses
196
     * 
197
     * @param array $licenses Array of license identifiers
198
     * @return string Status string with emoji
199
     */
200
    private function getComplianceStatus(array $licenses): string
201
    {
202
        if (empty($licenses) || in_array('Unknown', $licenses)) {
203
            $this->warningCount++;
204
            return '⚠️ Unknown';
205
        }
206
        
207
        $compatible = false;
208
        foreach ($licenses as $license) {
209
            $normalized = trim($license);
210
            
211
            // Check if compatible
212
            foreach (self::GPL_COMPATIBLE_LICENSES as $compat) {
213
                if (stripos($normalized, $compat) !== false) {
214
                    $compatible = true;
215
                    break 2;
216
                }
217
            }
218
        }
219
        
220
        if (!$compatible) {
221
            $this->warningCount++;
222
            return '⚠️ Review';
223
        }
224
        
225
        return '✅ Compatible';
226
    }
227
    
228
    /**
229
     * Write report footer
230
     * 
231
     * @return void
232
     */
233
    private function writeFooter(): void
234
    {
235
        $footer = "## Summary\n\n";
236
        $footer .= "- **Total Dependencies:** " . ($this->phpCount + $this->jsCount) . "\n";
237
        $footer .= "- **PHP Dependencies:** {$this->phpCount}\n";
238
        $footer .= "- **JavaScript Dependencies:** {$this->jsCount}\n";
239
        $footer .= "- **Errors:** {$this->errorCount}\n";
240
        $footer .= "- **Warnings:** {$this->warningCount}\n\n";
241
        
242
        if ($this->errorCount === 0 && $this->warningCount === 0) {
243
            $footer .= "✅ **Status:** All dependencies are GPL-3.0 compatible\n\n";
244
        } elseif ($this->errorCount > 0) {
245
            $footer .= "❌ **Status:** CRITICAL - Issues detected\n\n";
246
        } else {
247
            $footer .= "⚠️ **Status:** Some licenses require manual review\n\n";
248
        }
249
        
250
        $footer .= "## GPL-3.0 Compatible Licenses\n\n";
251
        foreach (self::GPL_COMPATIBLE_LICENSES as $license) {
252
            $footer .= "- {$license}\n";
253
        }
254
        $footer .= "\n";
255
        
256
        $footer .= "## Maintenance\n\n";
257
        $footer .= "**Update JavaScript dependencies:**\n";
258
        $footer .= "Edit `licences/javascript-dependencies.json`\n\n";
259
        $footer .= "**Run compliance check:**\n";
260
        $footer .= "```bash\n";
261
        $footer .= "php licences/compliance-checker.php\n";
262
        $footer .= "```\n\n";
263
        
264
        $footer .= "---\n\n";
265
        $footer .= "*Auto-generated report - Last updated: " . date('Y-m-d H:i:s') . "*\n";
266
        
267
        fwrite($this->reportFile, $footer);
268
    }
269
    
270
    /**
271
     * Create JavaScript dependencies template
272
     * 
273
     * @return void
274
     */
275
    private function createJsTemplate(): void
276
    {
277
        $template = [
278
            '_comment' => 'Manually maintain this file with JavaScript/CSS dependencies',
279
            'last_updated' => date('Y-m-d'),
280
            'dependencies' => [
281
                [
282
                    'name' => 'jQuery',
283
                    'version' => '3.x',
284
                    'licenses' => ['MIT'],
285
                    'homepage' => 'https://jquery.org',
286
                    'type' => 'JavaScript'
287
                ],
288
                [
289
                    'name' => 'AdminLTE',
290
                    'version' => '3.x',
291
                    'licenses' => ['MIT'],
292
                    'homepage' => 'https://adminlte.io',
293
                    'type' => 'CSS/JavaScript'
294
                ]
295
            ]
296
        ];
297
        
298
        file_put_contents(
299
            __DIR__ . '/javascript-dependencies.json',
300
            json_encode($template, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
301
        );
302
    }
303
}
304
305
// Execute
306
$checker = new LicenseComplianceChecker();
307
$checker->run();