HEX
Server: Apache/2.4.41 (Ubuntu)
System: Linux ip-172-31-42-149 5.15.0-1084-aws #91~20.04.1-Ubuntu SMP Fri May 2 07:00:04 UTC 2025 aarch64
User: ubuntu (1000)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/vhost/disk-apps/qas.sports-crowd.com/app/Http/Controllers/PrintTicketController.php
<?php

namespace App\Http\Controllers;

if (!defined('FPDF_FONTPATH')) {
    define('FPDF_FONTPATH', public_path('fonts'));
}

use QrCode;
use App\Team;
use App\Ticket;
use App\Parameter;
use Carbon\Carbon;
use App\TokenUserTicket;
use App\CorporateIdentity;
use Codedge\Fpdf\Fpdf\Fpdf;
use Spatie\Browsershot\Browsershot;
use App\Core\Ticket\TicketTypesEnum;
use Illuminate\Support\Facades\Auth;
use App\Services\TicketParametersService;
use App\Core\Ticket\Application\TicketService;

class PrintTicketController extends Controller
{
    private $ticket;
    private $main_team;
    private $rival_team;

    private $url_main_team;
    private $url_tournament;
    private $url_organizer;
    private $url_ticket_background;
    private $url_footer_sponsors;
    private $__INVALID_ROLE;

    private $fpdf;
    private $matchEventsController;
    private $ticketParametersService;
    private $ticketService;

    public function __construct()
    {
        $this->__INVALID_ROLE = [$this->__MESSENGER_ROL, $this->__CLIENT_ROL, $this->__LOGISTICS_ROL]; // De lo contrario es un admin y lo dejará imprimir.
        $this->matchEventsController = new MatchEventsController;
        $this->ticketParametersService = new TicketParametersService();
        $this->ticketService = new TicketService();
    }

    public function printTicket($code, $user_id = null, $token = null)
    {
        if ($this->validateUserWhoPrint($code, $user_id)) {
            return $this->buildTicket($token);
        } else {
            return ['status' => 'Unauthorized'];
        }
    }

    public function printTickets($codes, $user_id = null, $token = null)
    {
        $codesArray = explode(",", $codes);
        foreach ($codesArray as $code) {
            if (!$this->validateUserWhoPrint($code, $user_id)) {
                return ['status' => 'Unauthorized'];
            }
        }

        $ticketsController = new TicketsController();
        return $ticketsController->renderTicket($codes);
    }


    // Metodo para imprimir ticket para correo masivo
    public function printTicketForMassMail($code, $type = "base64")
    {
        $this->getTicketInfo($code);
        return $this->buildTicket(null, $type);
    }

    public function getTicketInfo($code, $user_id = null)
    {
        $this->ticket = Ticket::where('code_ticket', $code)
            ->with('seat')
            ->with('match_event')
            ->with('user')
            ->with('ticket_main');
        if ($user_id != null) {
            $this->ticket->where('user_id', $user_id);
        }
        $this->ticket = $this->ticket->first();

        if (!$this->ticket) {
            return false;
        }
        $this->rival_team = $this->ticket->match_event->team;
        $this->main_team = Team::where('is_main', true)->first();
        return true;
    }

    public function validateUserWhoPrint($code, $user_id)
    {
        try {
            if ($user_id) {
                return $this->getTicketInfo($code, $user_id);
            } else if (Auth::check() && Auth::user()->rol) {
                $role_user = Auth::user()->rol->id;
                if (!in_array($role_user, $this->__INVALID_ROLE)) {
                    return $this->getTicketInfo($code);
                }
            } else {
                return $this->getTicketInfo($code);
            }
            return false;
        } catch (\Throwable $th) {
            return false;
        }
    }

    public function buildTicket($token, $type = null)
    {
        $ticketsController = new TicketsController();
        return $ticketsController->renderTicket($this->ticket->code_ticket, $type);
    }

