Skip to content

Commit 6e6cf2e

Browse files
authored
Merge pull request #3432 from codeeu/dev
Dev
2 parents ef7945f + 1b81630 commit 6e6cf2e

21 files changed

Lines changed: 175 additions & 34 deletions

app/CertificateExcellence.php

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,30 @@ public function preflight(): void
7979
}
8080
}
8181

82+
/**
83+
* Generate the certificate PDF and save a copy to the given path (e.g. for viewing test certs).
84+
* Does not upload to S3. Cleans up LaTeX temp files after copying.
85+
*
86+
* @return string The full path where the PDF was saved
87+
*/
88+
public function generateAndSavePdfTo(string $fullPath): string
89+
{
90+
try {
91+
$this->customize_and_save_latex();
92+
$this->run_pdf_creation();
93+
$pdfName = $this->personalized_template_name . '.pdf';
94+
$dir = dirname($fullPath);
95+
if (! is_dir($dir) && ! @mkdir($dir, 0775, true) && ! is_dir($dir)) {
96+
throw new \RuntimeException("Cannot create directory: {$dir}");
97+
}
98+
$contents = Storage::disk('latex')->get($pdfName);
99+
file_put_contents($fullPath, $contents);
100+
return $fullPath;
101+
} finally {
102+
$this->clean_temp_files();
103+
}
104+
}
105+
82106
/**
83107
* Clean up LaTeX artifacts for the generated file.
84108
*/
@@ -99,6 +123,14 @@ public function is_greek()
99123
return (count($split) > 1);
100124
}
101125

126+
/**
127+
* Check for Cyrillic characters in the name (Russian, Ukrainian, etc.).
128+
*/
129+
public function is_cyrillic(): bool
130+
{
131+
return (bool) preg_match('/[\p{Cyrillic}]/u', $this->name_of_certificate_holder);
132+
}
133+
102134
/**
103135
* Escape LaTeX special characters in user data.
104136
*/
@@ -141,12 +173,12 @@ protected function customize_and_save_latex()
141173
Log::info("Using template: {$this->templateName}");
142174
$base_template = Storage::disk('latex')->get($this->templateName);
143175

144-
// Always replace <CERTIFICATE_HOLDER_NAME> for both excellence & super-organiser
145-
$template = str_replace(
146-
'<CERTIFICATE_HOLDER_NAME>',
147-
$this->tex_escape($this->name_of_certificate_holder),
148-
$base_template
149-
);
176+
// Name replacement: for default (non-Greek) template, wrap Cyrillic names in russian block so T2A is used
177+
$nameReplacement = $this->tex_escape($this->name_of_certificate_holder);
178+
if (! $this->is_greek() && $this->is_cyrillic()) {
179+
$nameReplacement = '\\begin{otherlanguage*}{russian}' . $nameReplacement . '\\end{otherlanguage*}';
180+
}
181+
$template = str_replace('<CERTIFICATE_HOLDER_NAME>', $nameReplacement, $base_template);
150182

