Skip to content

Commit 08b456b

Browse files
allanhaggettclaude
andcommitted
Add Moodle coding standards compliance and CI workflow
All PHP files now pass phpcs --standard=moodle with zero errors and warnings. Added file docblocks with @copyright/@license to all files, class and function docblocks, renamed member variables from snake_case to camelCase per Moodle naming conventions, fixed inline comment formatting, and removed unnecessary MOODLE_INTERNAL checks from autoloaded classes. Added moodle-cs.yml GitHub Action. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 91597cd commit 08b456b

File tree

20 files changed

+317
-111
lines changed

20 files changed

+317
-111
lines changed

.github/workflows/moodle-cs.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Moodle Coding Standards
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
codestyle:
11+
name: Moodle PHP CodeSniffer
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout plugin
16+
uses: actions/checkout@v4
17+
18+
- name: Setup PHP
19+
uses: shivammathur/setup-php@v2
20+
with:
21+
php-version: '8.2'
22+
tools: composer:v2
23+
24+
- name: Install dependencies
25+
run: composer install --no-interaction
26+
27+
- name: Run PHP CodeSniffer
28+
run: vendor/bin/phpcs --standard=moodle --extensions=php --ignore=vendor/,phpstan-bootstrap.php .

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,16 @@ composer install
300300
vendor/bin/phpstan analyse --memory-limit=512M
301301
```
302302

303+
### Moodle Coding Standards
304+
305+
[PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) with the [moodlehq/moodle-cs](https://github.com/moodlehq/moodle-cs) ruleset enforces Moodle's coding standards — file docblocks, naming conventions, inline comment formatting, and PHPDoc annotations.
306+
307+
**Running locally:**
308+
```bash
309+
composer install
310+
vendor/bin/phpcs --standard=moodle --extensions=php --ignore=vendor/,phpstan-bootstrap.php .
311+
```
312+
303313
## File Structure
304314

305315
```
@@ -335,6 +345,7 @@ local/githubsync/
335345
.github/workflows/
336346
semgrep.yml # Semgrep OWASP security scan
337347
phpstan.yml # PHPStan static analysis
348+
moodle-cs.yml # Moodle coding standards
338349
```
339350
340351
## License

classes/form/config_form.php

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@
2020

2121
require_once($CFG->libdir . '/formslib.php');
2222

23+
/**
24+
* Configuration form for GitHub Sync course settings.
25+
*
26+
* @package local_githubsync
27+
* @copyright 2026 Allan Haggett
28+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29+
*/
2330
class config_form extends \moodleform {
24-
31+
/**
32+
* Form definition.
33+
*/
2534
protected function definition() {
2635
$mform = $this->_form;
2736

@@ -42,20 +51,35 @@ protected function definition() {
4251
$mform->setType('branch', PARAM_TEXT);
4352
$mform->setDefault('branch', get_config('local_githubsync', 'default_branch') ?: 'main');
4453

45-
$mform->addElement('advcheckbox', 'auto_sync', get_string('auto_sync', 'local_githubsync'),
46-
get_string('auto_sync_desc', 'local_githubsync'));
54+
$mform->addElement(
55+
'advcheckbox',
56+
'auto_sync',
57+
get_string('auto_sync', 'local_githubsync'),
58+
get_string('auto_sync_desc', 'local_githubsync')
59+
);
4760

4861
// Show last sync info if available.
4962
if (!empty($this->_customdata['last_sync_time'])) {
5063
$lastsynced = userdate($this->_customdata['last_sync_time']);
5164
$sha = $this->_customdata['last_sync_sha'] ?? '';
52-
$mform->addElement('static', 'lastsyncinfo', get_string('lastsynced', 'local_githubsync'),
53-
$lastsynced . ($sha ? ' (commit ' . s($sha) . ')' : ''));
65+
$mform->addElement(
66+
'static',
67+
'lastsyncinfo',
68+
get_string('lastsynced', 'local_githubsync'),
69+
$lastsynced . ($sha ? ' (commit ' . s($sha) . ')' : '')
70+
);
5471
}
5572

5673
$this->add_action_buttons(true, get_string('savesettings', 'local_githubsync'));
5774
}
5875

76+
/**
77+
* Form validation.
78+
*
79+
* @param array $data Form data
80+
* @param array $files Uploaded files
81+
* @return array Validation errors
82+
*/
5983
public function validation($data, $files) {
6084
$errors = parent::validation($data, $files);
6185

classes/github/client.php

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
*
2626
* Uses the GitHub REST API to fetch repository content without requiring
2727
* git to be installed on the Moodle server.
28+
*
29+
* @package local_githubsync
30+
* @copyright 2026 Allan Haggett
31+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2832
*/
2933
class client {
30-
3134
/** @var string GitHub API base URL */
3235
private const API_BASE = 'https://api.github.com';
3336

@@ -44,10 +47,10 @@ class client {
4447
private string $pat;
4548

4649
/** @var int Remaining API requests (from X-RateLimit-Remaining header) */
47-
private int $ratelimit_remaining = -1;
50+
private int $ratelimitremaining = -1;
4851

4952
/** @var int Rate limit reset timestamp */
50-
private int $ratelimit_reset = 0;
53+
private int $ratelimitreset = 0;
5154

5255
/**
5356
* Constructor.
@@ -190,8 +193,8 @@ private function repo_endpoint(string $suffix = ''): string {
190193
*/
191194
public function get_rate_limit_status(): array {
192195
return [
193-
'remaining' => $this->ratelimit_remaining,
194-
'reset' => $this->ratelimit_reset,
196+
'remaining' => $this->ratelimitremaining,
197+
'reset' => $this->ratelimitreset,
195198
];
196199
}
197200

@@ -229,17 +232,21 @@ private function api_request(string $endpoint, array $params = []): array {
229232
// Track rate limit headers.
230233
$responseheaders = $curl->getResponse();
231234
if (isset($responseheaders['X-RateLimit-Remaining'])) {
232-
$this->ratelimit_remaining = (int) $responseheaders['X-RateLimit-Remaining'];
235+
$this->ratelimitremaining = (int) $responseheaders['X-RateLimit-Remaining'];
233236
}
234237
if (isset($responseheaders['X-RateLimit-Reset'])) {
235-
$this->ratelimit_reset = (int) $responseheaders['X-RateLimit-Reset'];
238+
$this->ratelimitreset = (int) $responseheaders['X-RateLimit-Reset'];
236239
}
237240

238241
// Handle rate limiting.
239-
if ($httpcode === 403 && $this->ratelimit_remaining === 0) {
240-
$resettime = userdate($this->ratelimit_reset);
241-
throw new \moodle_exception('connectionfailed', 'local_githubsync', '',
242-
"GitHub API rate limit exceeded. Resets at {$resettime}");
242+
if ($httpcode === 403 && $this->ratelimitremaining === 0) {
243+
$resettime = userdate($this->ratelimitreset);
244+
throw new \moodle_exception(
245+
'connectionfailed',
246+
'local_githubsync',
247+
'',
248+
"GitHub API rate limit exceeded. Resets at {$resettime}"
249+
);
243250
}
244251

245252
if ($httpcode >= 400) {

classes/sync/asset_handler.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
namespace local_githubsync\sync;
1818

19-
defined('MOODLE_INTERNAL') || die();
20-
2119
use local_githubsync\github\client;
2220

2321
/**
@@ -32,9 +30,12 @@
3230
* filename: the file name
3331
*
3432
* A pluginfile handler in lib.php serves these files.
33+
*
34+
* @package local_githubsync
35+
* @copyright 2026 Allan Haggett
36+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3537
*/
3638
class asset_handler {
37-
3839
/** @var array Allowed asset file extensions */
3940
private const ALLOWED_EXTENSIONS = [
4041
'css', 'js',
@@ -65,6 +66,12 @@ class asset_handler {
6566
/** @var int Count of assets skipped (unchanged) */
6667
private int $skipped = 0;
6768

69+
/**
70+
* Constructor.
71+
*
72+
* @param int $courseid The course ID
73+
* @param client $github GitHub API client
74+
*/
6875
public function __construct(int $courseid, client $github) {
6976
$this->courseid = $courseid;
7077
$this->context = \context_course::instance($courseid);
@@ -82,7 +89,7 @@ public function process_assets(array $assetpaths): array {
8289
global $DB;
8390

8491
foreach ($assetpaths as $repopath) {
85-
// Derive the storage path: assets/css/custom.css -> filepath=/css/, filename=custom.css
92+
// Derive the storage path: assets/css/custom.css -> filepath=/css/, filename=custom.css.
8693
$relpath = preg_replace('#^assets/#', '', $repopath);
8794
$dirname = dirname($relpath);
8895
$filename = basename($relpath);
@@ -205,6 +212,8 @@ public function rewrite_asset_urls(string $html): string {
205212

206213
/**
207214
* Get the operations log.
215+
*
216+
* @return array The operations log.
208217
*/
209218
public function get_operations(): array {
210219
return $this->operations;

classes/sync/course_builder.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,23 @@
2323

2424
/**
2525
* Handles creating and updating Moodle course structure from repo data.
26+
*
27+
* @package local_githubsync
28+
* @copyright 2026 Allan Haggett
29+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2630
*/
2731
class course_builder {
28-
2932
/** @var \stdClass The course object */
3033
private \stdClass $course;
3134

3235
/** @var array Module IDs from the modules table, keyed by module name */
3336
private array $moduleids = [];
3437

38+
/**
39+
* Constructor.
40+
*
41+
* @param \stdClass $course The course record
42+
*/
3543
public function __construct(\stdClass $course) {
3644
global $DB;
3745
$this->course = $course;
@@ -287,8 +295,12 @@ public function create_activity(int $sectionnum, string $name, string $htmlconte
287295
case 'url':
288296
$url = $frontmatter['url'] ?? '';
289297
if (empty($url)) {
290-
throw new \moodle_exception('syncfailed', 'local_githubsync', '',
291-
"URL activity requires 'url' in front matter");
298+
throw new \moodle_exception(
299+
'syncfailed',
300+
'local_githubsync',
301+
'',
302+
"URL activity requires 'url' in front matter"
303+
);
292304
}
293305
return $this->create_url($sectionnum, $name, $url, $htmlcontent);
294306

@@ -333,15 +345,17 @@ public static function parse_front_matter(string $content): array {
333345
$value = trim($m[2]);
334346

335347
// Remove surrounding quotes.
336-
if ((str_starts_with($value, '"') && str_ends_with($value, '"')) ||
337-
(str_starts_with($value, "'") && str_ends_with($value, "'"))) {
348+
if (
349+
(str_starts_with($value, '"') && str_ends_with($value, '"')) ||
350+
(str_starts_with($value, "'") && str_ends_with($value, "'"))
351+
) {
338352
$value = substr($value, 1, -1);
339353
}
340354

341355
// Handle booleans.
342356
if ($value === 'true') {
343357
$value = true;
344-
} elseif ($value === 'false') {
358+
} else if ($value === 'false') {
345359
$value = false;
346360
}
347361

0 commit comments

Comments
 (0)