diff --git a/app/CertificateExcellence.php b/app/CertificateExcellence.php index 3fb32be7a..04894e3cf 100644 --- a/app/CertificateExcellence.php +++ b/app/CertificateExcellence.php @@ -13,74 +13,70 @@ class CertificateExcellence private $templateName; private $name_of_certificate_holder; - private $email_of_certificate_holder; - private $resource_path; - private $pdflatex; - private $personalized_template_name; - - private $event; - private $id; - private $edition; - private $number_of_activities; + private $type; - public function __construct($edition, $name_for_certificate, $type, $number_of_activities) + public function __construct($edition, $name_for_certificate, $type = 'excellence', $number_of_activities = 0) { - $this->edition = $edition; $this->name_of_certificate_holder = $name_for_certificate; $this->email_of_certificate_holder = auth()->user()->email ?? ''; - $this->personalized_template_name = $edition.'-'.auth()->id(); - $this->resource_path = resource_path().'/latex'; + $this->personalized_template_name = $edition . '-' . auth()->id(); + $this->resource_path = resource_path() . '/latex'; $this->pdflatex = config('codeweek.pdflatex_path'); - $this->id = auth()->id().'-'.str_random(10); + $this->id = auth()->id() . '-' . str_random(10); $this->number_of_activities = $number_of_activities; - $this->type = $type ?? 'excellence'; + $this->type = $type ?: 'excellence'; + // e.g. "excellence-2025.tex" or "super-organiser-2025.tex" $this->templateName = "{$this->type}-{$this->edition}.tex"; - Log::info('User ID '.auth()->id()." generating {$this->type} certificate with name: ".$name_for_certificate); + Log::info('User ID ' . auth()->id() . " generating {$this->type} certificate with name: " . $name_for_certificate); } + /** + * Generates the certificate PDF, saves to S3, cleans up temp files. + * Returns the S3 path of the generated PDF. + */ public function generate() { - $this->customize_and_save_latex(); $this->run_pdf_creation(); $s3path = $this->copy_to_s3(); $this->clean_temp_files(); return $s3path; - } + /** + * Clean up LaTeX artifacts for the generated file. + */ private function clean_temp_files() { - Storage::disk('latex')->delete($this->personalized_template_name.'.aux'); - Storage::disk('latex')->delete($this->personalized_template_name.'.tex'); - Storage::disk('latex')->delete($this->personalized_template_name.'.pdf'); - Storage::disk('latex')->delete($this->personalized_template_name.'.log'); + Storage::disk('latex')->delete($this->personalized_template_name . '.aux'); + Storage::disk('latex')->delete($this->personalized_template_name . '.tex'); + Storage::disk('latex')->delete($this->personalized_template_name . '.pdf'); + Storage::disk('latex')->delete($this->personalized_template_name . '.log'); } + /** + * Check for Greek characters in the name. + */ public function is_greek() { - $split = preg_split('/[\p{Greek}]/u', $this->name_of_certificate_holder); - if (count($split) > 1) { - // Log::info("Detected as Greek: " . $this->name_of_certificate_holder); - return true; - } - - return false; - + return (count($split) > 1); } + /** + * Escape LaTeX special characters in user data. + */ private function tex_escape($string) { $map = [ @@ -96,82 +92,85 @@ private function tex_escape($string) '}' => '\\}', ]; - return preg_replace_callback("/([\^\%~\\\\#\$%&_\{\}])/", + return preg_replace_callback( + "/([\^\%~\\\\#\$%&_\{\}])/", function ($matches) use ($map) { foreach ($matches as $match) { return $map[$match]; } - }, $string); - } - - protected function update_event($s3path) - { - $this->event->update([ - 'certificate_url' => $s3path, - 'certificate_generated_at' => Carbon::now(), - ]); + }, + $string + ); } /** - * @throws \League\Flysystem\FileNotFoundException - */ - protected function copy_to_s3(): string - { - $inputStream = Storage::disk('latex')->getDriver()->readStream($this->personalized_template_name.'.pdf'); - $destination = Storage::disk('s3')->path('/certificates/'.$this->id.'.pdf'); - Storage::disk('s3')->put($destination, $inputStream); - - return Storage::disk('s3')->url('certificates/'.$this->id.'.pdf'); - } - - /** - * @return mixed - * - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + * Read the base template from disk, replace placeholders, and save the .tex file. */ protected function customize_and_save_latex() { + // If the name is Greek, switch to Greek template if it exists: if ($this->is_greek()) { $this->templateName = "{$this->type}_greek-{$this->edition}.tex"; } - Log::info($this->templateName); - //open the latex template + + Log::info("Using template: {$this->templateName}"); $base_template = Storage::disk('latex')->get($this->templateName); - //replace the text in template - $template = str_replace('', $this->tex_escape($this->name_of_certificate_holder), $base_template); + // Always replace for both excellence & super-organiser + $template = str_replace( + '', + $this->tex_escape($this->name_of_certificate_holder), + $base_template + ); - if ($this->type == 'super-organiser') { - $template = str_replace('', $this->tex_escape($this->email_of_certificate_holder), $template); - $template = str_replace('', $this->tex_escape(Carbon::now()->format('d/m/Y')), $template); + // If super-organiser, we also replace these: + if ($this->type === 'super-organiser') { + // Possibly also if you want it dynamic $template = str_replace('', $this->tex_escape($this->number_of_activities), $template); + $template = str_replace('', $this->tex_escape(Carbon::now()->format('d/m/Y')), $template); + // If you added or placeholders, handle them as well: + $template = str_replace('', $this->tex_escape($this->email_of_certificate_holder), $template); + $template = str_replace('', $this->edition, $template); } - Log::info($template); + // For excellence type, you can do other placeholder replacements here if needed. - //save it locally - Storage::disk('latex')->put($this->personalized_template_name.'.tex', $template); + // Save updated .tex + Log::info($template); + Storage::disk('latex')->put($this->personalized_template_name . '.tex', $template); } + /** + * Compile the .tex file with pdflatex. + */ protected function run_pdf_creation(): void { + $command = $this->pdflatex . ' -interaction=nonstopmode -output-directory ' . + $this->resource_path . ' ' . + $this->resource_path . '/' . $this->personalized_template_name . '.tex'; - //call the pdflatex command - $command = $this->pdflatex.' -interaction=nonstopmode -output-directory '.$this->resource_path.' '.$this->resource_path.'/'.$this->personalized_template_name.'.tex'; - - Log::info($command); - + Log::info("pdflatex command: $command"); $cwd = $this->resource_path; - Log::info($cwd); - - // $process = new Process($command, $cwd); $process = Process::fromShellCommandline($command, $cwd); $process->run(); - // executes after the command finishes - if (! $process->isSuccessful()) { + if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } } + + /** + * Copy the resulting PDF to S3, return its S3 URL. + */ + protected function copy_to_s3(): string + { + $pdfFile = $this->personalized_template_name . '.pdf'; + $inputStream = Storage::disk('latex')->getDriver()->readStream($pdfFile); + $destination = Storage::disk('s3')->path('/certificates/' . $this->id . '.pdf'); + + Storage::disk('s3')->put($destination, $inputStream); + + return Storage::disk('s3')->url('certificates/' . $this->id . '.pdf'); + } } diff --git a/app/Console/Commands/Excellence.php b/app/Console/Commands/Excellence.php index 4e536c235..647611feb 100644 --- a/app/Console/Commands/Excellence.php +++ b/app/Console/Commands/Excellence.php @@ -63,6 +63,5 @@ public function handle(): void Log::info($ex->getMessage()); } } - } } diff --git a/app/Console/Commands/NotifySuperOrganisers.php b/app/Console/Commands/NotifySuperOrganisers.php index 10822bc22..9972bd9a5 100644 --- a/app/Console/Commands/NotifySuperOrganisers.php +++ b/app/Console/Commands/NotifySuperOrganisers.php @@ -26,8 +26,6 @@ class NotifySuperOrganisers extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { @@ -39,30 +37,50 @@ public function __construct() */ public function handle(): void { - $edition = $this->argument('edition'); + $this->info("🔹 NotifySuperOrganisers command started for edition: $edition"); + // Fetch winners for the given edition $winners = Excellence::byYear($edition, 'SuperOrganiser'); - foreach ($winners as $winner) { + if ($winners->isEmpty()) { + $this->warn("⚠️ No winners found for edition: $edition"); + Log::info("No SuperOrganiser winners found for edition: $edition."); + return; + } + + $notifiedCount = 0; + foreach ($winners as $winner) { $user = $winner->user; if (! $user) { + $this->warn("⚠️ Skipping winner with missing user data (ID: $winner->id)"); + Log::warning("Skipping winner with missing user data. Winner ID: $winner->id"); continue; } if ($user->email && $user->receive_emails) { + // Send email notification Mail::to($user->email)->queue(new \App\Mail\NotifySuperOrganiser($user, $edition)); - $excellence = $user->superOrganisers->where('edition', '=', $edition)->first(); - $excellence->notified_at = Carbon::now(); - $excellence->save(); + // Mark as notified + $excellence = $user->superOrganisers->where('edition', '=', $edition)->first(); + if ($excellence) { + $excellence->notified_at = Carbon::now(); + $excellence->save(); + } + $notifiedCount++; + $this->info("✅ Email queued for: {$user->email}"); + Log::info("Email queued for SuperOrganiser: {$user->email}, Edition: $edition"); } else { - Log::info($user->id.' has no valid email address'); + $this->warn("⚠️ Skipping user {$user->id} (No valid email or opted out)"); + Log::info("User {$user->id} skipped - No valid email or opted out."); } - } + + $this->info("✅ NotifySuperOrganisers command executed for edition: $edition, Emails sent: $notifiedCount"); + Log::info("NotifySuperOrganisers command completed. Edition: $edition, Emails sent: $notifiedCount."); } } diff --git a/app/Console/Commands/NotifyWinners.php b/app/Console/Commands/NotifyWinners.php index e37edf9c4..9502c4cb7 100644 --- a/app/Console/Commands/NotifyWinners.php +++ b/app/Console/Commands/NotifyWinners.php @@ -26,8 +26,6 @@ class NotifyWinners extends Command /** * Create a new command instance. - * - * @return void */ public function __construct() { @@ -39,27 +37,46 @@ public function __construct() */ public function handle(): void { - $edition = $this->argument('edition'); + $this->info("🔹 NotifyWinners command started for edition: {$edition}"); + + // Get all Excellence rows for this edition that have notified_at=null $winners = Excellence::byYear($edition); - foreach ($winners as $winner) { + if ($winners->isEmpty()) { + $this->warn("⚠️ No winners found for edition: {$edition}"); + return; + } + $count = 0; + foreach ($winners as $winner) { $user = $winner->user; + // If no user is associated, we remove this row if (is_null($user)) { + $this->warn("User is null for winner ID {$winner->id}. Deleting row."); $winner->delete(); - } elseif ($user->email && $user->receive_emails === 1) { + continue; + } + + // If user has a valid email and is set to receive emails + if ($user->email && $user->receive_emails === 1) { Mail::to($user->email)->queue(new \App\Mail\NotifyWinner($user, $edition)); - $excellence = $user->excellences->where('edition', '=', $edition)->first(); - $excellence->notified_at = Carbon::now(); - $excellence->save(); + // Mark notified + $winner->notified_at = Carbon::now(); + $winner->save(); + + $count++; + $this->info("✅ Queued email for: {$user->email}"); } else { - Log::info($user->id.' has no valid email address'); + // user->email is null or user->receive_emails=0 + Log::info("User {$user->id} has no valid email or doesn't receive emails"); + $this->warn("User {$user->id} not notified (invalid email or receive_emails=0)"); } - } + + $this->info("✅ NotifyWinners command finished for edition {$edition}. Emails sent: {$count}"); } } diff --git a/app/Console/Commands/SuperOrganiserCertificate.php b/app/Console/Commands/SuperOrganiserCertificate.php new file mode 100644 index 000000000..115dbb696 --- /dev/null +++ b/app/Console/Commands/SuperOrganiserCertificate.php @@ -0,0 +1,180 @@ +name_of_certificate_holder = $holderName; + $this->number_of_activities = $numberOfActivities; + $this->certificate_date = $certificateDate ?: Carbon::now()->format('d/m/Y'); + $this->language = $language; + + // Choose correct LaTeX template + $this->templateName = ($language === 'el') + ? 'super-organiser-greek-2025.tex' + : 'super-organiser-2025.tex'; + + // Generate unique filename + $random = Str::random(10); + $userId = auth()->check() ? auth()->id() : 0; + $this->personalized_template_name = $random . '-' . $userId; + + // Set resource paths + $this->resource_path = resource_path('latex'); + $this->pdflatex = config('codeweek.pdflatex_path', '/Library/TeX/texbin/pdflatex'); + $this->id = $userId . '-' . $random; + } + + /** + * Generate the PDF certificate. + * @return string The base filename (without .pdf) + */ + public function generate() + { + $this->customize_and_save_latex(); + $this->run_pdf_creation(); + + // Upload the certificate PDF to S3 + $s3path = $this->upload_to_s3(); + + // Clean up local temporary files + $this->clean_temp_files(); + + return $s3path; + } + + /** + * Remove leftover .aux, .log, .out, and .tex files + */ + private function clean_temp_files() + { + $extensions = ['aux', 'log', 'out', 'tex']; + foreach ($extensions as $ext) { + Storage::disk('latex')->delete($this->personalized_template_name . '.' . $ext); + } + } + + /** + * Read the template, replace placeholders, and save the new .tex file. + */ + protected function customize_and_save_latex() + { + // Load the LaTeX template + $base_template = Storage::disk('latex')->get($this->templateName); + + // Perform placeholder replacements + $template = str_replace('', $this->tex_escape($this->name_of_certificate_holder), $base_template); + $template = str_replace('', $this->tex_escape($this->number_of_activities), $template); + $template = str_replace('', $this->tex_escape($this->certificate_date), $template); + + // Save new LaTeX file + Storage::disk('latex')->put($this->personalized_template_name . '.tex', $template); + } + + /** + * Compile the .tex file into a PDF using pdflatex. + */ + protected function run_pdf_creation(): void + { + if (!file_exists($this->pdflatex)) { + throw new \RuntimeException("pdflatex binary not found at path: {$this->pdflatex}"); + } + + $texFile = $this->resource_path . '/' . $this->personalized_template_name . '.tex'; + $command = sprintf( + '%s -interaction=nonstopmode -output-directory %s %s', + escapeshellcmd($this->pdflatex), + escapeshellarg($this->resource_path), + escapeshellarg($texFile) + ); + + $process = Process::fromShellCommandline($command, $this->resource_path); + $process->setTimeout(600); + $process->start(); + + while ($process->isRunning()) { + usleep(400000); + } + + // Optional: Uncomment to throw an error on failure + // if (!$process->isSuccessful()) { + // throw new ProcessFailedException($process); + // } + } + + + protected function upload_to_s3(): string + { + $pdfFile = $this->personalized_template_name . '.pdf'; + $localPath = storage_path('app/latex/' . $pdfFile); + $s3Path = 'certificates/super-organiser/' . $pdfFile; // Destination on S3 + + // Upload to S3 + Storage::disk('s3')->put($s3Path, file_get_contents($localPath), 'public'); + + // Return the public URL for the certificate + return Storage::disk('s3')->url($s3Path); + } + + /** + * Escape special LaTeX characters (#, $, %, &, etc.) + */ + private function tex_escape($string) + { + $map = [ + '#' => '\\#', + '$' => '\$', + '%' => '\\%', + '&' => '\\&', + '~' => '\\~{}', + '_' => '\\_', + '^' => '\\^{}', + '\\' => '\\textbackslash', + '{' => '\\{', + '}' => '\\}', + ]; + + return preg_replace_callback( + "/([\^\%~\\\\#\$%&_\{\}])/", + function ($matches) use ($map) { + return $map[$matches[0]]; + }, + $string + ); + } +} diff --git a/app/Excellence.php b/app/Excellence.php index fe604cb57..6b031577b 100644 --- a/app/Excellence.php +++ b/app/Excellence.php @@ -17,6 +17,7 @@ * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @property string|null $notified_at + * @property string $type * @property-read \App\User $user * * @method static \Illuminate\Database\Eloquent\Builder|Excellence newModelQuery() @@ -30,6 +31,7 @@ * @method static \Illuminate\Database\Eloquent\Builder|Excellence whereNotifiedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|Excellence whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|Excellence whereUserId($value) + * @method static \Illuminate\Database\Eloquent\Builder|Excellence whereType($value) * * @mixin \Eloquent */ @@ -37,20 +39,36 @@ class Excellence extends Model { use HasFactory; - protected $fillable = ['edition', 'name_for_certificate', 'certificate_url']; + /** + * Mass assignable fields. + * Including 'user_id', 'type', and 'notified_at' + * so updateOrCreate(...) can set them properly. + */ + protected $fillable = [ + 'user_id', + 'edition', + 'type', + 'name_for_certificate', + 'certificate_url', + 'notified_at', + ]; + /** + * Scope or static method to get winners by year and type + * who haven't been notified yet (notified_at=null). + */ public static function byYear($year, $type = 'Excellence') { - return Excellence::with('user')->where( - [ - ['edition', '=', $year], - ['notified_at', '=', null], - ['type', '=', $type], - - ] - )->get(); + return self::with('user') + ->where('edition', $year) + ->whereNull('notified_at') + ->where('type', $type) + ->get(); } + /** + * Relationship: each Excellence row belongs to a User. + */ public function user(): BelongsTo { return $this->belongsTo(\App\User::class); diff --git a/resources/latex/images/excellence-2025.png b/resources/latex/images/excellence-2025.png index e90ff2f3e..4cbe96e70 100644 Binary files a/resources/latex/images/excellence-2025.png and b/resources/latex/images/excellence-2025.png differ diff --git a/resources/latex/images/super-organiser-2025.png b/resources/latex/images/super-organiser-2025.png index 025b8e37a..4e45e918f 100644 Binary files a/resources/latex/images/super-organiser-2025.png and b/resources/latex/images/super-organiser-2025.png differ diff --git a/resources/latex/super-organiser-2025.tex b/resources/latex/super-organiser-2025.tex index 66793ac63..f763e5c12 100644 --- a/resources/latex/super-organiser-2025.tex +++ b/resources/latex/super-organiser-2025.tex @@ -2,10 +2,10 @@ \usepackage[T2A,T1]{fontenc} \usepackage[utf8]{inputenc} \usepackage[greek,russian,english]{babel} -\usepackage{mathpazo} -\renewcommand{\familydefault}{\sfdefault} + \usepackage[landscape,a4paper]{geometry} \geometry{verbose,tmargin=0cm,bmargin=0cm,lmargin=3cm,rmargin=3cm} + \usepackage{fancybox} \usepackage{calc} \usepackage{multicol} @@ -15,77 +15,56 @@ \usepackage{textcomp} \usepackage{paratype} \usepackage{tgpagella} +\usepackage{xcolor} +\usepackage{mathpazo} % (Optional) For Palatino style math fonts +% If you want a different main sans font: +\renewcommand{\familydefault}{\sfdefault} -\DeclareFontFamilySubstitution{T2A}{\rmdefault}{PTSerif-TLF} - -\definecolor{myblue}{RGB}{40, 53, 131} +% Define custom colors +\definecolor{Orange500}{HTML}{F95C22} +\definecolor{Slate600}{HTML}{20262C} +\definecolor{DarkBlue500}{HTML}{1C4DA1} +\definecolor{GrayLine}{HTML}{858B91} +% Background image command \newcommand\BackgroundPic{% -\put(0,0){% -\parbox[b][\paperheight]{\paperwidth}{% -\vfill -\centering -\includegraphics[width=\paperwidth,height=\paperheight,% -keepaspectratio]{images/super-organiser-2025.png}% -\vfill + \put(0,0){% + \parbox[b][\paperheight]{\paperwidth}{% + \vfill + \centering + \includegraphics[width=\paperwidth,height=\paperheight,keepaspectratio]{images/super-organiser-2025.png}% + \vfill }}} - \begin{document} \AddToShipoutPicture{\BackgroundPic} + +% Some top spacing ~ -\vspace{2.2cm} +\vspace{3.5cm} ~ \begin{center} + \vspace{1.5cm} + \textcolor{Orange500}{\fontsize{36}{42}\selectfont EU CodeWeek 2025} \\ + \vspace{0.5cm} + \textcolor{Slate600}{\fontsize{13}{13}\selectfont The European Commission presents this} \\ + \vspace{1cm} + \textcolor{DarkBlue500}{\fontsize{32}{32}\selectfont Certificate of Super Organiser} \\ + \vspace{0.5cm} + \textcolor{Slate600}{\fontsize{13}{13}\selectfont To} \\ + \vspace{0.5cm} + \textcolor{Orange500}{\fontsize{28}{32}\selectfont } \\ + \vspace{0.5cm} + \textcolor{Slate600}{\fontsize{13}{13}\selectfont For Organising} \\ + \vspace{0.5cm} + \textcolor{DarkBlue500}{\fontsize{20}{20}\selectfont + \textbf{ coding activities in 2025} + } \\ + \vspace{1cm} + \textcolor{Slate600}{\fontsize{13}{13}\selectfont Brussels, } \\ -\vspace{6.7cm} - -{\centering\Huge\ -\hspace{-0.9cm} -\begin{otherlanguage*}{russian} -\textcolor{myblue}{} -\end{otherlanguage*} -\par} - -\vspace{1.3cm} - -{\centering\Large\ -\hspace{-0.9cm} -\begin{otherlanguage*}{russian} -\textbf{ coding activities in 2023} -\end{otherlanguage*} -\par} - -\vspace{4.8cm} - -{\centering\normalsize\ -\hspace{-0.9cm} -\begin{otherlanguage*}{russian} -\textcolor{black}{Brussels, } -\end{otherlanguage*} -\par} - -\begin{table}[h] -\footnotesize -\begin{center} - -\end{center} -\end{table} -\vspace{-3cm} -\end{center} -\begin{table}[h] -\footnotesize -\begin{center} -\begin{tabular}{lr} -%~\hspace{0.7cm} -\end{tabular} \end{center} -\end{table} -\vspace{-2.2cm} -\begin{center} -%\end{tabular} -\end{center} -\end{document} +\end{document} \ No newline at end of file diff --git a/resources/latex/super-organiser_greek-2025.tex b/resources/latex/super-organiser_greek-2025.tex index 082cf493d..b5e6c9245 100644 --- a/resources/latex/super-organiser_greek-2025.tex +++ b/resources/latex/super-organiser_greek-2025.tex @@ -2,10 +2,10 @@ \usepackage[T1,LGR,T2A]{fontenc} \usepackage[utf8]{inputenc} \usepackage[greek,russian,english]{babel} -\usepackage{mathpazo} -\renewcommand{\familydefault}{\sfdefault} + \usepackage[landscape,a4paper]{geometry} \geometry{verbose,tmargin=0cm,bmargin=0cm,lmargin=3cm,rmargin=3cm} + \usepackage{fancybox} \usepackage{calc} \usepackage{multicol} @@ -15,12 +15,19 @@ \usepackage{textcomp} \usepackage{paratype} \usepackage{tgpagella} +\usepackage{xcolor} +\usepackage{mathpazo} +\renewcommand{\familydefault}{\sfdefault} \DeclareFontFamilySubstitution{T2A}{\rmdefault}{PTSerif-TLF} \DeclareFontFamilySubstitution{LGR}{\rmdefault}{udidot} \definecolor{myblue}{RGB}{40, 53, 131} +\definecolor{Orange500}{HTML}{F95C22} +\definecolor{Slate600}{HTML}{20262C} +\definecolor{DarkBlue500}{HTML}{1C4DA1} +\definecolor{GrayLine}{HTML}{858B91} \newcommand\BackgroundPic{% \put(0,0){% @@ -36,55 +43,42 @@ \begin{document} \AddToShipoutPicture{\BackgroundPic} ~ -\vspace{2.2cm} +\vspace{3.5cm} ~ \begin{center} + \vspace{1.5cm} + \textcolor{Orange500}{\fontsize{36}{42}\selectfont EU CodeWeek 2025} \\ + \vspace{0.5cm} + \textcolor{Slate600}{\fontsize{13}{13}\selectfont The European Commission presents this} \\ + \vspace{1cm} + \textcolor{DarkBlue500}{\fontsize{32}{32}\selectfont Certificate of Super Organiser} \\ + \vspace{0.5cm} + \textcolor{Slate600}{\fontsize{13}{13}\selectfont To} \\ + \vspace{0.5cm} + + % Greek translation for certificate holder + \begin{otherlanguage*}{greek} + \textcolor{Orange500}{\fontsize{28}{32}\selectfont } + \end{otherlanguage*} \\ + + \vspace{0.5cm} + \textcolor{Slate600}{\fontsize{13}{13}\selectfont For Organising} \\ + \vspace{0.5cm} + + % Greek translation for activities + \begin{otherlanguage*}{greek} + \textcolor{DarkBlue500}{\fontsize{20}{20}\selectfont + \textbf{ coding activities in 2025} + } + \end{otherlanguage*} \\ + + \vspace{1cm} + + % Greek translation for Brussels date + \begin{otherlanguage*}{greek} + \textcolor{Slate600}{\fontsize{13}{13}\selectfont Βρυξέλλες, } + \end{otherlanguage*} \\ -\vspace{6.7cm} - -{\centering\Huge\ -\begin{otherlanguage*}{greek} -\textcolor{myblue}{} -\end{otherlanguage*} -\par} - -\vspace{1.3cm} - -{\centering\Large\ -\hspace{-0.9cm} -\begin{otherlanguage*}{greek} -\textbf{ coding activities in 2023} -\end{otherlanguage*} -\par} - -\vspace{4.8cm} - -{\centering\normalsize\ -\begin{otherlanguage*}{greek} -\textcolor{black}{Βρυξέλλες, } -\end{otherlanguage*} -\par} - -\begin{table}[h] -\footnotesize -\begin{center} - -\end{center} -\end{table} - -\vspace{-3cm} -\end{center} -\begin{table}[h] -\footnotesize -\begin{center} -\begin{tabular}{lr} -%~\hspace{0.7cm} -\end{tabular} \end{center} -\end{table} -\vspace{-2.2cm} -\begin{center} -%\end{tabular} -\end{center} -\end{document} +\end{document} \ No newline at end of file