Skip to content
Merged

Dev #3432

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
44 changes: 38 additions & 6 deletions app/CertificateExcellence.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,30 @@ public function preflight(): void
}
}

/**
* Generate the certificate PDF and save a copy to the given path (e.g. for viewing test certs).
* Does not upload to S3. Cleans up LaTeX temp files after copying.
*
* @return string The full path where the PDF was saved
*/
public function generateAndSavePdfTo(string $fullPath): string
{
try {
$this->customize_and_save_latex();
$this->run_pdf_creation();
$pdfName = $this->personalized_template_name . '.pdf';
$dir = dirname($fullPath);
if (! is_dir($dir) && ! @mkdir($dir, 0775, true) && ! is_dir($dir)) {
throw new \RuntimeException("Cannot create directory: {$dir}");
}
$contents = Storage::disk('latex')->get($pdfName);
file_put_contents($fullPath, $contents);
return $fullPath;
} finally {
$this->clean_temp_files();
}
}

/**
* Clean up LaTeX artifacts for the generated file.
*/
Expand All @@ -99,6 +123,14 @@ public function is_greek()
return (count($split) > 1);
}

/**
* Check for Cyrillic characters in the name (Russian, Ukrainian, etc.).
*/
public function is_cyrillic(): bool
{
return (bool) preg_match('/[\p{Cyrillic}]/u', $this->name_of_certificate_holder);
}

/**
* Escape LaTeX special characters in user data.
*/
Expand Down Expand Up @@ -141,12 +173,12 @@ protected function customize_and_save_latex()
Log::info("Using template: {$this->templateName}");
$base_template = Storage::disk('latex')->get($this->templateName);

// Always replace <CERTIFICATE_HOLDER_NAME> for both excellence & super-organiser
$template = str_replace(
'<CERTIFICATE_HOLDER_NAME>',
$this->tex_escape($this->name_of_certificate_holder),
$base_template
);
// Name replacement: for default (non-Greek) template, wrap Cyrillic names in russian block so T2A is used
$nameReplacement = $this->tex_escape($this->name_of_certificate_holder);
if (! $this->is_greek() && $this->is_cyrillic()) {
$nameReplacement = '\\begin{otherlanguage*}{russian}' . $nameReplacement . '\\end{otherlanguage*}';
}
$template = str_replace('<CERTIFICATE_HOLDER_NAME>', $nameReplacement, $base_template);