    public function pdfOutput($token, $type = null, $fpdf)
    {
        $code = $this->ticket->code_ticket;

        switch ($type) {
            case 'base64':
                $pdfString = $fpdf->Output('S');
                return response($pdfString, 200)
                    ->header('Content-Type', 'application/pdf')
                    ->header('Content-Disposition', 'inline; filename="' . $code . '.pdf"');

            case 'file':
                $filePath = public_path("tickets_tmp/{$code}.pdf");
                $fpdf->Output('F', $filePath);
                return "tickets_tmp/{$code}.pdf";

            case 'content':
                return $fpdf->Output('S');
        }

        if (!$token) {
            $pdfString = $fpdf->Output('S');
            return response($pdfString, 200)
                ->header('Content-Type', 'application/pdf')
                ->header('Content-Disposition', 'inline; filename="' . $code . '.pdf"');
        }

        $pdfPath = public_path("tickets_tmp/{$token}.pdf");
        $fpdf->Output('F', $pdfPath);

        $image = new \Spatie\PdfToImage\Pdf($pdfPath);
        $image->setPage(1)
            ->setOutputFormat('png')
            ->saveImage(public_path("tickets_tmp/{$token}.png"));

        return true;
    }

    public function buildHtmlTicket()
    {
        $printTicketController = new PrintTicketController;
        $utilController = new UtilController;
        $ticket = $this->ticket;
        $qrData = 'data://text/plain;base64,' . $printTicketController->generateQRCode($ticket->code_ticket)['base64'];
        $price = $utilController->formatCurrency($ticket->price);
        $team = CorporateIdentity::value('platform_name');
        $css = file_get_contents(public_path('css/ticket/templates/ticket-template.css'));

        $html = view(
            'tickets.templates.ticket_template',
            ['ticket' => $ticket, 'qrData' => $qrData, 'price' => $price, 'team' => $team, 'css' => $css]
        )->render();

        return $html;
    }


