Skip to content

Commit 4b66c55

Browse files
author
felix.manrique
committed
hotflix: corrected file
1 parent ac37808 commit 4b66c55

1 file changed

Lines changed: 40 additions & 88 deletions

File tree

classes/scanners/structural_scanner.php

Lines changed: 40 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424

2525
namespace local_mrca\scanners;
2626

27+
defined('MOODLE_INTERNAL') || die();
28+
2729
/**
28-
* Analyzes plugin file organization, detects deprecated function usage,
29-
* validates version metadata, and checks coding standards compliance.
30+
* Analyzes plugin file organization and quality.
3031
*
3132
* @package local_mrca
3233
* @copyright 2026 Mr Jacket
3334
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35+
* @SuppressWarnings(PHPMD.CamelCaseClassName)
36+
* @SuppressWarnings(PHPMD.CamelCaseMethodName)
3437
*/
3538
class structural_scanner {
3639
/** @var int Score for missing version.php. */
@@ -92,7 +95,7 @@ class structural_scanner {
9295
];
9396

9497
/**
95-
* Scans a plugin's structural integrity with deep analysis.
98+
* Scans a plugin's structural integrity.
9699
*
97100
* @param string $component Component name.
98101
* @return array Structural findings with score.
@@ -120,19 +123,13 @@ public function scan(string $component): array {
120123
}
121124

122125
$score = 0;
123-
124-
// 1. File structure checks.
125126
$this->check_base_structure($dir, $findings, $score);
126127

127-
// 2. Check version.php for maturity and legacy cron.
128128
if ($findings['has_version_file']) {
129129
$this->check_version_metadata($dir, $findings, $score);
130130
}
131131

132-
// 3. Scan PHP source files for deprecated/unsafe function calls.
133132
$this->scan_php_sources($dir, $findings, $score);
134-
135-
// Cap total structural score at 65.
136133
$findings['structural_score'] = min($score, 65);
137134

138135
return $findings;
@@ -142,8 +139,8 @@ public function scan(string $component): array {
142139
* Checks basic file structure of the plugin.
143140
*
144141
* @param string $dir Plugin directory.
145-
* @param array &$findings Findings array passed by reference.
146-
* @param int &$score Risk score passed by reference.
142+
* @param array $findings Findings array.
143+
* @param int $score Risk score.
147144
* @return void
148145
*/
149146
private function check_base_structure(string $dir, array &$findings, int &$score): void {
@@ -177,22 +174,20 @@ private function check_base_structure(string $dir, array &$findings, int &$score
177174
* Validates metadata inside version.php.
178175
*
179176
* @param string $dir Plugin directory.
180-
* @param array &$findings Findings array passed by reference.
181-
* @param int &$score Risk score passed by reference.
177+
* @param array $findings Findings array.
178+
* @param int $score Risk score.
182179
* @return void
183180
*/
184181
private function check_version_metadata(string $dir, array &$findings, int &$score): void {
185182
$versioncontent = @file_get_contents($dir . '/version.php');
186183
if ($versioncontent !== false) {
187-
// Maturity check.
188184
if (strpos($versioncontent, '$plugin->maturity') !== false) {
189185
$findings['has_maturity'] = true;
190186
} else {
191187
$score += self::SCORE_NO_MATURITY;
192188
$findings['issues'][] = get_string('structural_no_maturity', 'local_mrca');
193189
}
194190

195-
// Legacy cron check.
196191
if (strpos($versioncontent, '$plugin->cron') !== false) {
197192
$findings['uses_legacy_cron'] = true;
198193
$score += self::SCORE_LEGACY_CRON;
@@ -205,8 +200,8 @@ private function check_version_metadata(string $dir, array &$findings, int &$sco
205200
* Scans PHP files for deprecated and unsafe function calls.
206201
*
207202
* @param string $dir Plugin directory.
208-
* @param array &$findings Findings array passed by reference.
209-
* @param int &$score Risk score passed by reference.
203+
* @param array $findings Findings array.
204+
* @param int $score Risk score.
210205
* @return void
211206
*/
212207
private function scan_php_sources(string $dir, array &$findings, int &$score): void {
@@ -220,34 +215,35 @@ private function scan_php_sources(string $dir, array &$findings, int &$score): v
220215
}
221216

222217
$relative = str_replace($dir . '/', '', $file);
218+
$deprecatedscore += $this->scan_content($content, $relative, $findings);
219+
}
223220

224-
// Check deprecated Moodle functions.
225-
foreach (self::DEPRECATED_FUNCTIONS as $func => $reason) {
226-
if ($this->contains_function_call($content, $func)) {
227-
$findings['deprecated_calls'][] = [
228-
'file' => $relative,
229-
'function' => $func,
230-
'reason' => $reason,
231-
];
232-
$deprecatedscore += self::SCORE_DEPRECATED_CALL;
233-
}
234-
}
221+
$score += min($deprecatedscore, self::DEPRECATED_CALLS_CAP);
222+
}
235223

236-
// Check unsafe PHP functions.
237-
foreach (self::UNSAFE_PHP_FUNCTIONS as $func => $reason) {
238-
if ($this->contains_function_call($content, $func)) {
239-
$findings['unsafe_calls'][] = [
240-
'file' => $relative,
241-
'function' => $func,
242-
'reason' => $reason,
243-
];
244-
$deprecatedscore += self::SCORE_DEPRECATED_CALL;
245-
}
224+
/**
225+
* Scans content of a single file.
226+
*
227+
* @param string $content
228+
* @param string $relative
229+
* @param array $findings
230+
* @return int
231+
*/
232+
private function scan_content(string $content, string $relative, array &$findings): int {
233+
$score = 0;
234+
foreach (self::DEPRECATED_FUNCTIONS as $func => $reason) {
235+
if ($this->contains_function_call($content, $func)) {
236+
$findings['deprecated_calls'][] = ['file' => $relative, 'function' => $func, 'reason' => $reason];
237+
$score += self::SCORE_DEPRECATED_CALL;
246238
}
247239
}
248-
249-
// Cap deprecated calls score.
250-
$score += min($deprecatedscore, self::DEPRECATED_CALLS_CAP);
240+
foreach (self::UNSAFE_PHP_FUNCTIONS as $func => $reason) {
241+
if ($this->contains_function_call($content, $func)) {
242+
$findings['unsafe_calls'][] = ['file' => $relative, 'function' => $func, 'reason' => $reason];
243+
$score += self::SCORE_DEPRECATED_CALL;
244+
}
245+
}
246+
return $score;
251247
}
252248

253249
/**
@@ -259,8 +255,6 @@ private function scan_php_sources(string $dir, array &$findings, int &$score): v
259255
*/
260256
private function get_php_files(string $dir, int $depth = 0): array {
261257
$files = [];
262-
263-
// Limit recursion to avoid scanning massive plugin trees.
264258
if ($depth > 5) {
265259
return $files;
266260
}
@@ -271,15 +265,11 @@ private function get_php_files(string $dir, int $depth = 0): array {
271265
}
272266

273267
foreach ($entries as $entry) {
274-
if (
275-
$entry === '.' || $entry === '..' || $entry === 'vendor' ||
276-
$entry === 'node_modules' || $entry === '.git'
277-
) {
268+
if (in_array($entry, ['.', '..', 'vendor', 'node_modules', '.git'])) {
278269
continue;
279270
}
280271

281272
$path = $dir . '/' . $entry;
282-
283273
if (is_dir($path)) {
284274
$files = array_merge($files, $this->get_php_files($path, $depth + 1));
285275
} else if (pathinfo($entry, PATHINFO_EXTENSION) === 'php') {
@@ -291,55 +281,17 @@ private function get_php_files(string $dir, int $depth = 0): array {
291281
}
292282

293283
/**
294-
* Checks if a file content contains a function call (not class/method name or comment).
295-
*
296-
* Uses a simple heuristic: looks for the function name followed by '('.
297-
* Skips matches inside single-line comments or class/method definitions.
284+
* Checks if a file content contains a function call.
298285
*
299286
* @param string $content File content.
300287
* @param string $function Function name.
301288
* @return bool
302289
*/
303290
private function contains_function_call(string $content, string $function): bool {
304-
// Pattern: function name followed by opening parenthesis, not preceded by
305-
// 'function ', '->' or '::' (those would be definitions or method calls on objects).
306291
$pattern = '/(?<!\w)(?<!->)(?<!::)(?<!function\s)' . preg_quote($function, '/') . '\s*\(/';
307-
308-
// Quick pre-check to avoid regex on files that don't contain the name at all.
309292
if (strpos($content, $function) === false) {
310293
return false;
311294
}
312-
313-
// Remove comments to avoid false positives.
314-
$lines = explode("\n", $content);
315-
$inblockcomment = false;
316-
$cleancontent = '';
317-
318-
foreach ($lines as $line) {
319-
$trimmed = ltrim($line);
320-
321-
if ($inblockcomment) {
322-
if (strpos($line, '*/') !== false) {
323-
$inblockcomment = false;
324-
}
325-
continue;
326-
}
327-
328-
if (strpos($trimmed, '/*') === 0 || strpos($trimmed, '/**') === 0) {
329-
if (strpos($line, '*/') === false) {
330-
$inblockcomment = true;
331-
}
332-
continue;
333-
}
334-
335-
// Skip single-line comments.
336-
if (strpos($trimmed, '//') === 0) {
337-
continue;
338-
}
339-
340-
$cleancontent .= $line . "\n";
341-
}
342-
343-
return (bool)preg_match($pattern, $cleancontent);
295+
return (bool)preg_match($pattern, $content);
344296
}
345297
}

0 commit comments

Comments
 (0)