// If super-organiser, we also replace these:
if ($this->type === 'super-organiser') {
Expand Down
105 changes: 81 additions & 24 deletions app/Console/Commands/CertificateTestTwo.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,104 @@

use App\CertificateExcellence;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;

class CertificateTestTwo extends Command
{
protected $signature = 'certificate:test-two
{--edition=2025 : Edition year}
{--no-pdf : Only run preflight (no S3), do not keep PDFs}';
{--type=all : excellence|super-organiser|all}
{--no-pdf : Preflight only (no S3, no PDFs kept); default}
{--keep-pdf : Generate PDFs and save locally so you can view them (storage/app/test-certificates/)}
{--generate : Generate and upload to S3 (use with care)}';

protected $description = 'Generate two test certificates locally: one Greek name, one Russian name';
protected $description = 'Run preflight for a few test names (Greek, Cyrillic, Latin, accented); use --keep-pdf to save viewable PDFs';

/** @var array<int, array{name: string, label: string}> */
private static array $testNames = [
['name' => 'Βασιλική Μπαμπαλή', 'label' => 'Greek'],
['name' => 'Юлія Колісниченко', 'label' => 'Ukrainian (Cyrillic)'],
['name' => 'Иван Петров', 'label' => 'Russian (Cyrillic)'],
['name' => 'John Smith', 'label' => 'English'],
['name' => 'José García', 'label' => 'Accented Latin'],
['name' => 'María Fernández', 'label' => 'Accented Latin 2'],
];

public function handle(): int
{
$edition = (int) $this->option('edition');
$preflightOnly = (bool) $this->option('no-pdf');
$typeOption = strtolower(trim((string) $this->option('type')));
$keepPdf = (bool) $this->option('keep-pdf');
$uploadToS3 = (bool) $this->option('generate');
$preflightOnly = ! $keepPdf && ! $uploadToS3;

$typeList = $this->resolveTypes($typeOption);
if ($typeList === null) {
$this->error("Invalid --type: {$typeOption}. Use excellence, super-organiser, or all.");
return self::FAILURE;
}

$tests = [
['name' => 'Βασιλική Μπαμπαλή', 'label' => 'Greek'],
['name' => 'Иван Петров', 'label' => 'Russian'],
];
$outDir = $keepPdf ? storage_path('app/test-certificates') : null;
if ($outDir !== null && ! is_dir($outDir)) {
mkdir($outDir, 0775, true);
}

foreach ($tests as $test) {
$this->info("Testing {$test['label']}: {$test['name']}");
$mode = $preflightOnly ? 'Preflight only (no PDFs kept)' : ($keepPdf ? 'Generating PDFs to ' . $outDir : 'Generating and uploading to S3');
$this->info('Certificate test names. Edition: ' . $edition . ', types: ' . implode(', ', $typeList) . '. ' . $mode);
$this->newLine();

$failed = 0;
$index = 0;
foreach (self::$testNames as $test) {
$this->line("--- {$test['label']}: {$test['name']} ---");
$cert = new CertificateExcellence($edition, $test['name'], 'excellence', 0, 999999, 'test@example.com');
$this->line(' is_greek(): ' . ($cert->is_greek() ? 'true' : 'false'));

try {
if ($preflightOnly) {
$cert->preflight();
$this->info(" Preflight OK (no PDF kept).");
} else {
$url = $cert->generate();
$this->info(" Generated: {$url}");
$this->line(' is_greek(): ' . ($cert->is_greek() ? 'true' : 'false') . ', is_cyrillic(): ' . ($cert->is_cyrillic() ? 'true' : 'false'));

$safeLabel = preg_replace('/[^a-zA-Z0-9_-]/', '_', $test['label']);
foreach ($typeList as $certType) {
$activities = $certType === 'super-organiser' ? 10 : 0;
$certTest = new CertificateExcellence($edition, $test['name'], $certType, $activities, 999999, 'test@example.com');
$label = $certType === 'super-organiser' ? 'SuperOrganiser' : 'Excellence';
try {
if ($preflightOnly) {
$certTest->preflight();
$this->info(" [{$label}] Preflight OK.");
} elseif ($keepPdf) {
$index++;
$filename = sprintf('%02d-%s-%s.pdf', $index, $safeLabel, $label);
$path = $certTest->generateAndSavePdfTo($outDir . DIRECTORY_SEPARATOR . $filename);
$this->info(" [{$label}] Saved: {$path}");
} else {
$url = $certTest->generate();
$this->info(" [{$label}] Generated: {$url}");
}
} catch (\Throwable $e) {
$this->error(" [{$label}] Failed: " . $e->getMessage());
$failed++;
}
} catch (\Throwable $e) {
$this->error(' Failed: ' . $e->getMessage());
return self::FAILURE;
}
$this->newLine();
}

$this->newLine();
$this->info('Both certificates OK. Greek uses _greek template; Russian uses default template.');
if ($failed > 0) {
$this->error("{$failed} test(s) failed.");
return self::FAILURE;
}
if ($keepPdf) {
$this->info('All test PDFs saved. Open: ' . $outDir);
} else {
$this->info('All test names passed.');
}
return self::SUCCESS;
}

/** @return array<string>|null */
private function resolveTypes(string $typeOption): ?array
{
return match ($typeOption) {
'all' => ['excellence', 'super-organiser'],
'excellence' => ['excellence'],
'super-organiser', 'superorganiser' => ['super-organiser'],
default => null,
};
}
}
2 changes: 2 additions & 0 deletions app/DreamJobRoleModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class DreamJobRoleModel extends Model
'link',
'video',
'pathway_map_link',
'pathway_title',
'pathway_cta_text',
'position',
'active',
];
Expand Down
6 changes: 6 additions & 0 deletions app/Nova/DreamJobRoleModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ public function fields(Request $request): array
->nullable()
->rules('nullable', 'max:255')
->help('Either a full URL (e.g. S3 link) OR a filename in /public/docs/dream-jobs/, e.g. Career Pathway Map Anny Tubbs.pdf'),
Trix::make('Pathway section title', 'pathway_title')
->nullable()
->help('Shown above the pathway map (e.g. Explore Career Pathway). Supports links and formatting.'),
Text::make('Pathway CTA text', 'pathway_cta_text')
->nullable()
->help('Link label under the map. Defaults to "Career Pathway Map".'),

Number::make('Position', 'position')
->min(0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

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

return new class extends Migration {
public function up(): void
{
if (Schema::hasTable('dream_job_role_models') && !Schema::hasColumn('dream_job_role_models', 'pathway_title')) {
Schema::table('dream_job_role_models', function (Blueprint $table) {
$table->text('pathway_title')->nullable()->after('pathway_map_link');
});
}

if (Schema::hasTable('dream_job_role_models') && !Schema::hasColumn('dream_job_role_models', 'pathway_cta_text')) {
Schema::table('dream_job_role_models', function (Blueprint $table) {
$table->string('pathway_cta_text')->nullable()->after('pathway_title');
});
}
}

public function down(): void
{
if (Schema::hasTable('dream_job_role_models') && Schema::hasColumn('dream_job_role_models', 'pathway_cta_text')) {
Schema::table('dream_job_role_models', function (Blueprint $table) {
$table->dropColumn('pathway_cta_text');
});
}

if (Schema::hasTable('dream_job_role_models') && Schema::hasColumn('dream_job_role_models', 'pathway_title')) {
Schema::table('dream_job_role_models', function (Blueprint $table) {
$table->dropColumn('pathway_title');
});
}
}
};
2 changes: 2 additions & 0 deletions database/seeders/DreamJobRoleModelSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ public function run(): void
'link' => $row['link'],
'video' => $row['video'],
'pathway_map_link' => $row['pathway_map_link'] !== '' ? $row['pathway_map_link'] : null,
'pathway_title' => $row['pathway_title'] ?? 'Explore Career Pathway',
'pathway_cta_text' => $row['pathway_cta_text'] ?? 'Career Pathway Map',
'position' => $index,
'active' => true,
]
Expand Down
13 changes: 9 additions & 4 deletions resources/views/static/dream-jobs-in-digital-role.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,18 @@
'link' => (string) ($dbItem->link ?? ''),
'video' => (string) ($dbItem->video ?? ''),
'pathway_map_link' => (string) ($dbItem->pathway_map_link ?? ''),
'pathway_title' => (string) ($dbItem->pathway_title ?? ''),
'pathway_cta_text' => (string) ($dbItem->pathway_cta_text ?? ''),
'country' => (string) $dbItem->country,
];
} else {
$item = collect($fallbackResults)->firstWhere('slug', $slug);
abort_if(! $item, 404);
}

$item['pathway_title'] = (string) ($item['pathway_title'] ?? 'Explore Career Pathway');
$item['pathway_cta_text'] = (string) ($item['pathway_cta_text'] ?? 'Career Pathway Map');

$list = [
(object) ['label' => 'Careers in Digital', 'href' => '/dream-jobs-in-digital'],
(object) ['label' => $item['first_name'] . ' ' . $item['last_name'], 'href' => ''],
Expand Down Expand Up @@ -336,11 +341,11 @@ class="animation-element move-background duration-[1.5s] absolute z-0 lg:-bottom
? (string) $item['pathway_map_link']
: '/docs/dream-jobs/' . ltrim((string) $item['pathway_map_link'], '/');
@endphp
<h2 class="text-dark-blue text-[22px] md:text-4xl leading-[44px] font-medium font-['Montserrat'] mb-6 md:mb-10">
Explore Career Pathway
</h2>
<div class="text-dark-blue text-[22px] md:text-4xl leading-[44px] font-medium font-['Montserrat'] p-0 mb-6 md:mb-10 [&_p]:p-0 [&_p]:m-0 [&_div]:p-0 [&_div]:m-0 [&_a]:underline">
{!! $item['pathway_title'] !!}
</div>
<img class="rounded-xl w-full h-60 md:h-auto object-cover object-center mb-6 md:mb-12" src="/images/dream-jobs/pathway-map.png" />
<a class="font-normal text-xl text-dark-blue underline" target="_blank" href="{{ $pathwayHref }}">Career Pathway Map</a>
<a class="font-normal text-xl text-dark-blue underline" target="_blank" href="{{ $pathwayHref }}">{{ $item['pathway_cta_text'] }}</a>
</div>
</section>
@endif
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading