Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,7 @@ return [

'vite_theme' => '',
'colors' => [],
'awards' => [
'Default' => 'default',
],
// awards was removed. Course certificates use certificate_template_id.
'top_navigation' => false,
'show_exit_lms_link' => true,
];
Expand Down Expand Up @@ -423,7 +421,32 @@ When `multipart_upload.enabled` is `true` **and** `spykapps/filament-uppy-upload

## Certificate Customization

The LMS package generates PDF certificates when users complete courses. You can customize the appearance and content of certificates using the following configuration options:
The LMS package generates PDF certificates when users complete courses. You can customize the appearance and content of certificates using the following configuration options.

### Certificate-builder templates

Course certificates require [tapp/filament-certificate-builder](https://github.com/TappNetwork/filament-certificate-builder). See [UPGRADING.md](UPGRADING.md) to convert legacy `award` Blades.

```bash
composer require tapp/filament-certificate-builder
php artisan vendor:publish --tag=filament-lms-migrations
php artisan migrate
```

```php
// config/filament-lms.php
'integrations' => [
'certificate_builder' => [
'enabled' => true,
'token_set' => 'course',
'template_resource' => \Tapp\FilamentCertificateBuilder\Filament\Resources\CertificateTemplates\CertificateTemplateResource::class,
],
],
```

Add a matching `course` token set in `config/certificate-builder.php`. LMS resolves tokens with context `['course' => $course, 'user' => $user]` on `filament-lms::certificates.show` / `filament-lms::certificates.download`. A course without a template returns 404.

On **Edit Course**, **Create Certificate Template** creates a template for that token set, sets `certificate_template_id`, and opens the designer. **Edit Certificate Template** opens the assigned template.

### certificate_logo

Expand Down
58 changes: 58 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Upgrading

## Award certificates → certificate templates

This is a breaking change. Course certificates are builder-only. The `lms_courses.award` column is removed.

### Before you deploy

1. Require the builder in the host app:

```bash
composer require tapp/filament-certificate-builder
```

2. Keep `filament-lms.awards` in the host config until `php artisan migrate` has run. The upgrade reads those labels (and any `award` values already on courses) to name templates. After migrate succeeds you can delete the `awards` array.

3. Keep published award Blades on disk until migrate has run (`resources/views/vendor/filament-lms/certificates/*.blade.php`). They are the source for logos, copy, borders, and headers.

4. Enable the course token set in `config/certificate-builder.php` and point `filament-lms.integrations.certificate_builder.template_resource` at your Filament resource.

### Upgrade

```bash
php artisan filament-lms:upgrade-awards --dry-run
php artisan migrate
```

`php artisan migrate` runs `drop_award_from_lms_courses_table`, which:

1. Creates or reuses one certificate template per award key
2. Assigns a template to every course that does not already have a living `certificate_template_id` (`award = null` uses **Default Certificate**)
3. Does **not** overwrite hand-edited templates or retarget courses that already point at a different template
4. Fails if any course still lacks a template
5. Makes `certificate_template_id` required (`restrictOnDelete`) and drops `award`

Preview or force-refresh migrated layouts (not custom templates) with:

```bash
php artisan filament-lms:upgrade-awards --dump=storage/logs/lms-award-upgrade.json
php artisan filament-lms:upgrade-awards --force
php artisan filament-lms:upgrade-awards --award=decan
```

`filament-lms:migrate-awards-to-templates` is a hidden alias and will be removed in a later release.

### After migrate

- Delete published LMS award Blades and any Tailwind `@source` aimed only at those views
- Remove `awards` from `config/filament-lms.php`
- Point SCORM imports and new courses at a certificate template (the course form and `CourseFactory` default to **Default Certificate**)
- Training / certification certificates (CHECK `awardCertification()`) are unchanged

### Notes

- Layouts are not pixel-perfect copies of the old Blades
- `--force` only refreshes templates named `{Award label} Certificate`
- Courses with `award` values that had no Blade (for example CHECK `chwad`) get the default Blade source
- Rollback cannot restore per-course `award` values unless you keep the `--dump` JSON
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"suggest": {
"tapp/filament-library": "Required for Library file / Library link step materials (set filament-lms.integrations.filament_library.enabled for the admin material picker).",
"tapp/filament-certificate-builder": "Required for course certificates. Install before running filament-lms:upgrade-awards.",
"spykapps/filament-uppy-upload": "Optional. Enables Uppy chunked (multipart) uploads for SCORM package import so large ZIPs stay under Cloudflare's ~100MB limit (set filament-lms.common_cartridge_import.multipart_upload.enabled)."
},
"require-dev": {
Expand Down
15 changes: 12 additions & 3 deletions config/filament-lms.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use App\Models\User;
use Tapp\FilamentCertificateBuilder\Filament\Resources\CertificateTemplates\CertificateTemplateResource;

return [
'theme' => 'default',
Expand All @@ -21,9 +22,6 @@

'vite_theme' => '',
'colors' => [],
'awards' => [
'default' => 'Default',
],
// Enable top navigation on the LMS dashboard (courses list page).
// Note: This only affects the dashboard. Course pages always use sidebar navigation.
'top_navigation' => false,
Expand Down Expand Up @@ -186,6 +184,17 @@
// Max rows returned when searching library files or links on the Step form.
'material_select_limit' => 200,
],

'certificate_builder' => [
// Required for course certificates. The builder package must be installed.
'enabled' => true,

// Token set key from config/certificate-builder.php used for new course templates.
'token_set' => 'course',

// Filament resource used after creating a template from Edit Course.
'template_resource' => CertificateTemplateResource::class,
],
],

/*
Expand Down
10 changes: 9 additions & 1 deletion database/factories/CourseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use Tapp\FilamentLms\Models\Course;
use Tapp\FilamentLms\Support\CertificateBuilder;

class CourseFactory extends Factory
{
Expand All @@ -23,8 +24,15 @@ public function definition(): array
'name' => $name,
'slug' => $slug,
'external_id' => $externalId,
'award' => 'default',
'certificate_template_id' => CertificateBuilder::defaultTemplateId(),
'description' => $this->faker->sentence(),
];
}

public function withoutCertificateTemplate(): static
{
return $this->state([
'certificate_template_id' => null,
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (Schema::hasColumn('lms_courses', 'certificate_template_id')) {
return;
}

Schema::table('lms_courses', function (Blueprint $table) {
$table->unsignedBigInteger('certificate_template_id')
->nullable()
->after('award')
->index();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('lms_courses', function (Blueprint $table) {
$table->dropIndex(['certificate_template_id']);
$table->dropColumn('certificate_template_id');
});
}
};
55 changes: 55 additions & 0 deletions database/migrations/drop_award_from_lms_courses_table.php.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Tapp\FilamentLms\Services\MigrateAwardsToCertificateTemplates;
use Tapp\FilamentLms\Support\CertificateBuilder;

return new class extends Migration
{
public function up(): void
{
if (! Schema::hasColumn('lms_courses', 'award')) {
return;
}

if (! CertificateBuilder::enabled()) {
throw new RuntimeException('tapp/filament-certificate-builder must be installed before dropping lms_courses.award. See UPGRADING.md.');
}

app(MigrateAwardsToCertificateTemplates::class)->handle();

$unverified = app(MigrateAwardsToCertificateTemplates::class)->unverifiedCourseIds();

if ($unverified !== []) {
throw new RuntimeException(
'Cannot drop lms_courses.award while courses lack a certificate template (ids: '.implode(', ', $unverified).'). Run php artisan filament-lms:upgrade-awards.',
);
}

$hasForeignKey = collect(Schema::getForeignKeys('lms_courses'))
->contains(fn (array $foreignKey): bool => in_array('certificate_template_id', $foreignKey['columns'], true));

if ($hasForeignKey) {
Schema::table('lms_courses', function (Blueprint $table): void {
$table->dropForeign(['certificate_template_id']);
});
}

Schema::table('lms_courses', function (Blueprint $table): void {
$table->unsignedBigInteger('certificate_template_id')->nullable(false)->change();
});
Comment thread
johnwesely marked this conversation as resolved.

Schema::table('lms_courses', function (Blueprint $table): void {
$table->foreign('certificate_template_id')
->references('id')
->on('certificate_templates')
->restrictOnDelete();
});

Schema::table('lms_courses', function (Blueprint $table): void {
$table->dropColumn('award');
});
}
};
4 changes: 4 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ parameters:
message: '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::(getFirstMedia|getSecureUrl)\(\)#'
paths:
- src/Livewire/LibraryFileStep.php
- src/Livewire/LibraryLinkStep.php
# Optional filament-certificate-builder integration
- '#Class Tapp\\FilamentCertificateBuilder\\#'
- '#unknown class Tapp\\FilamentCertificateBuilder\\#'
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Tapp\FilamentLms\Console\Commands;

use Illuminate\Console\Command;

final class MigrateAwardsToCertificateTemplatesCommand extends Command
{
protected $hidden = true;

protected $signature = 'filament-lms:migrate-awards-to-templates
{--award= : Only migrate this award key}
{--dry-run : Show the plan without writing}
{--force : Recreate layouts for migrated template names}
{--dump= : Write a JSON summary to this path}';

protected $description = 'Deprecated alias of filament-lms:upgrade-awards';

public function handle(): int
{
$this->warn('filament-lms:migrate-awards-to-templates is deprecated. Use filament-lms:upgrade-awards.');

return $this->call('filament-lms:upgrade-awards', [
'--award' => $this->option('award'),
'--dry-run' => $this->option('dry-run'),
'--force' => $this->option('force'),
'--dump' => $this->option('dump'),
]);
}
}
Loading
Loading