151183
// If super-organiser, we also replace these:
152184
if ($this->type === 'super-organiser') {

app/Console/Commands/CertificateTestTwo.php

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,104 @@
44

55
use App\CertificateExcellence;
66
use Illuminate\Console\Command;
7-
use Illuminate\Support\Facades\Storage;
87

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

15-
protected $description = 'Generate two test certificates locally: one Greek name, one Russian name';
17+
protected $description = 'Run preflight for a few test names (Greek, Cyrillic, Latin, accented); use --keep-pdf to save viewable PDFs';
18+
19+
/** @var array<int, array{name: string, label: string}> */
20+
private static array $testNames = [
21+
['name' => 'Βασιλική Μπαμπαλή', 'label' => 'Greek'],
22+
['name' => 'Юлія Колісниченко', 'label' => 'Ukrainian (Cyrillic)'],
23+
['name' => 'Иван Петров', 'label' => 'Russian (Cyrillic)'],
24+
['name' => 'John Smith', 'label' => 'English'],
25+
['name' => 'José García', 'label' => 'Accented Latin'],
26+
['name' => 'María Fernández', 'label' => 'Accented Latin 2'],
27+
];
1628

1729
public function handle(): int
1830
{
1931
$edition = (int) $this->option('edition');
20-
$preflightOnly = (bool) $this->option('no-pdf');
32+
$typeOption = strtolower(trim((string) $this->option('type')));
33+
$keepPdf = (bool) $this->option('keep-pdf');
34+
$uploadToS3 = (bool) $this->option('generate');
35+
$preflightOnly = ! $keepPdf && ! $uploadToS3;
36+
37+
$typeList = $this->resolveTypes($typeOption);
38+
if ($typeList === null) {
39+
$this->error("Invalid --type: {$typeOption}. Use excellence, super-organiser, or all.");
40+
return self::FAILURE;
41+
}
2142

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

27-
foreach ($tests as $test) {
28-
$this->info("Testing {$test['label']}: {$test['name']}");
48+
$mode = $preflightOnly ? 'Preflight only (no PDFs kept)' : ($keepPdf ? 'Generating PDFs to ' . $outDir : 'Generating and uploading to S3');
49+
$this->info('Certificate test names. Edition: ' . $edition . ', types: ' . implode(', ', $typeList) . '. ' . $mode);
50+
$this->newLine();
51+
52+
$failed = 0;
53+
$index = 0;
54+
foreach (self::$testNames as $test) {
55+
$this->line("--- {$test['label']}: {$test['name']} ---");
2956
$cert = new CertificateExcellence($edition, $test['name'], 'excellence', 0, 999999, '[email protected]');
30-
$this->line(' is_greek(): ' . ($cert->is_greek() ? 'true' : 'false'));
31-
32-
try {
33-
if ($preflightOnly) {
34-
$cert->preflight();
35-
$this->info(" Preflight OK (no PDF kept).");
36-
} else {
37-
$url = $cert->generate();
38-
$this->info(" Generated: {$url}");
57+
$this->line(' is_greek(): ' . ($cert->is_greek() ? 'true' : 'false') . ', is_cyrillic(): ' . ($cert->is_cyrillic() ? 'true' : 'false'));
58+
59+
$safeLabel = preg_replace('/[^a-zA-Z0-9_-]/', '_', $test['label']);
60+
foreach ($typeList as $certType) {
61+
$activities = $certType === 'super-organiser' ? 10 : 0;
62+
$certTest = new CertificateExcellence($edition, $test['name'], $certType, $activities, 999999, '[email protected]');
63+
$label = $certType === 'super-organiser' ? 'SuperOrganiser' : 'Excellence';
64+
try {
65+
if ($preflightOnly) {
66+
$certTest->preflight();
67+
$this->info(" [{$label}] Preflight OK.");
68+
} elseif ($keepPdf) {
69+
$index++;
70+
$filename = sprintf('%02d-%s-%s.pdf', $index, $safeLabel, $label);
71+
$path = $certTest->generateAndSavePdfTo($outDir . DIRECTORY_SEPARATOR . $filename);
72+
$this->info(" [{$label}] Saved: {$path}");
73+
} else {
74+
$url = $certTest->generate();
75+
$this->info(" [{$label}] Generated: {$url}");
76+
}
77+
} catch (\Throwable $e) {
78+
$this->error(" [{$label}] Failed: " . $e->getMessage());
79+
$failed++;
3980
}
40-
} catch (\Throwable $e) {
41-
$this->error(' Failed: ' . $e->getMessage());
42-
return self::FAILURE;
4381
}
82+
$this->newLine();
4483
}
4584

46-
$this->newLine();
47-
$this->info('Both certificates OK. Greek uses _greek template; Russian uses default template.');
85+
if ($failed > 0) {
86+
$this->error("{$failed} test(s) failed.");
87+
return self::FAILURE;
88+
}
89+
if ($keepPdf) {
90+
$this->info('All test PDFs saved. Open: ' . $outDir);
91+
} else {
92+
$this->info('All test names passed.');
93+
}
4894
return self::SUCCESS;
4995
}
96+
97+
/** @return array<string>|null */
98+
private function resolveTypes(string $typeOption): ?array
99+
{
100+
return match ($typeOption) {
101+
'all' => ['excellence', 'super-organiser'],
102+
'excellence' => ['excellence'],
103+
'super-organiser', 'superorganiser' => ['super-organiser'],
104+
default => null,
105+
};
106+
}
50107
}

app/DreamJobRoleModel.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class DreamJobRoleModel extends Model
1818
'link',
1919
'video',
2020
'pathway_map_link',
21+
'pathway_title',
22+
'pathway_cta_text',
2123
'position',
2224
'active',
2325
];

app/Nova/DreamJobRoleModel.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ public function fields(Request $request): array
6565
->nullable()
6666
->rules('nullable', 'max:255')
6767
->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'),
68+
Trix::make('Pathway section title', 'pathway_title')
69+
->nullable()
70+
->help('Shown above the pathway map (e.g. Explore Career Pathway). Supports links and formatting.'),
71+
Text::make('Pathway CTA text', 'pathway_cta_text')
72+
->nullable()
73+
->help('Link label under the map. Defaults to "Career Pathway Map".'),
6874

6975
Number::make('Position', 'position')
7076
->min(0)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration {
8+
public function up(): void
9+
{
10+
if (Schema::hasTable('dream_job_role_models') && !Schema::hasColumn('dream_job_role_models', 'pathway_title')) {
11+
Schema::table('dream_job_role_models', function (Blueprint $table) {
12+
$table->text('pathway_title')->nullable()->after('pathway_map_link');
13+
});
14+
}
15+
16+
if (Schema::hasTable('dream_job_role_models') && !Schema::hasColumn('dream_job_role_models', 'pathway_cta_text')) {
17+
Schema::table('dream_job_role_models', function (Blueprint $table) {
18+
$table->string('pathway_cta_text')->nullable()->after('pathway_title');
19+
});
20+
}
21+
}
22+
23+
public function down(): void
24+
{
25+
if (Schema::hasTable('dream_job_role_models') && Schema::hasColumn('dream_job_role_models', 'pathway_cta_text')) {
26+
Schema::table('dream_job_role_models', function (Blueprint $table) {
27+
$table->dropColumn('pathway_cta_text');
28+
});
29+
}
30+
31+
if (Schema::hasTable('dream_job_role_models') && Schema::hasColumn('dream_job_role_models', 'pathway_title')) {
32+
Schema::table('dream_job_role_models', function (Blueprint $table) {
33+
$table->dropColumn('pathway_title');
34+
});
35+
}
36+
}
37+
};

database/seeders/DreamJobRoleModelSeeder.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ public function run(): void
199199
'link' => $row['link'],
200200
'video' => $row['video'],
201201
'pathway_map_link' => $row['pathway_map_link'] !== '' ? $row['pathway_map_link'] : null,
202+
'pathway_title' => $row['pathway_title'] ?? 'Explore Career Pathway',
203+
'pathway_cta_text' => $row['pathway_cta_text'] ?? 'Career Pathway Map',
202204
'position' => $index,
203205
'active' => true,
204206
]

resources/views/static/dream-jobs-in-digital-role.blade.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,18 @@
213213
'link' => (string) ($dbItem->link ?? ''),
214214
'video' => (string) ($dbItem->video ?? ''),
215215
'pathway_map_link' => (string) ($dbItem->pathway_map_link ?? ''),
216+
'pathway_title' => (string) ($dbItem->pathway_title ?? ''),
217+
'pathway_cta_text' => (string) ($dbItem->pathway_cta_text ?? ''),
216218
'country' => (string) $dbItem->country,
217219
];
218220
} else {
219221
$item = collect($fallbackResults)->firstWhere('slug', $slug);
220222
abort_if(! $item, 404);
221223
}
222224
225+
$item['pathway_title'] = (string) ($item['pathway_title'] ?? 'Explore Career Pathway');
226+
$item['pathway_cta_text'] = (string) ($item['pathway_cta_text'] ?? 'Career Pathway Map');
227+
223228
$list = [
224229
(object) ['label' => 'Careers in Digital', 'href' => '/dream-jobs-in-digital'],
225230
(object) ['label' => $item['first_name'] . ' ' . $item['last_name'], 'href' => ''],
@@ -336,11 +341,11 @@ class="animation-element move-background duration-[1.5s] absolute z-0 lg:-bottom
336341
? (string) $item['pathway_map_link']
337342
: '/docs/dream-jobs/' . ltrim((string) $item['pathway_map_link'], '/');
338343
@endphp
339-
<h2 class="text-dark-blue text-[22px] md:text-4xl leading-[44px] font-medium font-['Montserrat'] mb-6 md:mb-10">
340-
Explore Career Pathway
341-
</h2>
344+
<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">
345+
{!! $item['pathway_title'] !!}
346+
</div>
342347
<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" />
343-
<a class="font-normal text-xl text-dark-blue underline" target="_blank" href="{{ $pathwayHref }}">Career Pathway Map</a>
348+
<a class="font-normal text-xl text-dark-blue underline" target="_blank" href="{{ $pathwayHref }}">{{ $item['pathway_cta_text'] }}</a>
344349
</div>
345350
</section>
346351
@endif
192 KB
Binary file not shown.
167 KB
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)