    public function pdfOutputFromHtml($html, $pagesCount, $type)
    {
        $heightPx = Browsershot::html($html)->evaluate('document.body.scrollHeight');
        $heightCm = ($heightPx * 0.264583) / $pagesCount;
        $pdfName = $this->ticket->code_ticket . '.pdf';
        $path = 'tickets_tmp/' . $pdfName;

        // Generar PDF
        $pdfPath = public_path($path);

        Browsershot::html($html)
            ->margins(0, 0, 0, 0)
            ->paperSize(90, $heightCm, 'mm')
            ->showBackground()
            ->save($pdfPath);


        if ($type == 'file') {
            return $path;
        }


        return response()->file($pdfPath, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'inline; filename="' . $pdfName . '"'
        ]);
    }

    private function buildPdfTicket($fpdf, $cacheFpdf = true)
    {
        $parameters = Parameter::select('presuscription', 'url_ticket_backgroung', 'url_ticket_sponsor')->first();
        $parameters->hideDataSeat = $this->matchEventsController->validateSaleByCapacity($this->ticket->match_event->id);
        // Inicio variables
        $tournament = $this->ticket->match_event->season->tournament->name;
        $tournament = $this->cleanString($tournament);
        $tournament = urldecode($tournament);
        $date_name = $this->ticket->match_event->date_name;
        $date_event = Carbon::parse($this->ticket->match_event->event_start)->format('d-m-Y');
        $abbr_local = $this->main_team->abbreviation;
        $abbr_visit = $this->rival_team->abbreviation;
        $code = $this->ticket->code_ticket;
        $time = substr($this->ticket->match_event->event_start, 11, 5);
        $tribune = $this->ticket->seat->zone->zone->is_saleable ? $this->ticket->seat->zone->zone->name : $this->ticket->seat->zone->name;
        $zones = explode("_", $this->ticket->zone);
        $zone = $zones[0];
        $zone = str_replace($tribune, "", $zone);
        $zone = trim($zone);
        $ticketZone = '';
        if (count($zones) > 1)
            $ticketZone = $zones[1];
        else {
            $zones = explode(" ", $this->ticket->zone);
            if ((count($zones) > 1) && str_contains($zones[0], $tribune)) {
                $zone = $zones[1];
            } else {
                $zone = $zones[0];
            }
            $ticketZone = count(explode($zone . ' ', $this->ticket->zone)) > 1 ? explode($zone . ' ', $this->ticket->zone)[1] : '';
            $zone = substr($zone, 0, 3);
        }
        $tribune = strtoupper($tribune);
        $ticketRow = $this->ticket->letter_seat;
        $ticketSeat = $this->ticket->code_seat;
        $user = explode(' ', $this->ticket->user->first_name)[0] . ' ' . explode(' ', $this->ticket->user->last_name)[0];
        $user = $this->cleanString($user);
        $document = $this->ticket->user->document;
        $price = $this->ticket->price ?? 0;
        $stadium = $this->ticket->match_event->stadium_to_play;
        $stadium = $this->cleanString($stadium);
        $stadium = urldecode($stadium);

        // DYNAMIC IMAGES
        $url_main_team = config('filesystems.disks.s3.url') . '/teams/' . $this->main_team->logo;
        $url_tournament = $this->ticket->match_event->season->tournament->logo ? config('filesystems.disks.s3.url') . '/tournaments/' . $this->ticket->match_event->season->tournament->logo : null;
        $url_organizer = $this->ticket->match_event->season->tournament->tournament_organizer->logo ? config('filesystems.disks.s3.url') . '/organizer/' . $this->ticket->match_event->season->tournament->tournament_organizer->logo : null;
        $url_ticket_background = $this->ticket->match_event->season->tournament->ticket_background ? $this->ticket->match_event->season->tournament->ticket_background : $parameters->url_ticket_backgroung;
        $url_footer_sponsors = $this->ticket->match_event->season->tournament->footer_sponsors ? config('filesystems.disks.s3.url') . '/tournaments/' . $this->ticket->match_event->season->tournament->footer_sponsors : $parameters->url_ticket_sponsor;
        $qrData = $this->generateQRCode($code);
        // Fin variables

        // Formato Ticket
        $fpdf->AddPage();
        $fpdf->SetFillColor(130, 130, 130);
        $fpdf->Rect(0, 0, 210, 297, 'F');

        if (!$this->url_main_team && $url_main_team) {
            $mainTeams = explode("/", $url_main_team);

            $this->url_main_team = 'tickets_tmp/main_team_' . end($mainTeams);
            if (!file_exists($this->url_main_team) || file_get_contents($this->url_main_team) == '') {
                touch($this->url_main_team);
                copy($url_main_team, $this->url_main_team);
            }
        }
        if (!$this->url_tournament && $url_tournament) {
            $tournaments = explode("/", $url_tournament);

            $this->url_tournament = 'tickets_tmp/tournament_' . end($tournaments);
            if (!file_exists($this->url_tournament) || file_get_contents($this->url_tournament) == '') {
                touch($this->url_tournament);
                copy($url_tournament, $this->url_tournament);
            }
        }
        if (!$this->url_organizer && $url_organizer) {
            $organizers = explode("/", $url_organizer);

            $this->url_organizer = 'tickets_tmp/organizer_' . end($organizers);
            if (!file_exists($this->url_organizer) || file_get_contents($this->url_organizer) == '') {
                touch($this->url_organizer);
                copy($url_organizer, $this->url_organizer);
            }
        }
        if (!$this->url_ticket_background && $url_ticket_background) {
            $backgrounds = explode("/", $url_ticket_background);

            $this->url_ticket_background = 'tickets_tmp/background_' . end($backgrounds);
            if (!file_exists($this->url_ticket_background) || file_get_contents($this->url_ticket_background) == '') {
                touch($this->url_ticket_background);
                copy($url_ticket_background, $this->url_ticket_background);
            }
        }
        if (!$this->url_footer_sponsors && $url_footer_sponsors) {
            $footers = explode("/", $url_footer_sponsors);

            $this->url_footer_sponsors = 'tickets_tmp/footer_sponsors_' . end($footers);
            if (!file_exists($this->url_footer_sponsors) || file_get_contents($this->url_footer_sponsors) == '') {
                touch($this->url_footer_sponsors);
                copy($url_footer_sponsors, $this->url_footer_sponsors);
            }
        }

        $fpdf = $this->buildImage($fpdf, $this->url_main_team, $this->url_tournament, $this->url_organizer, $qrData, $this->url_ticket_background, $this->url_footer_sponsors, $cacheFpdf);
        $fpdf = $this->addFonts($fpdf);

        if ($this->ticket->ticket_type_id != TicketTypesEnum::COMPLIMENTARY) {
            $fpdf->SetFont('organetto-regularsemiext', '', 7);
            $fpdf->Cell(0, -14, ('NOMBRE: ' . $user . ' - ID: ' . $document), 0, 0, 'C');  // NOMBRE TITULAR
        }

        $fpdf->SetX(10);
        $fpdf->SetFont('organetto-ultraboldsemiext', '', 11);
        $fpdf->Cell(0, 32, $tournament, 0, 0, 'C');                 // TORNEO
        $fpdf->SetX(11.5);
        $fpdf->SetFont('organetto-lightsemiext', '', 11);
        $fpdf->Cell(0, 42, $date_name, 0, 0, 'C');                  // EQUIPOS
        if ($this->ticket->match_event->stadium_to_play) {
            $fpdf->SetFont('organetto-regularsemiext', '', 9);
            $fpdf->SetX(11.5);
            $fpdf->Cell(0, 52, ('Estadio: ' . $stadium), 0, 0, 'C');     // ESTADIO
        }

        $fpdf->SetFont('organetto-regularsemiext', '', 11);
        $fpdf->Text(12, 45, 'FECHA DEL PARTIDO:');
        $fpdf->SetFont('organetto-ultraboldsemiext', '', 11);
        $fpdf->Text(68.5, 45, $date_event);

        $fpdf->SetFont('organetto-boldsemiext', '', 30);
        $fpdf->Text(15, 58, $abbr_local);                           // EQUIPO LOCAL
        $fpdf->Text(70, 58, $abbr_visit);                           // EQUIPO VISITANTE
        $fpdf->SetFont('organetto-regularsemiext', '', 21);
        $fpdf->Text(48, 57, 'VS.');
        $specialText = mb_convert_encoding($this->ticket->special_text, 'ISO-8859-1', 'UTF-8');

        if (!$parameters->hideDataSeat) {
            $fpdf->SetFont('organetto-regularsemiext', '', 9);
            $fpdf->Text(4, 68, 'HORA:');
            $fpdf->Text(25, 68, 'TRIBUNA:');
            $fpdf->Text(50, 68, 'SECTOR:');
            $fpdf->Text(75, 68, 'FILA:');
            $fpdf->Text(93, 68, 'SILLA');

            $fpdf->SetFont('organetto-ultraboldsemiext', '', 12);
            // HORA DEL PARTIDO
            $fpdf->Text(3, 74, $time);
            // TRIBUNA
            if ($zone) {
                if ($specialText) {
                    $tribune = substr($tribune, 0, 3);
                    $fpdf->Text(22, 74, $tribune . '-' . $zone);
                } else {
                    $fpdf->SetFont('organetto-ultraboldsemiext', '', 10);
                    if (str_contains(strtolower($tribune), strtolower($zone))) {
                        $fpdf->Text(22, 74, $zone);
                    } else {
                        $fpdf->Text(22, 74, $tribune . '-');
                        $fpdf->Text(22, 78, $zone);
                    }
                }
            } else
                $fpdf->Text(22, 74, $tribune);

            if ($specialText) {
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 25);
                $fpdf->SetTextColor(255, 0, 0);
                $mid_x = (($fpdf->GetPageWidth() / 2) - ($fpdf->GetStringWidth($specialText) / 2));
                // TEXTO ESPECIAL BOLETA
                $fpdf->Text($mid_x, 84, $specialText);
                $fpdf->SetTextColor(0, 0, 0);
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 12);
            }
            // SECTOR
            if (strlen($ticketZone) > 9) {
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 7);
                $ticketZoneWords = explode(" ", $ticketZone);
                $chunkSize = ceil(count($ticketZoneWords) / 2);
                $chunks = array_chunk($ticketZoneWords, $chunkSize);
                $firstArray = $chunks[0]; // First array
                $upperTicketZoneText = implode(" ", $firstArray);
                $secondArray = $chunks[1] ?? []; // Second array
                $lowerTicketZoneText = implode(" ", $secondArray);

                $fpdf->Text(50, 72, $upperTicketZoneText);
                $fpdf->Text(50, 76, $lowerTicketZoneText);
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 12);
            } else if (strlen($ticketZone) > 2 && strlen($ticketZone) <= 9) {
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 10);
                $fpdf->Text(50, 74, $ticketZone);
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 12);
            } else {
                $fpdf->Text(57, 74, $ticketZone);
            }
            // FILA
            $fpdf->Text(78, 74, $ticketRow);
            // SILLA
            $fpdf->Text(95, 74, $ticketSeat);
        } else {
            $fpdf->SetFont('organetto-regularsemiext', '', 9);
            $fpdf->Text(35, 68, 'HORA:');
            $fpdf->Text(57, 68, 'TRIBUNA');

            $fpdf->SetFont('organetto-ultraboldsemiext', '', 12);
            $fpdf->Text(35, 74, $time);                             // HORA DEL PARTIDO
            if (strlen($tribune) > 18) {
                $tribuneWords = explode(" ", $tribune);
                $chunkSize = ceil(count($tribuneWords) / 2);
                $chunks = array_chunk($tribuneWords, $chunkSize);
                $firstArray = $chunks[0]; // First array
                $upperTribuneText = implode(" ", $firstArray);
                $secondArray = $chunks[1]; // Second array
                $lowerTribuneText = implode(" ", $secondArray);
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 7);
                $fpdf->Text(55, 72, $upperTribuneText);             // TRIBUNA PARTE SUPERIOR
                $fpdf->Text(55, 75, $lowerTribuneText);             // TRIBUNA PARTE INFERIOR
            } else if (strlen($tribune) > 10 && strlen($tribune) <= 18) {
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 9);
                $fpdf->Text(55, 74, $tribune);                   // TRIBUNA
            } else {
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 12);
                $fpdf->Text(55, 74, $tribune);                   // TRIBUNA
            }
            if ($specialText) {
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 25);
                $fpdf->SetTextColor(255, 0, 0);
                $mid_x = (($fpdf->GetPageWidth() / 2) - ($fpdf->GetStringWidth($specialText) / 2));
                $fpdf->Text($mid_x, 84, $specialText);              // TEXTO ESPECIAL BOLETA
                $fpdf->SetTextColor(0, 0, 0);
                $fpdf->SetFont('organetto-ultraboldsemiext', '', 12);
            }
        }

        // Codigo boleta
        $fpdf->SetY(138);
        $fpdf->SetX(30);
        $fpdf->SetFont('organetto-ultraboldsemiext', '', 9);
        $fpdf->Cell(0, 0, 'COD: ', 0, 0, 'L');
        $fpdf->SetX(40);
        $fpdf->SetFont('organetto-regularsemiext', '', 9);
        $fpdf->Cell(0, 0, $code, 0, 0, 'L');                        // CODIGO

        // Precio boleta
        $fpdf->SetFont('organetto-ultraboldsemiext', '', 12);
        $fpdf->Text(35, 145, 'VALOR:');
        $fpdf->SetFont('organetto-regularsemiext', '', 12);
        $fpdf->Text(55, 145, ('$' . $price));                       // PRECIO

        // CUFE
        if ($this->ticket->ticket_main->cufe) {
            $fpdf->SetFont('organetto-regularsemiext', '', 3);
            $fpdf->Text(14, 158, 'CUFE: ' . $this->ticket->ticket_main->cufe);
        }
        return $fpdf;
    }


    public function buildImage($fpdf, $url_main_team, $url_tournament, $url_organizer, $qrData, $url_ticket_background, $url_footer_sponsors, $cacheFpdf = true)
    {
        if (!$this->fpdf) {
            // Background
            if ($url_ticket_background)
                $fpdf->Image($url_ticket_background, 1, 1, 107.8, 157.8);


            $subtractPositionX = 0;
            if (!$url_organizer || !$url_tournament) {
                $subtractPositionX = 11;
            }

            // Tournament Type IMAGE
            if ($url_organizer)
                $fpdf->Image($url_organizer, 22 - $subtractPositionX, 5, 18, 18, 'png');

            // MAIN_TEAM_IMAGE
            if ($url_main_team)
                $fpdf->Image($url_main_team, 45 - $subtractPositionX, 5, 18, 18);

            // TOURNAMENT_IMAGE
            if ($url_tournament)
                $fpdf->Image($url_tournament, 68 - $subtractPositionX, 5, 18, 18);

            // Point line
            // $fpdf->Image(public_path('/img/ticket/point-line.png'), 2, 36.3, 105, 0.5, 'png');

            // Footer
            if ($url_footer_sponsors) {
                $fpdf->Image($url_footer_sponsors, 5, 150, 100, 5);
            }

            if ($cacheFpdf) $this->fpdf = clone $fpdf;
        } else {
            $fpdf = clone $this->fpdf;
        }

        // Escalar el tamaño de imagen en el PDF
        $scaleFactor = 0.25;
        $imageSize = $qrData['size'] * $scaleFactor;

        // Calcular posicion dinamica (centrado horizontal)
        $pageWidth = $fpdf->GetPageWidth();
        $posX = ($pageWidth - $imageSize) / 2;

        // 65% de la altura de la pagina
        $posY = $fpdf->GetPageHeight() * 0.65 - ($imageSize / 2);

        // QR
        $qr_base64_png = $qrData['base64'];
        $pic = 'data://text/plain;base64,' . $qr_base64_png;
        $fpdf->Image($pic, $posX, $posY, $imageSize, $imageSize, 'png');

        // Agregar un borde alrededor de la imagen QR
        $borderThickness = 0.2; // Grosor del borde en mm
        $fpdf->SetLineWidth($borderThickness);
        $fpdf->SetDrawColor(255, 255, 255); // Color blanco
        $fpdf->Rect($posX, $posY, $imageSize, $imageSize);

        return $fpdf;
    }

    public function addFonts($fpdf)
    {
        $fpdf->AddFont('organetto-lightsemiext', '');
        $fpdf->AddFont('organetto-boldsemiext', '');
        $fpdf->AddFont('organetto-regularsemiext', '');
        $fpdf->AddFont('organetto-ultraboldsemiext', '');
        return $fpdf;
    }

    public function generateQRCode($code)
    {
        $qrCodeSize = $this->ticketParametersService->qrCodeSize();
        $qrCode = $code;
        if ($this->ticketParametersService->isDinamicQRType()) {
            $qrCode = $this->ticketService->encryptedQR($code);
        }
        $qr = QrCode::format('png')
            ->size($qrCodeSize)
            ->errorCorrection('M')
            ->generate($qrCode);

        return [
            'base64'    => base64_encode($qr),
            'size'      => $qrCodeSize
        ];
    }

    public function printTicketValidate($token)
    {
        $validate = TokenUserTicket::where('token', $token)->first();
        if ($validate) {
            $code = $validate->code;
            $user_id = $validate->user_id;
            $validate->delete();
            return $this->printTicket($code, $user_id);
        }
        return ":(";
    }

    private function cleanString($text)
    {
        $utf8 = array(
            '/[áàâãªä]/u'   =>   'a',
            '/[ÁÀÂÃÄ]/u'    =>   'A',
            '/[ÍÌÎÏ]/u'     =>   'I',
            '/[íìîï]/u'     =>   'i',
            '/[éèêë]/u'     =>   'e',
            '/[ÉÈÊË]/u'     =>   'E',
            '/[óòôõºö]/u'   =>   'o',
            '/[ÓÒÔÕÖ]/u'    =>   'O',
            '/[úùûü]/u'     =>   'u',
            '/[ÚÙÛÜ]/u'     =>   'U',
            '/ç/'           =>   'c',
            '/Ç/'           =>   'C',
            '/ñ/'           =>   'n',
            '/Ñ/'           =>   'N',
            '/–/'           =>   '-', // UTF-8 hyphen to "normal" hyphen
            '/[’‘‹›‚]/u'    =>   ' ', // Literally a single quote
            '/[“”«»„]/u'    =>   ' ', // Double quote
            '/ /'           =>   ' ', // nonbreaking space (equiv. to 0x160)
        );
        return preg_replace(array_keys($utf8), array_values($utf8), $text);
    }
}