<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Qr_code extends CI_Controller
{
    private $menuLink = 'qr-code';
    private $menuName = 'Generator QR Code';
    private $menuLevels = '1,2,3,4,5,6';
    private $activeFrameTextColor = NULL;

    public function __construct()
    {
        parent::__construct();
        cek_session_login();
        $this->title = info()['title'];
        $this->ensureMenuEntry();
    }

    public function index()
    {
        cek_menu_akses();
        $this->template->set('title', 'Generator QR Code | '.$this->title);
        close_session_for_readonly();
        $html = $this->template->load('main/themes', 'qr_code/index', array(), TRUE);
        output_query_profile_headers('qr-code');
        $this->output->set_output($html);
    }

    public function generate()
    {
        $content = trim((string) $this->input->post('content', FALSE));
        $level = strtoupper((string) $this->input->post('level', TRUE));
        $size = (int) $this->input->post('size', TRUE);
        $foreground = $this->normalizeColor($this->input->post('foreground', TRUE), '#111827');
        $background = $this->normalizeColor($this->input->post('background', TRUE), '#FFFFFF');
        $moduleStyle = (string) $this->input->post('module_style', TRUE);
        $eyeStyle = (string) $this->input->post('eye_style', TRUE);
        $frameStyle = (string) $this->input->post('frame_style', TRUE);
        $frameColor = $this->normalizeColor($this->input->post('frame_color', TRUE), '#111827');
        $frameTextColor = $this->normalizeColor($this->input->post('frame_text_color', TRUE), '#FFFFFF');
        $frameText = preg_replace('/\s+/', ' ', trim(strip_tags((string) $this->input->post('frame_text', FALSE))));

        if ($content === '') {
            return $this->jsonError('Isi QR Code tidak boleh kosong.', 422);
        }
        if (strlen($content) > 1000) {
            return $this->jsonError('Isi QR Code maksimal 1.000 karakter.', 422);
        }
        if (!in_array($level, array('L', 'M', 'Q', 'H'), TRUE)) {
            $level = 'M';
        }
        if (!in_array($moduleStyle, array('square', 'rounded', 'dots', 'diamond'), TRUE)) {
            $moduleStyle = 'square';
        }
        if (!in_array($eyeStyle, array('square', 'rounded', 'circle'), TRUE)) {
            $eyeStyle = 'square';
        }
        $allowedFrames = array('none','envelope','video','tray','sketch','beer','scooter','coffee','chef','pointing','plain_text','separate_footer','speech_bottom','solid_footer','outline_text','smartphone','floating_label','clipboard','triangle_footer','spiral','arrow_script','receipt','bow','script','speech_top','ticket','drink','ribbon','takeaway','cafe_bag');
        if (!in_array($frameStyle, $allowedFrames, TRUE)) {
            $frameStyle = 'none';
        }
        $frameText = substr($frameText !== '' ? $frameText : 'Scan me!', 0, 32);
        $size = min(max($size, 150), 1000);

        $this->load->library('ciqrcode');
        $this->ciqrcode->initialize(array('cacheable' => TRUE, 'quality' => TRUE, 'size' => 2048));
        if (isset($_FILES['logo']) && (int) $_FILES['logo']['error'] !== UPLOAD_ERR_OK) {
            return $this->jsonError('Logo gagal diunggah. Pastikan ukurannya maksimal 2 MB.', 422);
        }
        $hasLogo = isset($_FILES['logo']) && is_uploaded_file($_FILES['logo']['tmp_name']);
        if ($hasLogo) {
            $level = 'H';
        }
        $levelMap = array('L' => QR_ECLEVEL_L, 'M' => QR_ECLEVEL_M, 'Q' => QR_ECLEVEL_Q, 'H' => QR_ECLEVEL_H);
        $previousReporting = error_reporting();
        error_reporting($previousReporting & ~E_DEPRECATED);
        $matrix = QRcode::text($content, FALSE, $levelMap[$level], 1, 0);
        error_reporting($previousReporting);
        if (empty($matrix) || !is_array($matrix)) {
            return $this->jsonError('QR Code gagal dibuat. Silakan periksa isi yang dimasukkan.', 500);
        }
        $frameRatios = array(
            'envelope'=>.42,'tray'=>.42,'beer'=>.44,'scooter'=>.36,'coffee'=>.44,'chef'=>.44,
            'pointing'=>.48,'drink'=>.44,'takeaway'=>.40,'cafe_bag'=>.42,'bow'=>.54,
            'speech_top'=>.56,'ribbon'=>.52,'separate_footer'=>.58
        );
        $qrRatio = isset($frameRatios[$frameStyle]) ? $frameRatios[$frameStyle] : .66;
        $qrSize = $frameStyle === 'none' ? $size : (int) round($size * $qrRatio);
        $canvas = $this->renderMatrix($matrix, $qrSize, $foreground, $background, $moduleStyle, $eyeStyle);
        if ($hasLogo) {
            $logoError = $this->addUploadedLogo($canvas, $_FILES['logo'], $qrSize, $background);
            if ($logoError !== NULL) {
                imagedestroy($canvas);
                return $this->jsonError($logoError, 422);
            }
        }
        if ($frameStyle !== 'none') {
            $framedCanvas = $this->applyFrame($canvas, $size, $frameStyle, $frameText, $frameColor, $background, $frameTextColor);
            imagedestroy($canvas);
            $canvas = $framedCanvas;
        }

        $download = $this->input->post('download', TRUE) === '1';
        $filename = 'qr-code-'.date('Ymd-His').'.png';
        $this->output->set_content_type('image/png');
        $this->output->set_header('Cache-Control: no-store, no-cache, must-revalidate');
        if ($download) {
            $this->output->set_header('Content-Disposition: attachment; filename="'.$filename.'"');
        }

        ob_start();
        imagepng($canvas);
        $png = ob_get_clean();
        imagedestroy($canvas);
        $this->output->set_output($png);
    }

    private function jsonError($message, $status)
    {
        $this->output
            ->set_status_header($status)
            ->set_content_type('application/json', 'utf-8')
            ->set_output(json_encode(array('status' => FALSE, 'message' => $message)));
    }

    private function normalizeColor($color, $fallback)
    {
        $color = strtoupper(trim((string) $color));
        return preg_match('/^#[0-9A-F]{6}$/', $color) ? $color : $fallback;
    }

    private function hexToRgb($color)
    {
        return array(
            hexdec(substr($color, 1, 2)),
            hexdec(substr($color, 3, 2)),
            hexdec(substr($color, 5, 2)),
        );
    }

    private function renderMatrix($matrix, $size, $foreground, $background, $moduleStyle, $eyeStyle)
    {
        $scale = 3;
        $workSize = $size * $scale;
        $canvas = imagecreatetruecolor($workSize, $workSize);
        $bg = imagecolorallocate($canvas, ...$this->hexToRgb($background));
        $fg = imagecolorallocate($canvas, ...$this->hexToRgb($foreground));
        imagefill($canvas, 0, 0, $bg);
        $count = count($matrix);
        $quiet = 4;
        $module = $workSize / ($count + ($quiet * 2));

        for ($y = 0; $y < $count; $y++) {
            for ($x = 0; $x < $count; $x++) {
                if ($this->isEyeArea($x, $y, $count) || !isset($matrix[$y][$x]) || $matrix[$y][$x] !== '1') {
                    continue;
                }
                $this->drawModule($canvas, ($x + $quiet) * $module, ($y + $quiet) * $module, $module, $fg, $moduleStyle);
            }
        }
        $this->drawEye($canvas, $quiet * $module, $quiet * $module, $module, $fg, $bg, $eyeStyle);
        $this->drawEye($canvas, ($quiet + $count - 7) * $module, $quiet * $module, $module, $fg, $bg, $eyeStyle);
        $this->drawEye($canvas, $quiet * $module, ($quiet + $count - 7) * $module, $module, $fg, $bg, $eyeStyle);

        $result = imagecreatetruecolor($size, $size);
        imagecopyresampled($result, $canvas, 0, 0, 0, 0, $size, $size, $workSize, $workSize);
        imagedestroy($canvas);
        return $result;
    }

    private function isEyeArea($x, $y, $count)
    {
        return ($x < 7 && $y < 7) || ($x >= $count - 7 && $y < 7) || ($x < 7 && $y >= $count - 7);
    }

    private function drawModule($image, $x, $y, $module, $color, $style)
    {
        $gap = $style === 'square' ? 0 : $module * .11;
        $x1 = (int) round($x + $gap); $y1 = (int) round($y + $gap);
        $x2 = (int) round($x + $module - $gap); $y2 = (int) round($y + $module - $gap);
        if ($style === 'dots') {
            imagefilledellipse($image, (int) (($x1 + $x2) / 2), (int) (($y1 + $y2) / 2), $x2 - $x1, $y2 - $y1, $color);
        } elseif ($style === 'diamond') {
            $points = array((int)(($x1+$x2)/2),$y1,$x2,(int)(($y1+$y2)/2),(int)(($x1+$x2)/2),$y2,$x1,(int)(($y1+$y2)/2));
            if (PHP_VERSION_ID >= 80100) imagefilledpolygon($image, $points, $color);
            else imagefilledpolygon($image, $points, 4, $color);
        } elseif ($style === 'rounded') {
            $radius = max(2, (int) (($x2 - $x1) * .42));
            $halfRadius = (int) round($radius / 2);
            imagefilledrectangle($image, $x1 + $halfRadius, $y1, $x2 - $halfRadius, $y2, $color);
            imagefilledrectangle($image, $x1, $y1 + $halfRadius, $x2, $y2 - $halfRadius, $color);
            foreach (array(array($x1+$radius/2,$y1+$radius/2),array($x2-$radius/2,$y1+$radius/2),array($x1+$radius/2,$y2-$radius/2),array($x2-$radius/2,$y2-$radius/2)) as $p) imagefilledellipse($image,(int)$p[0],(int)$p[1],$radius,$radius,$color);
        } else {
            imagefilledrectangle($image, $x1, $y1, $x2, $y2, $color);
        }
    }

    private function drawEye($image, $x, $y, $module, $fg, $bg, $style)
    {
        $cx = (int) round($x + 3.5 * $module); $cy = (int) round($y + 3.5 * $module);
        if ($style === 'circle') {
            imagefilledellipse($image, $cx, $cy, (int)(7*$module), (int)(7*$module), $fg);
            imagefilledellipse($image, $cx, $cy, (int)(5*$module), (int)(5*$module), $bg);
            imagefilledellipse($image, $cx, $cy, (int)(3*$module), (int)(3*$module), $fg);
            return;
        }
        $radius = $style === 'rounded' ? $module * 1.15 : 0;
        $this->filledRoundedRect($image, $x, $y, $x+7*$module, $y+7*$module, $radius, $fg);
        $this->filledRoundedRect($image, $x+$module, $y+$module, $x+6*$module, $y+6*$module, max(0,$radius-$module*.2), $bg);
        $this->filledRoundedRect($image, $x+2*$module, $y+2*$module, $x+5*$module, $y+5*$module, max(0,$radius-$module*.4), $fg);
    }

    private function filledRoundedRect($image, $x1, $y1, $x2, $y2, $radius, $color)
    {
        $x1=(int)round($x1);$y1=(int)round($y1);$x2=(int)round($x2);$y2=(int)round($y2);$r=(int)round($radius);
        if ($r <= 0) { imagefilledrectangle($image,$x1,$y1,$x2,$y2,$color); return; }
        imagefilledrectangle($image,$x1+$r,$y1,$x2-$r,$y2,$color); imagefilledrectangle($image,$x1,$y1+$r,$x2,$y2-$r,$color);
        imagefilledellipse($image,$x1+$r,$y1+$r,$r*2,$r*2,$color); imagefilledellipse($image,$x2-$r,$y1+$r,$r*2,$r*2,$color);
        imagefilledellipse($image,$x1+$r,$y2-$r,$r*2,$r*2,$color); imagefilledellipse($image,$x2-$r,$y2-$r,$r*2,$r*2,$color);
    }

    private function addUploadedLogo($canvas, $file, $size, $background)
    {
        if ((int)$file['size'] > 2 * 1024 * 1024) return 'Ukuran logo maksimal 2 MB.';
        $info = @getimagesize($file['tmp_name']);
        if (!$info || !in_array($info['mime'], array('image/png','image/jpeg'), TRUE)) return 'Logo harus berupa JPG, JPEG, atau PNG.';
        $logo = @imagecreatefromstring(file_get_contents($file['tmp_name']));
        if (!$logo) return 'File logo tidak dapat dibaca.';
        $max = (int)round($size * .19); $padding = (int)round($size * .025);
        $ratio = min($max/imagesx($logo), $max/imagesy($logo));
        $w=max(1,(int)(imagesx($logo)*$ratio)); $h=max(1,(int)(imagesy($logo)*$ratio));
        $x=(int)(($size-$w)/2); $y=(int)(($size-$h)/2);
        $bg=imagecolorallocate($canvas, ...$this->hexToRgb($background));
        $this->filledRoundedRect($canvas,$x-$padding,$y-$padding,$x+$w+$padding,$y+$h+$padding,$padding,$bg);
        imagealphablending($canvas, TRUE); imagecopyresampled($canvas,$logo,$x,$y,0,0,$w,$h,imagesx($logo),imagesy($logo)); imagedestroy($logo);
        return NULL;
    }

    private function applyFrame($qr, $size, $style, $text, $frameColor, $background, $textColor = '#FFFFFF')
    {
        $scale = 3;
        $scaledQr = imagecreatetruecolor(imagesx($qr) * $scale, imagesy($qr) * $scale);
        imagecopyresampled($scaledQr, $qr, 0, 0, 0, 0, imagesx($scaledQr), imagesy($scaledQr), imagesx($qr), imagesy($qr));
        $large = $this->renderFrameCanvas($scaledQr, $size * $scale, $style, $text, $frameColor, $background, $textColor);
        imagedestroy($scaledQr);

        $result = imagecreatetruecolor($size, $size);
        imagecopyresampled($result, $large, 0, 0, 0, 0, $size, $size, imagesx($large), imagesy($large));
        imagedestroy($large);
        return $result;
    }

    private function renderFrameCanvas($qr, $size, $style, $text, $frameColor, $background, $textColor)
    {
        $aliases = array(
            'video'=>'screen','tray'=>'hand',
            'plain_text'=>'signature','solid_footer'=>'card','outline_text'=>'panel','script'=>'signature'
        );
        if (isset($aliases[$style])) $style = $aliases[$style];
        $canvas = imagecreatetruecolor($size, $size);
        $bg = imagecolorallocate($canvas, ...$this->hexToRgb($background));
        $ink = imagecolorallocate($canvas, ...$this->hexToRgb($frameColor));
        $txt = imagecolorallocate($canvas, ...$this->hexToRgb($textColor));
        $this->activeFrameTextColor = $txt;
        imagefill($canvas, 0, 0, $bg);
        $line = max(3, (int) round($size * .009));
        imagesetthickness($canvas, $line);
        $qrW = imagesx($qr); $qrH = imagesy($qr);
        $x = (int) round(($size - $qrW) / 2);
        $y = (int) round($size * .09);

        if (in_array($style, array('screen','hand','sketch','beer','scooter','coffee','chef','pointing','solid','phone','panel','smartphone','clipboard','tilted','tape','signature','ribbon','gift','spiral','arrow_script','drink','takeaway','round_bag'), TRUE)) {
            if ($style === 'screen') {
                $this->filledRoundedRect($canvas,$size*.12,$size*.06,$size*.88,$size*.92,$size*.025,$ink);
                $this->filledRoundedRect($canvas,$size*.12+$line,$size*.06+$line,$size*.88-$line,$size*.92-$line,$size*.02,$bg);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.12);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
                imagefilledrectangle($canvas,(int)($size*.12),(int)($size*.80),(int)($size*.88),(int)($size*.92),$ink);$this->drawCenteredText($canvas,$text,(int)($size*.835),$bg);
            } elseif ($style === 'hand') {
                $this->filledRoundedRect($canvas,$size*.27,$size*.04,$size*.73,$size*.66,$size*.025,$ink);$this->filledRoundedRect($canvas,$size*.27+$line,$size*.04+$line,$size*.73-$line,$size*.66-$line,$size*.02,$bg);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.10);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.58),$txt);
                imageline($canvas,(int)($size*.18),(int)($size*.70),(int)($size*.82),(int)($size*.70),$ink);imageline($canvas,(int)($size*.12),(int)($size*.73),(int)($size*.50),(int)($size*.88),$ink);imageline($canvas,(int)($size*.50),(int)($size*.88),(int)($size*.82),(int)($size*.72),$ink);imageline($canvas,(int)($size*.12),(int)($size*.73),(int)($size*.05),(int)($size*.86),$ink);imageline($canvas,(int)($size*.05),(int)($size*.86),(int)($size*.22),(int)($size*.96),$ink);
            } elseif ($style === 'sketch' || $style === 'tape') {
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.14);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
                imagerectangle($canvas,(int)($size*.14),(int)($size*.08),(int)($size*.86),(int)($size*.90),$ink);
                foreach(array(array(.08,.13,.24,.03),array(.76,.04,.92,.15),array(.07,.82,.25,.95),array(.76,.94,.93,.82)) as $d) imageline($canvas,(int)($size*$d[0]),(int)($size*$d[1]),(int)($size*$d[2]),(int)($size*$d[3]),$ink);
                $this->drawCenteredText($canvas,$text,(int)($size*.82),$ink);
            } elseif ($style === 'beer') {
                $this->filledRoundedRect($canvas,$size*.12,$size*.12,$size*.76,$size*.88,$size*.08,$ink);$this->filledRoundedRect($canvas,$size*.12+$line,$size*.12+$line,$size*.76-$line,$size*.88-$line,$size*.07,$bg);
                imagearc($canvas,(int)($size*.80),(int)($size*.48),(int)($size*.30),(int)($size*.36),270,90,$ink);imagearc($canvas,(int)($size*.80),(int)($size*.48-$line),(int)($size*.20),(int)($size*.25),270,90,$ink);
                imagefilledellipse($canvas,(int)($size*.28),(int)($size*.12),(int)($size*.26),(int)($size*.18),$bg);imageellipse($canvas,(int)($size*.28),(int)($size*.12),(int)($size*.26),(int)($size*.18),$ink);imagefilledellipse($canvas,(int)($size*.46),(int)($size*.08),(int)($size*.28),(int)($size*.20),$bg);imageellipse($canvas,(int)($size*.46),(int)($size*.08),(int)($size*.28),(int)($size*.20),$ink);imagefilledellipse($canvas,(int)($size*.63),(int)($size*.13),(int)($size*.24),(int)($size*.17),$bg);imageellipse($canvas,(int)($size*.63),(int)($size*.13),(int)($size*.24),(int)($size*.17),$ink);
                $x=(int)($size*.17);$y=(int)($size*.20);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.78),$ink);
            } elseif ($style === 'coffee') {
                for($i=0;$i<3;$i++) imagearc($canvas,(int)($size*(.36+$i*.12)),(int)($size*.13),(int)($size*.10),(int)($size*.22),190,345,$ink);
                $this->filledRoundedRect($canvas,$size*.12,$size*.20,$size*.78,$size*.82,$size*.05,$ink);$this->filledRoundedRect($canvas,$size*.12+$line,$size*.20+$line,$size*.78-$line,$size*.82-$line,$size*.04,$bg);imagearc($canvas,(int)($size*.82),(int)($size*.48),(int)($size*.27),(int)($size*.30),270,90,$ink);imageline($canvas,(int)($size*.08),(int)($size*.84),(int)($size*.86),(int)($size*.84),$ink);
                $x=(int)($size*.17);$y=(int)($size*.24);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.88),$txt);
            } elseif ($style === 'scooter') {
                $this->filledRoundedRect($canvas,$size*.08,$size*.08,$size*.46,$size*.60,$size*.02,$ink);$this->filledRoundedRect($canvas,$size*.08+$line,$size*.08+$line,$size*.46-$line,$size*.60-$line,$size*.015,$bg);
                $x=(int)($size*.09);$y=(int)($size*.11);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);imagefilledrectangle($canvas,(int)($size*.08),(int)($size*.53),(int)($size*.46),(int)($size*.62),$ink);$this->drawTextInBox($canvas,$text,$size*.08,$size*.46,$size*.54,$txt);
                imagefilledellipse($canvas,(int)($size*.32),(int)($size*.82),(int)($size*.16),(int)($size*.16),$ink);imagefilledellipse($canvas,(int)($size*.78),(int)($size*.82),(int)($size*.16),(int)($size*.16),$ink);imagefilledellipse($canvas,(int)($size*.32),(int)($size*.82),(int)($size*.075),(int)($size*.075),$bg);imagefilledellipse($canvas,(int)($size*.78),(int)($size*.82),(int)($size*.075),(int)($size*.075),$bg);
                $body=array((int)($size*.24),(int)($size*.72),(int)($size*.64),(int)($size*.72),(int)($size*.76),(int)($size*.60),(int)($size*.82),(int)($size*.74),(int)($size*.68),(int)($size*.78),(int)($size*.34),(int)($size*.78));if(PHP_VERSION_ID>=80100)imagefilledpolygon($canvas,$body,$ink);else imagefilledpolygon($canvas,$body,6,$ink);
                imageline($canvas,(int)($size*.68),(int)($size*.72),(int)($size*.75),(int)($size*.36),$ink);imageline($canvas,(int)($size*.70),(int)($size*.38),(int)($size*.87),(int)($size*.38),$ink);imagefilledrectangle($canvas,(int)($size*.51),(int)($size*.64),(int)($size*.66),(int)($size*.68),$ink);
            } elseif ($style === 'chef') {
                imagefilledellipse($canvas,(int)($size*.35),(int)($size*.13),(int)($size*.28),(int)($size*.20),$ink);imagefilledellipse($canvas,(int)($size*.50),(int)($size*.09),(int)($size*.30),(int)($size*.24),$ink);imagefilledellipse($canvas,(int)($size*.65),(int)($size*.13),(int)($size*.28),(int)($size*.20),$ink);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.22);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.84),$ink);
            } elseif ($style === 'pointing') {
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.20);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);imagerectangle($canvas,(int)($size*.22),(int)($size*.16),(int)($size*.78),(int)($size*.76),$ink);
                imageline($canvas,(int)($size*.08),(int)($size*.70),(int)($size*.22),(int)($size*.60),$ink);imageline($canvas,(int)($size*.78),(int)($size*.28),(int)($size*.92),(int)($size*.18),$ink);$this->drawCenteredText($canvas,$text,(int)($size*.82),$txt);
            } elseif ($style === 'phone' || $style === 'smartphone') {
                $this->filledRoundedRect($canvas,$size*.18,$size*.03,$size*.82,$size*.97,$size*.05,$ink);$this->filledRoundedRect($canvas,$size*.18+$line,$size*.03+$line,$size*.82-$line,$size*.97-$line,$size*.04,$bg);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.16);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);imagefilledellipse($canvas,(int)($size*.50),(int)($size*.91),(int)($size*.045),(int)($size*.045),$ink);$this->drawCenteredText($canvas,$text,(int)($size*.80),$ink);
            } elseif ($style === 'clipboard') {
                imagerectangle($canvas,(int)($size*.13),(int)($size*.10),(int)($size*.87),(int)($size*.94),$ink);$this->filledRoundedRect($canvas,$size*.34,$size*.03,$size*.66,$size*.18,$size*.03,$ink);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.20);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.84),$ink);
            } elseif ($style === 'spiral') {
                imagerectangle($canvas,(int)($size*.16),(int)($size*.08),(int)($size*.88),(int)($size*.94),$ink);for($i=0;$i<5;$i++){ $yy=(int)($size*(.20+$i*.14));imageline($canvas,(int)($size*.10),$yy,(int)($size*.22),$yy,$ink);imagefilledellipse($canvas,(int)($size*.16),$yy,(int)($size*.035),(int)($size*.035),$ink); }
                $x=(int)($size*.25);$y=(int)($size*.18);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);imagefilledrectangle($canvas,(int)($size*.16),(int)($size*.82),(int)($size*.88),(int)($size*.94),$ink);$this->drawCenteredText($canvas,$text,(int)($size*.85),$txt);
            } elseif ($style === 'arrow_script') {
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.12);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);imagearc($canvas,(int)($size*.23),(int)($size*.65),(int)($size*.20),(int)($size*.33),90,265,$ink);imageline($canvas,(int)($size*.16),(int)($size*.49),(int)($size*.11),(int)($size*.58),$ink);imageline($canvas,(int)($size*.16),(int)($size*.49),(int)($size*.23),(int)($size*.55),$ink);$this->drawCenteredText($canvas,$text,(int)($size*.80),$txt);
            } elseif ($style === 'drink') {
                $poly=array((int)($size*.18),(int)($size*.18),(int)($size*.82),(int)($size*.18),(int)($size*.76),(int)($size*.91),(int)($size*.24),(int)($size*.91));if(PHP_VERSION_ID>=80100)imagefilledpolygon($canvas,$poly,$ink);else imagefilledpolygon($canvas,$poly,4,$ink);
                imagefilledrectangle($canvas,(int)($size*.21),(int)($size*.24),(int)($size*.79),(int)($size*.69),$bg);imageline($canvas,(int)($size*.42),(int)($size*.18),(int)($size*.35),(int)($size*.02),$ink);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.27);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.80),$txt);
            } elseif ($style === 'takeaway') {
                imagerectangle($canvas,(int)($size*.10),(int)($size*.34),(int)($size*.34),(int)($size*.87),$ink);imagefilledrectangle($canvas,(int)($size*.07),(int)($size*.30),(int)($size*.37),(int)($size*.36),$ink);
                $poly=array((int)($size*.28),(int)($size*.18),(int)($size*.82),(int)($size*.18),(int)($size*.76),(int)($size*.92),(int)($size*.34),(int)($size*.92));if(PHP_VERSION_ID>=80100)imagefilledpolygon($canvas,$poly,$ink);else imagefilledpolygon($canvas,$poly,4,$ink);$inner=array((int)($size*.30),(int)($size*.23),(int)($size*.80),(int)($size*.23),(int)($size*.75),(int)($size*.87),(int)($size*.35),(int)($size*.87));if(PHP_VERSION_ID>=80100)imagefilledpolygon($canvas,$inner,$bg);else imagefilledpolygon($canvas,$inner,4,$bg);imagefilledrectangle($canvas,(int)($size*.24),(int)($size*.08),(int)($size*.86),(int)($size*.20),$ink);
                $x=(int)($size*.39);$y=(int)($size*.29);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);imagefilledrectangle($canvas,(int)($size*.30),(int)($size*.74),(int)($size*.80),(int)($size*.88),$ink);$this->drawCenteredText($canvas,$text,(int)($size*.77),$txt);
            } elseif ($style === 'ribbon') {
                imagerectangle($canvas,(int)($size*.20),(int)($size*.06),(int)($size*.80),(int)($size*.78),$ink);$x=(int)(($size-$qrW)/2);$y=(int)($size*.12);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
                $leftTail=array((int)($size*.05),(int)($size*.66),(int)($size*.23),(int)($size*.66),(int)($size*.23),(int)($size*.84),(int)($size*.05),(int)($size*.84),(int)($size*.11),(int)($size*.75));$rightTail=array((int)($size*.77),(int)($size*.66),(int)($size*.95),(int)($size*.66),(int)($size*.89),(int)($size*.75),(int)($size*.95),(int)($size*.84),(int)($size*.77),(int)($size*.84));if(PHP_VERSION_ID>=80100){imagefilledpolygon($canvas,$leftTail,$ink);imagefilledpolygon($canvas,$rightTail,$ink);}else{imagefilledpolygon($canvas,$leftTail,5,$ink);imagefilledpolygon($canvas,$rightTail,5,$ink);}
                $this->filledRoundedRect($canvas,$size*.14,$size*.64,$size*.86,$size*.84,$size*.015,$ink);$this->drawCenteredText($canvas,$text,(int)($size*.69),$txt);
            } elseif ($style === 'gift') {
                imagerectangle($canvas,(int)($size*.10),(int)($size*.16),(int)($size*.90),(int)($size*.94),$ink);imagefilledrectangle($canvas,(int)($size*.46),(int)($size*.10),(int)($size*.54),(int)($size*.94),$ink);
                imagearc($canvas,(int)($size*.38),(int)($size*.10),(int)($size*.24),(int)($size*.18),180,360,$ink);imagearc($canvas,(int)($size*.62),(int)($size*.10),(int)($size*.24),(int)($size*.18),180,360,$ink);$x=(int)(($size-$qrW)/2);$y=(int)($size*.22);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
            } elseif ($style === 'store') {
                imagerectangle($canvas,(int)($size*.12),(int)($size*.20),(int)($size*.88),(int)($size*.92),$ink);for($i=0;$i<5;$i++) imagefilledrectangle($canvas,(int)($size*(.10+$i*.16)),(int)($size*.08),(int)($size*(.18+$i*.16)),(int)($size*.22),$i%2?$bg:$ink);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.27);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.84),$ink);
            } elseif ($style === 'round_bag') {
                imageellipse($canvas,(int)($size*.50),(int)($size*.55),(int)($size*.82),(int)($size*.78),$ink);imagearc($canvas,(int)($size*.50),(int)($size*.20),(int)($size*.35),(int)($size*.28),180,360,$ink);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.25);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.82),$ink);
            } elseif ($style === 'suitcase') {
                $this->filledRoundedRect($canvas,$size*.12,$size*.16,$size*.88,$size*.92,$size*.06,$ink);$this->filledRoundedRect($canvas,$size*.12+$line,$size*.16+$line,$size*.88-$line,$size*.92-$line,$size*.05,$bg);imagearc($canvas,(int)($size*.50),(int)($size*.18),(int)($size*.34),(int)($size*.28),180,360,$ink);
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.27);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.82),$ink);
            } elseif ($style === 'tilted') {
                $rotated = imagerotate($qr, 5, $bg);
                $rw=imagesx($rotated);$rh=imagesy($rotated);$x=(int)(($size-$rw)/2);$y=(int)($size*.10);imagecopy($canvas,$rotated,$x,$y,0,0,$rw,$rh);imagedestroy($rotated);
                imageline($canvas,(int)($size*.12),(int)($size*.16),(int)($size*.27),(int)($size*.03),$ink);imageline($canvas,(int)($size*.73),(int)($size*.91),(int)($size*.90),(int)($size*.78),$ink);$this->drawCenteredText($canvas,$text,(int)($size*.86),$ink);
            } elseif ($style === 'signature') {
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.12);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
                $this->drawCenteredText($canvas,$text,(int)($size*.82),$ink);imageline($canvas,(int)($size*.26),(int)($size*.88),(int)($size*.74),(int)($size*.88),$ink);
            } else {
                $x=(int)(($size-$qrW)/2);$y=(int)($size*.12);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
                if ($style==='solid') imagefilledrectangle($canvas,(int)($size*.12),(int)($size*.82),(int)($size*.88),(int)($size*.94),$ink);
                else imagerectangle($canvas,(int)($size*.12),(int)($size*.06),(int)($size*.88),(int)($size*.94),$ink);
                $this->drawCenteredText($canvas,$text,(int)($size*.85),$style==='solid'?$bg:$ink);
            }
        } elseif ($style === 'caption') {
            $this->filledRoundedRect($canvas, $size*.08, $size*.04, $size*.92, $size*.96, $size*.045, $ink);
            $this->filledRoundedRect($canvas, $size*.08+$line, $size*.04+$line, $size*.92-$line, $size*.96-$line, $size*.04, $bg);
            imagecopy($canvas, $qr, $x, $y, 0, 0, $qrW, $qrH);
            $this->drawCenteredText($canvas, $text, (int)($size*.84), $ink);
        } elseif ($style === 'top_label') {
            $this->filledRoundedRect($canvas, $size*.07, $size*.08, $size*.93, $size*.94, $size*.035, $ink);
            $this->filledRoundedRect($canvas, $size*.07+$line, $size*.08+$line, $size*.93-$line, $size*.94-$line, $size*.03, $bg);
            $this->filledRoundedRect($canvas, $size*.27, $size*.03, $size*.73, $size*.15, $size*.025, $ink);
            $this->drawCenteredText($canvas, $text, (int)($size*.075), $bg);
            $y = (int)($size*.20); imagecopy($canvas, $qr, $x, $y, 0, 0, $qrW, $qrH);
        } elseif ($style === 'card') {
            $this->filledRoundedRect($canvas, $size*.08, $size*.04, $size*.92, $size*.96, $size*.07, $ink);
            $this->filledRoundedRect($canvas, $size*.08+$line, $size*.04+$line, $size*.92-$line, $size*.96-$line, $size*.06, $bg);
            $y=(int)($size*.13); imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
            imagefilledrectangle($canvas,(int)($size*.20),(int)($size*.84),(int)($size*.80),(int)($size*.93),$ink);
            $this->drawCenteredText($canvas,$text,(int)($size*.865),$bg);
        } elseif ($style === 'envelope') {
            // Amplop memakai stroke lebih tebal daripada frame lain agar tetap tegas
            // setelah kanvas 500px diperkecil di area pratinjau.
            $envelopeLine=max($line,(int)round($size*.014));
            imagesetthickness($canvas,$envelopeLine);
            $left=(int)($size*.15);$right=(int)($size*.85);$top=(int)($size*.44);$bottom=(int)($size*.95);$apexX=(int)($size*.50);$apexY=(int)($size*.65);
            // Bagian belakang amplop digambar lebih dahulu agar tertutup oleh kartu QR.
            imageline($canvas,$left,$top,$left,$bottom,$ink);imageline($canvas,$right,$top,$right,$bottom,$ink);imageline($canvas,$left,$bottom,$right,$bottom,$ink);
            imageline($canvas,$left,$top,$apexX,$apexY,$ink);imageline($canvas,$right,$top,$apexX,$apexY,$ink);

            $this->filledRoundedRect($canvas,$size*.24,$size*.04,$size*.76,$size*.69,$size*.025,$ink);
            $this->filledRoundedRect($canvas,$size*.24+$envelopeLine,$size*.04+$envelopeLine,$size*.76-$envelopeLine,$size*.69-$envelopeLine,$size*.018,$bg);
            $x=(int)(($size-$qrW)/2);$y=(int)($size*.11);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);

            // Flap depan berada di depan kartu, tetapi tidak memasuki area QR.
            imageline($canvas,$left,$bottom,$apexX,$apexY,$ink);imageline($canvas,$right,$bottom,$apexX,$apexY,$ink);
            $this->drawCenteredText($canvas,$text,(int)($size*.835),$ink);
        } elseif ($style === 'separate_footer') {
            imagerectangle($canvas,(int)($size*.12),(int)($size*.05),(int)($size*.88),(int)($size*.72),$ink);
            $x=(int)(($size-$qrW)/2);$y=(int)($size*.09);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
            $this->filledRoundedRect($canvas,$size*.12,$size*.77,$size*.88,$size*.94,$size*.02,$ink);$this->drawCenteredText($canvas,$text,(int)($size*.81),$txt);
        } elseif ($style === 'bow') {
            imagerectangle($canvas,(int)($size*.16),(int)($size*.15),(int)($size*.84),(int)($size*.94),$ink);imagefilledrectangle($canvas,(int)($size*.16),(int)($size*.82),(int)($size*.84),(int)($size*.94),$ink);
            $x=(int)(($size-$qrW)/2);$y=(int)($size*.22);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.85),$txt);
            imagefilledellipse($canvas,(int)($size*.38),(int)($size*.12),(int)($size*.30),(int)($size*.18),$ink);imagefilledellipse($canvas,(int)($size*.62),(int)($size*.12),(int)($size*.30),(int)($size*.18),$ink);imagefilledrectangle($canvas,(int)($size*.46),(int)($size*.06),(int)($size*.54),(int)($size*.20),$ink);imageline($canvas,(int)($size*.30),(int)($size*.10),(int)($size*.16),(int)($size*.04),$ink);imageline($canvas,(int)($size*.70),(int)($size*.10),(int)($size*.84),(int)($size*.04),$ink);
        } elseif ($style === 'speech_top') {
            $this->filledRoundedRect($canvas,$size*.12,$size*.04,$size*.88,$size*.20,$size*.025,$ink);$this->drawCenteredText($canvas,$text,(int)($size*.075),$txt);
            $tri=array((int)($size*.68),(int)($size*.20),(int)($size*.76),(int)($size*.20),(int)($size*.73),(int)($size*.26));if(PHP_VERSION_ID>=80100)imagefilledpolygon($canvas,$tri,$ink);else imagefilledpolygon($canvas,$tri,3,$ink);
            imagerectangle($canvas,(int)($size*.14),(int)($size*.27),(int)($size*.86),(int)($size*.94),$ink);$x=(int)(($size-$qrW)/2);$y=(int)($size*.32);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
        } elseif ($style === 'cafe_bag') {
            imagefilledellipse($canvas,(int)($size*.50),(int)($size*.48),(int)($size*.88),(int)($size*.38),$ink);imagefilledellipse($canvas,(int)($size*.50),(int)($size*.48),(int)($size*.80),(int)($size*.28),$bg);imagefilledellipse($canvas,(int)($size*.50),(int)($size*.83),(int)($size*.88),(int)($size*.32),$ink);imagefilledellipse($canvas,(int)($size*.50),(int)($size*.80),(int)($size*.80),(int)($size*.22),$bg);
            $this->filledRoundedRect($canvas,$size*.20,$size*.20,$size*.80,$size*.82,$size*.05,$ink);$this->filledRoundedRect($canvas,$size*.20+$line,$size*.20+$line,$size*.80-$line,$size*.82-$line,$size*.04,$bg);imagefilledellipse($canvas,(int)($size*.50),(int)($size*.13),(int)($size*.18),(int)($size*.12),$ink);
            $x=(int)(($size-$qrW)/2);$y=(int)($size*.29);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);imagefilledrectangle($canvas,(int)($size*.20),(int)($size*.70),(int)($size*.80),(int)($size*.82),$ink);$this->drawCenteredText($canvas,$text,(int)($size*.72),$txt);
        } elseif ($style === 'floating_label') {
            $x=(int)(($size-$qrW)/2);$y=(int)($size*.08);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
            $this->filledRoundedRect($canvas,$size*.16,$size*.78,$size*.84,$size*.94,$size*.025,$ink);
            $this->drawCenteredText($canvas,$text,(int)($size*.82),$txt);
        } elseif ($style === 'speech_bottom' || $style === 'triangle_footer') {
            imagerectangle($canvas,(int)($size*.10),(int)($size*.06),(int)($size*.90),(int)($size*.73),$ink);
            $x=(int)(($size-$qrW)/2);$y=(int)($size*.10);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
            $this->filledRoundedRect($canvas,$size*.10,$size*.78,$size*.90,$size*.95,$size*.02,$ink);
            $tri=array((int)($size*.44),(int)($size*.78),(int)($size*.50),(int)($size*.72),(int)($size*.56),(int)($size*.78));
            if(PHP_VERSION_ID>=80100)imagefilledpolygon($canvas,$tri,$ink);else imagefilledpolygon($canvas,$tri,3,$ink);
            $this->drawCenteredText($canvas,$text,(int)($size*.83),$txt);
        } elseif ($style === 'receipt') {
            imageline($canvas,(int)($size*.10),(int)($size*.06),(int)($size*.90),(int)($size*.06),$ink);imageline($canvas,(int)($size*.10),(int)($size*.06),(int)($size*.10),(int)($size*.88),$ink);imageline($canvas,(int)($size*.90),(int)($size*.06),(int)($size*.90),(int)($size*.88),$ink);
            $x=(int)(($size-$qrW)/2);$y=(int)($size*.12);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);$this->drawCenteredText($canvas,$text,(int)($size*.77),$txt);
            for($i=0;$i<8;$i++){ $x1=(int)($size*(.10+$i*.10));$x2=(int)($size*(.15+$i*.10));$x3=(int)($size*(.20+$i*.10));imageline($canvas,$x1,(int)($size*.88),$x2,(int)($size*.94),$ink);imageline($canvas,$x2,(int)($size*.94),$x3,(int)($size*.88),$ink); }
        } elseif ($style === 'ticket') {
            imagefilledrectangle($canvas,(int)($size*.08),(int)($size*.05),(int)($size*.92),(int)($size*.95),$ink);
            imagefilledrectangle($canvas,(int)($size*.15),(int)($size*.10),(int)($size*.85),(int)($size*.70),$bg);
            $x=(int)(($size-$qrW)/2);$y=(int)($size*.12);imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
            foreach(array(array(.08,.08),array(.92,.08),array(.08,.92),array(.92,.92)) as $p) imagefilledellipse($canvas,(int)($size*$p[0]),(int)($size*$p[1]),(int)($size*.12),(int)($size*.12),$bg);
            $this->drawCenteredText($canvas,$text,(int)($size*.81),$txt);
        } else {
            imagefilledellipse($canvas,(int)($size*.50),(int)($size*.48),(int)($size*.90),(int)($size*.90),$ink);
            imagefilledellipse($canvas,(int)($size*.50),(int)($size*.48),(int)($size*.90-$line*2),(int)($size*.90-$line*2),$bg);
            $y=(int)($size*.14); imagecopy($canvas,$qr,$x,$y,0,0,$qrW,$qrH);
            $this->filledRoundedRect($canvas,$size*.22,$size*.82,$size*.78,$size*.94,$size*.04,$ink);
            $this->drawCenteredText($canvas,$text,(int)($size*.85),$bg);
        }
        imagesetthickness($canvas, 1);
        return $canvas;
    }

    private function drawCenteredText($image, $text, $y, $color)
    {
        if ($this->activeFrameTextColor !== NULL) $color = $this->activeFrameTextColor;
        $fontPath = 'C:\\Windows\\Fonts\\arialbd.ttf';
        if (function_exists('imagettftext') && is_file($fontPath)) {
            $fontSize = max(10, (int) round(imagesx($image) * .032));
            $maxWidth = imagesx($image) * .70;
            while ($fontSize > 10) {
                $box = imagettfbbox($fontSize, 0, $fontPath, $text);
                if (($box[2] - $box[0]) <= $maxWidth) break;
                $fontSize--;
            }
            $box = imagettfbbox($fontSize, 0, $fontPath, $text);
            $textWidth = $box[2] - $box[0];
            $x = (int) round((imagesx($image) - $textWidth) / 2 - $box[0]);
            imagettftext($image, $fontSize, 0, max(0, $x), (int) round($y + $fontSize), $color, $fontPath, $text);
            return;
        }
        $font = 5;
        $maxChars = max(1, (int) floor(imagesx($image) * .72 / imagefontwidth($font)));
        if (strlen($text) > $maxChars) $text = substr($text, 0, max(1, $maxChars - 3)).'...';
        $x = (int) round((imagesx($image) - imagefontwidth($font) * strlen($text)) / 2);
        imagestring($image, $font, max(0, $x), $y, $text, $color);
    }

    private function drawTextInBox($image, $text, $left, $right, $y, $color)
    {
        if ($this->activeFrameTextColor !== NULL) $color = $this->activeFrameTextColor;
        $fontPath = 'C:\\Windows\\Fonts\\arialbd.ttf';
        if (function_exists('imagettftext') && is_file($fontPath)) {
            $fontSize = max(9, (int) round(imagesx($image) * .026));
            $maxWidth = ($right - $left) * .86;
            do {
                $box = imagettfbbox($fontSize, 0, $fontPath, $text);
                if (($box[2] - $box[0]) <= $maxWidth || $fontSize <= 9) break;
                $fontSize--;
            } while (TRUE);
            $width = $box[2] - $box[0];
            $x = (int) round($left + (($right - $left - $width) / 2) - $box[0]);
            imagettftext($image,$fontSize,0,$x,(int)round($y+$fontSize),$color,$fontPath,$text);
            return;
        }
        $font=3;$width=imagefontwidth($font)*strlen($text);$x=(int)round($left+(($right-$left-$width)/2));imagestring($image,$font,max((int)$left,$x),(int)$y,$text,$color);
    }

    private function ensureMenuEntry()
    {
        if (!$this->db->table_exists('menuadmin')) {
            return;
        }

        $menu = $this->db->where('link', $this->menuLink)->limit(1)->get('menuadmin')->row_array();
        if (!empty($menu['idmenu'])) {
            $updates = array();
            foreach (array('nama_menu' => $this->menuName, 'aktif' => 'Y', 'id_level' => $this->menuLevels) as $key => $value) {
                if ((string) $menu[$key] !== (string) $value) {
                    $updates[$key] = $value;
                }
            }
            if ($updates) {
                $this->db->where('idmenu', (int) $menu['idmenu'])->update('menuadmin', $updates);
                $this->syncMenuUsers();
                $this->clearSidebarCache();
            }
            return;
        }

        $order = $this->db->select_max('urutan')->where('idparent', 0)->get('menuadmin')->row_array();
        $this->db->insert('menuadmin', array(
            'idparent' => 0, 'id_level' => $this->menuLevels, 'nama_menu' => $this->menuName,
            'link' => $this->menuLink, 'target' => '_self', 'link_on' => 'N', 'treeview' => '',
            'classes' => '', 'classicon' => 'Y', 'icon' => 'fa-qrcode', 'aktif' => 'Y',
            'level' => NULL, 'urutan' => isset($order['urutan']) ? (int) $order['urutan'] + 1 : 1,
        ));
        if ($this->db->insert_id()) {
            $this->syncMenuUsers();
            $this->clearSidebarCache();
        }
    }

    private function syncMenuUsers()
    {
        if (!$this->db->table_exists('tb_users') || !$this->db->table_exists('hak_akses') || !function_exists('atur_menu')) {
            return;
        }
        $levels = $this->db->select('id_level')->where('publish', 'Y')->get('hak_akses')->result();
        foreach ($levels as $row) {
            $levelId = (int) $row->id_level;
            if ($levelId > 0) {
                $this->db->where('id_level', $levelId)->update('tb_users', array('idmenu' => atur_menu($levelId)));
            }
        }
    }

    private function clearSidebarCache()
    {
        foreach (glob(APPPATH.'cache'.DIRECTORY_SEPARATOR.'sidebar_menu_*') ?: array() as $file) {
            if (is_file($file)) {
                @unlink($file);
            }
        }
    }
}
