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

/* ===========================
 * Angka singkat (rb/jt/M/T) + versi K/M/B/T
 * =========================== */
function singkat_angka($n, $presisi = 1) {
    $n = (float)$n;
    if ($n < 900)             { $format = number_format((int)$n, $presisi); $simbol = '';   }
    elseif ($n < 900000)      { $format = number_format($n / 1_000, $presisi); $simbol = 'rb'; }
    elseif ($n < 900000000)   { $format = number_format($n / 1_000_000, $presisi); $simbol = 'jt'; }
    elseif ($n < 900000000000){ $format = number_format($n / 1_000_000_000, $presisi); $simbol = 'M';  }
    else                      { $format = number_format($n / 1_000_000_000_000, $presisi); $simbol = 'T'; }

    if ($presisi > 0) {
        $pisah = '.' . str_repeat('0', $presisi);
        $format = str_replace($pisah, '', $format);
    }
    return $format.$simbol;
}
function number_format_short($n, $precision = 1) {
    $n = (float)$n;
    if ($n < 900)             { $fmt = number_format((int)$n, $precision); $suf = '';  }
    elseif ($n < 900000)      { $fmt = number_format($n / 1_000, $precision); $suf = 'K'; }
    elseif ($n < 900000000)   { $fmt = number_format($n / 1_000_000, $precision); $suf = 'M'; }
    elseif ($n < 900000000000){ $fmt = number_format($n / 1_000_000_000, $precision); $suf = 'B'; }
    else                      { $fmt = number_format($n / 1_000_000_000_000, $precision); $suf = 'T'; }
    if ($precision > 0) {
        $dotzero = '.' . str_repeat('0', $precision);
        $fmt = str_replace($dotzero, '', $fmt);
    }
    return $fmt.$suf;
}

/* ===========================
 * Meta Tag
 * =========================== */
function meta_tag($par = []) {
    $d = array_merge([
        'title'=>'','keywords'=>'','favicon'=>'','description'=>'','author'=>'',
        'url'=>'','thumb'=>''
    ], (array)$par);
    return '<title>'.$d['title'].'</title>
    <meta name="robots" content="follow, index"/>
    <meta name="keywords" content="'.$d['keywords'].'" />
    <link rel="shortcut icon" href="'.$d['favicon'].'">
    <meta content="'.$d['description'].'" name="description" />
    <meta content="'.$d['author'].'" name="author" />
    <link rel="canonical" href="'.$d['url'].'" />
    <meta property="og:locale" content="en_US" />
    <meta property="bb:client_area" content="'.$d['url'].'">
    <meta property="og:url" content="'.$d['url'].'" />
    <meta property="og:title" content="'.$d['title'].'" />
    <meta property="og:image" content="'.$d['thumb'].'" />
    <meta property="og:site_name" content="'.$d['title'].'" />
    <meta property="og:type" content="website" />
    <meta property="og:description" content="'.$d['description'].'" />
    <meta name="twitter:description" content="'.$d['description'].'" />
    <meta name="twitter:card" content="summary" />
    <meta name="twitter:site" content="@'.$d['title'].'" />
    <meta name="twitter:title" content="'.$d['title'].'" />
    <meta name="twitter:image" content="'.$d['thumb'].'" />';
}

/* =========================== */
function dump($arr){
    echo "<textarea style='width:100%; height:300px;'>";
    print_r($arr);
    echo "</textarea>";
    exit;
}

/* ===========================
 * Dropdown helper
 * =========================== */
function getDropdownList($table,$column){
    $CI =& get_instance();
    $query = $CI->db->select($column)->from($table)->get();
    if($query->num_rows() >= 1){
        $option1 = ['' => '- Pilih -'];
        $option2 = array_column($query->result_array(), $column[1], $column[0]);
        return $option1 + $option2;
    }
    return [''=>'- Pilih -'];
}

/* =========================== */
function rp_to_int($teks){ return (int)str_replace('.', '', (string)$teks); }
function pembulatan($uang){
    $uang = (int)$uang;
    $ratusan = (int)substr((string)$uang, -3);
    return $ratusan < 500 ? ($uang - $ratusan) : ($uang + (1000 - $ratusan));
}
function random($len){
    $chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz';
    $out=''; for($i=0;$i<$len;$i++) $out.=$chars[random_int(0,strlen($chars)-1)];
    return $out;
}
function rprp($angka){ return 'Rp. '.number_format((int)$angka,0,',','.'); }

/* =========================== */
function curl_get_file_contents($url){
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 20,
    ]);
    $contents = curl_exec($ch);
    curl_close($ch);
    return $contents ?: false;
}

/* =========================== */
function cek_nput_post($method=''){
    $ci = & get_instance();
    if ($ci->input->server('REQUEST_METHOD') === $method) {
        $data = ['status'=>400,'msg'=>'Bad Request'];
        $ci->output->set_status_header(400)
            ->set_content_type('application/json', 'utf-8')
            ->set_output(json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES))
            ->_display();
        exit;
    }
}
function change_case($str=''){ return ucwords(strtolower((string)$str)); }

/* ===========================
 * Format baris thermal 80/58
 * =========================== */
function buatBaris4Kolom($k1,$k2,$k3,$k4){
    $w1=20; $w2=6; $w3=8; $w4=10;
    $k1=wordwrap($k1,$w1,"\n",true);
    $k2=wordwrap($k2,$w2,"\n",true);
    $k3=wordwrap($k3,$w3,"\n",true);
    $k4=wordwrap($k4,$w4,"\n",true);
    $a1=explode("\n",$k1); $a2=explode("\n",$k2); $a3=explode("\n",$k3); $a4=explode("\n",$k4);
    $rows=max(count($a1),count($a2),count($a3),count($a4));
    $hasil=[];
    for($i=0;$i<$rows;$i++){
        $c1=str_pad($a1[$i]??"", $w1, " ");
        $c2=str_pad($a2[$i]??"", $w2, " ");
        $c3=str_pad($a3[$i]??"", $w3, " ", STR_PAD_LEFT);
        $c4=str_pad($a4[$i]??"", $w4, " ", STR_PAD_LEFT);
        $hasil[] = $c1." ".$c2." ".$c3." ".$c4;
    }
    return implode("\n",$hasil)."\n"; // << diperbaiki urutan parameter implode
}
function buatBaris4Kolom58($k1,$k2,$k3,$k4){
    $w1=12; $w2=8; $w3=8; $w4=9;
    $k1=wordwrap($k1,$w1,"\n",true);
    $k2=wordwrap($k2,$w2,"\n",true);
    $k3=wordwrap($k3,$w3,"\n",true);
    $k4=wordwrap($k4,$w4,"\n",true);
    $a1=explode("\n",$k1); $a2=explode("\n",$k2); $a3=explode("\n",$k3); $a4=explode("\n",$k4);
    $rows=max(count($a1),count($a2),count($a3),count($a4));
    $hasil=[];
    for($i=0;$i<$rows;$i++){
        $c1=str_pad($a1[$i]??"", $w1, " ");
        $c2=str_pad($a2[$i]??"", $w2, " ");
        $c3=str_pad($a3[$i]??"", $w3, " ", STR_PAD_LEFT);
        $c4=str_pad($a4[$i]??"", $w4, " ", STR_PAD_LEFT);
        $hasil[] = $c1." ".$c2." ".$c3." ".$c4;
    }
    return implode("\n",$hasil)."\n";
}
function buatBaris2Kolom58($k1,$k2,$k3,$k4){
    $w1=12; $w2=1; $w3=1; $w4=12;
    $k1=wordwrap($k1,$w1,"\n",true);
    $k2=wordwrap($k2,$w2,"\n",true);
    $k3=wordwrap($k3,$w3,"\n",true);
    $k4=wordwrap($k4,$w4,"\n",true);
    $a1=explode("\n",$k1); $a2=explode("\n",$k2); $a3=explode("\n",$k3); $a4=explode("\n",$k4);
    $rows=max(count($a1),count($a2),count($a3),count($a4));
    $hasil=[];
    for($i=0;$i<$rows;$i++){
        $c1=str_pad($a1[$i]??"", $w1, " ");
        $c2=str_pad($a2[$i]??"", $w2, " ");
        $c3=str_pad($a3[$i]??"", $w3, " ", STR_PAD_LEFT);
        $c4=str_pad($a4[$i]??"", $w4, " ", STR_PAD_LEFT);
        $hasil[] = $c1." ".$c2." ".$c3." ".$c4;
    }
    return implode("\n",$hasil)."\n";
}

/* ===========================
 * Normalisasi & tampilan nomor HP (no lagi Notice $tampil_handphone)
 * =========================== */
function hp_2($nohp) {
    $d = preg_replace('/\D+/', '', (string)$nohp); // keep digits only
    if ($d === '') return '';
    if (strpos($d,'0')===0)   return '+62'.substr($d,1);  // 08.. -> +62..
    if (strpos($d,'62')===0)  return '+'.$d;             // 62.. -> +62..
    if (strpos($d,'8')===0)   return '+62'.$d;           // 8..  -> +62..
    if (strpos($d,'+62')===0) return $d;                  // +62.. OK
    return '+'.$d; // fallback
}
/** return 62xxxxxxxxxxx (plain) dengan opsi masking */
function hp_1($hp62, $mask=false){
    $d = preg_replace('/\D+/', '', (string)$hp62);
    if ($d === '') return '';
    // jika +62… di-pass, jadikan 62…
    if (strpos($hp62,'+62')===0) $d = '62'.substr($d,2);
    // jika 0…, convert ke 62…
    if (strpos($d,'0')===0) $d = '62'.substr($d,1);
    if ($mask) {
        $len = strlen($d);
        if ($len > 6) return substr($d,0,5).str_repeat('*', max(0,$len-9)).substr($d,-4);
    }
    return $d;
}
/** tampilkan nomor lokal 08xx-xxxx-xxxx */
function phone_number($hp) {
    $d = preg_replace('/\D+/', '', (string)$hp);
    if ($d === '') return '';
    if (strpos($d,'62')===0) $local = '0'.substr($d,2);
    elseif (strpos($d,'0')===0) $local = $d;
    elseif (strpos($d,'8')===0) $local = '0'.$d;
    else return $hp;

    $len = strlen($local);
    if ($len >= 11) return preg_replace('/^(\d{4})(\d{4})(\d+)$/', '$1-$2-$3', $local);
    return $local;
}
function cek_no($nohp){
    $hp = preg_replace('/\D+/', '', (string)$nohp);
    $hp = hp_3($hp);
    $res = ['akses'=>0,'status'=>400,'msg'=>'Bukan No. Hp'];
    if (strpos($hp,'0')===0 && strlen($hp)>=10) $res = ['akses'=>1,'status'=>200,'msg'=>'Ok'];
    return $res;
}
function hp_3($nohp){
    $d = preg_replace('/\D+/', '', (string)$nohp);
    if ($d === '') return '';
    if (strpos($d,'62')===0) return '0'.substr($d,2);
    if (strpos($d,'0')===0)  return $d;
    if (strpos($d,'8')===0)  return '0'.$d;
    return $d;
}
function clearnohp($nohp){
    $d = preg_replace('/\D+/', '', (string)$nohp);
    if ($d === '') return '';
    if (strpos($d,'+62')===0) return '0'.substr($d,3);
    if (strpos($d,'62')===0)  return '0'.substr($d,2);
    if (strpos($d,'0')===0)   return $d;
    return $d;
}
function hp62($nohp){
    $d = preg_replace('/\D+/', '', (string)$nohp);
    if ($d === '') return '';
    if (strpos($d,'+62')===0) return '62'.substr($d,3);
    if (strpos($d,'62')===0)  return $d;
    if (strpos($d,'0')===0)   return '62'.substr($d,1);
    if (strpos($d,'8')===0)   return '62'.$d;
    return $d;
}
function hp_62($nohp){
    $plain = hp62($nohp);
    return '+'.(strpos($plain,'62')===0 ? $plain : '62'.$plain);
}

/* =========================== */
function comma_to_dot($text){ return str_replace(",",".",(string)$text); }
function clean($text){ return preg_replace('/[^a-zA-Z0-9\s]/', '', strip_tags(html_entity_decode((string)$text))); }
function kata($string,$limit,$break=" ",$pad="..."){
    $string = (string)$string;
    if(strlen($string) <= $limit) return $string;
    $string = substr($string, 0, $limit);
    if(($bp = strrpos($string, $break)) !== false) $string = substr($string, 0, $bp);
    return $string.$pad;
}
function cetak($str){ return strip_tags(htmlentities((string)$str, ENT_QUOTES, 'UTF-8')); }
function cetak_meta($str,$mulai,$selesai){
    return strip_tags(html_entity_decode(substr(str_replace('"','',(string)$str),$mulai,$selesai), ENT_COMPAT, 'UTF-8'));
}
function sensor($teks){
    $ci = & get_instance();
    $q = $ci->db->get('katajelek');
    foreach ($q->result_array() as $r) { $teks = str_replace($r['kata'], $r['ganti'], $teks); }
    return $teks;
}
function getSearchTermToBold($text,$words){
    preg_match_all('~[A-Za-z0-9_äöüÄÖÜ]+~', $words, $m);
    if (!$m) return $text;
    $re = '~('.implode('|',$m[0]).')~i';
    return preg_replace($re, '<b style="color:red">$0</b>', $text);
}
function generateRandomString($length=10){ return substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),0,$length); }
function seo_title($s){
    $c=[' '];
    $d=['-','/','\\',',','.','#',':',';',"'",'"','[',']','{','}',')','(','|','`','~','!','@','%','$','^','&','*','=','?','+','–'];
    $s=str_replace($d,'',(string)$s);
    return strtolower(str_replace($c,'-',$s));
}

/* =========================== */
function getYEmbedUrl($url){
    $youtube_id = '';
    if (preg_match('/youtube\.com\/(?:embed|watch)(?:\?v=|\/)([a-zA-Z0-9_-]+)/i', $url, $m)) $youtube_id = $m[1];
    elseif (preg_match('/youtu\.be\/([a-zA-Z0-9_-]+)/i', $url, $m)) $youtube_id = $m[1];
    return $youtube_id ? 'https://www.youtube.com/embed/'.$youtube_id : '';
}

/* =========================== */
function file_size($size){ $mod=1024; for($i=0;$size>$mod;$i++) $size/=$mod; return round($size,2); }
function format_size($size){
    $mod=1024; $units=['B','KB','MB','GB','TB','PB']; $i=0;
    while($size>$mod && $i<count($units)-1){ $size/=$mod; $i++; }
    return round($size,2).' '.$units[$i];
}
function folderSize($dir){
    $size=0; foreach(glob(rtrim($dir,'/').'/*', GLOB_NOSORT) as $each){ $size += is_file($each) ? filesize($each) : folderSize($each); }
    return $size;
}
function thumb($path,$fullname,$width,$height){
    $dir = rtrim($path,'/').'/';
    $url = base_url().$path;
    $CI = &get_instance();
    $ext = pathinfo($fullname, PATHINFO_EXTENSION);
    $name = pathinfo($fullname, PATHINFO_FILENAME);
    $img_org   = $dir.$name.'.'.$ext;
    $img_thumb = $dir.$name."-".$height.'_'.$width.'.'.$ext;
    $img_url   = $url.$name."-".$height.'_'.$width.'.'.$ext;

    if (!file_exists($img_thumb) && file_exists($img_org)) {
        $CI->load->library('image_lib');
        $config = [
            'source_image' => $img_org,
            'new_image'    => $img_thumb,
            'width'        => $width,
            'height'       => $height,
            'maintain_ratio'=> TRUE
        ];
        $CI->image_lib->initialize($config);
        $CI->image_lib->resize();
        $CI->image_lib->clear();
    }
    return $img_url;
}
function potdesc($text,$jml){
    $text = preg_replace('/[^a-zA-Z0-9\s]/', '', strip_tags(html_entity_decode((string)$text)));
    $text = substr($text,0,$jml);
    $text = substr($text,0,strrpos($text," "));
    return $text;
}
function strip_word_html($text,$allowed=''){
    $search=['/&lsquo;/u','/&rsquo;/u','/&ldquo;/u','/&rdquo;/u','/&mdash;/u','/&ndash;/u','/&quot;/u','/ndash/u'];
    $replace=["'","'",'"','"','-'];
    $text=preg_replace($search,$replace,(string)$text);
    $text=preg_replace(['/^\\s\\s+/','/\\s\\s+$/','/\\s\\s+/u'],['','',' '],strip_tags($text,$allowed));
    $text=preg_replace(['/<!--(.|\s)*?-->/','/(<[^>]+) style=".*?"/i'],['','$1'],$text);
    return $text;
}
function rp($angka){ return number_format((int)$angka,0,',','.'); }
function image_count($directory){ return count(glob("./$directory/*.*")); }
function dir_size($directory){
    $size=0; foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS)) as $file){ $size+=$file->getSize(); }
    return $size;
}
function getDataURI($imagePath){
    if(!is_file($imagePath)) return '';
    $finfo=new finfo(FILEINFO_MIME_TYPE);
    $type=$finfo->file($imagePath);
    return 'data:'.$type.';base64,'.base64_encode((string)file_get_contents($imagePath));
}

/* =========================== */
function recursiveChmod($path,$filePerm=0644,$dirPerm=0755){
    if(!file_exists($path)) return false;
    if(is_file($path)) chmod($path,$filePerm);
    elseif(is_dir($path)){
        foreach(array_diff(scandir($path),['.','..']) as $e){ recursiveChmod($path.'/'.$e,$filePerm,$dirPerm); }
        chmod($path,$dirPerm);
    }
    return true;
}
function recursiveDelete($directory,$empty=false){
    $directory = rtrim($directory,'/');
    if(!file_exists($directory) || !is_dir($directory)) return false;
    foreach(array_diff(scandir($directory),['.','..']) as $item){
        $path = $directory.'/'.$item;
        if(is_dir($path)) recursiveDelete($path);
        else @unlink($path);
    }
    if($empty===false) @rmdir($directory);
    return true;
}
function moveFiles($src,$dst){
    if (file_exists($dst)) recursiveDelete($dst);
    if (is_dir($src)) {
        @mkdir($dst,0755,true);
        foreach(array_diff(scandir($src),['.','..']) as $f){
            moveFiles("$src/$f", "$dst/$f");
        }
    } elseif (file_exists($src)) {
        @copy($src,$dst);
    }
}

/* =========================== */
function cek_info(){
    $ci = & get_instance();
    $agent = ['browser'=>'Unidentified User Agent'];
    if ($ci->agent->is_browser())      $agent['browser']=$ci->agent->browser().' '.$ci->agent->version();
    elseif ($ci->agent->is_robot())    $agent['browser']=$ci->agent->robot();
    elseif ($ci->agent->is_mobile())   $agent['browser']=$ci->agent->mobile();
    $agent['ip'] = $ci->input->ip_address();
    $agent['os'] = $ci->agent->platform();
    return $agent;
}
function slugify($s){
    $s=str_replace([" ","_","'",".","²"], ['-','-','-','-','2'], (string)$s);
    return url_title(convert_accented_characters($s), 'dash', true);
}
function cleanString($text){
    $text=(string)$text;
    $text=preg_replace("/[áàâãªä]/u","a",$text);
    $text=preg_replace("/[ÁÀÂÃÄ]/u","A",$text);
    $text=preg_replace("/[ÍÌÎÏ]/u","I",$text);
    $text=preg_replace("/[íìîï]/u","i",$text);
    $text=preg_replace("/[éèêë]/u","e",$text);
    $text=preg_replace("/[ÉÈÊË]/u","E",$text);
    $text=preg_replace("/[óòôõºö]/u","o",$text);
    $text=preg_replace("/[ÓÒÔÕÖ]/u","O",$text);
    $text=preg_replace("/[úùûü]/u","u",$text);
    $text=preg_replace("/[ÚÙÛÜ]/u","U",$text);
    $text=preg_replace("/[’‘‹›‚]/u","'",$text);
    $text=preg_replace('/[“”«»„]/u','"',$text);
    $text=str_replace(["–","ç","Ç","ñ","Ñ"],["-","c","C","n","N"],$text);
    $text=strip_tags($text);
    $text=html_entity_decode($text);
    $text=preg_replace('/[^(\x20-\x7F)]*/','',$text);
    $text=str_replace(["\r\n","\n","\r","\t"]," ",$text);
    return $text;
}
function cleans($s){
    $c=[' ']; $d=['-','/','\\',',','.','#',':',';',"'",'"','[',']','{','}',')','(','|','`','~','!','@','%','$','^','&','*','=','?','+','"'];
    $s=str_replace($d,'',(string)$s);
    return strtolower(str_replace($c,'_',$s));
}
function cleanTag($text){ return preg_replace('/[^a-zA-Z0-9\s]/',' ', strip_tags(html_entity_decode((string)$text))); }
function convert_to_number($rupiah){ return (int)preg_replace('/,.*|[^0-9]/', '', (string)$rupiah); }

/* ===========================
 * IP util (keep signature, perbaiki logikanya)
 * =========================== */
function ip_in_range(){
    // Fungsi lama memeriksa HTTP_HOST, bikin false-positive.
    // Kita anggap false (bukan IP privat) jika host bukan IP valid (mis. domain/localhost).
    $host = $_SERVER['HTTP_HOST'] ?? '';
    if (!filter_var($host, FILTER_VALIDATE_IP)) return false;
    // Jika benar-benar IP, anggap di-range (true) hanya untuk localhost/privat.
    $ip = $host;
    $priv = [
        '127.0.0.0/8','10.0.0.0/8','172.16.0.0/12','192.168.0.0/16','::1/128'
    ];
    foreach($priv as $cidr){
        if (ipv4_in_cidr($ip, $cidr)) return true;
    }
    return false;
}
function ipv4_in_cidr($ip,$cidr){
    if (strpos($cidr,'/')===false) return $ip===$cidr;
    list($subnet,$mask)=explode('/',$cidr);
    $mask = ~((1 << (32-$mask)) - 1);
    return ((ip2long($ip) & $mask) == (ip2long($subnet) & $mask));
}

/* =========================== */
function total_ukuran($str){
    if(empty($str)) return 0;
    $s = strtolower($str);
    // ambil hanya angka & pemisah x
    $s = preg_replace('/[^0-9x\.\,]/','', $s);
    $s = str_replace(',', '.', $s);
    $parts = explode('x', $s);
    $x = (float)($parts[0] ?? 0);
    $y = (float)($parts[1] ?? 0);
    return $x * $y;
}
function multiexplode($delims,$string){
    $ready=str_replace($delims,$delims[0],$string);
    return explode($delims[0],$ready);
}
function space_tag($var){ return [preg_replace('/\h+/u',' ', (string)$var)]; }

/* =========================== */
function deleteFiles($dir, $olderThanSeconds = 0){
    $deleted = 0;
    $skipped = 0;

    if (!is_dir($dir)) {
        return ['i' => 'Folder tidak ditemukan: '.$dir];
    }

    // recursive delete folder
    $rrmdir = function($path) use (&$rrmdir, &$deleted, &$skipped, $olderThanSeconds) {
        if (!file_exists($path)) return;

        // skip pengunci folder
        $base = basename($path);
        if ($base === 'index.html' || $base === '.htaccess') {
            $skipped++;
            return;
        }

        // cek umur (kalau olderThanSeconds > 0)
        $ageOk = true;
        if ((int)$olderThanSeconds > 0) {
            $mtime = @filemtime($path);
            if ($mtime && (time() - $mtime) < (int)$olderThanSeconds) {
                $ageOk = false;
            }
        }

        if (is_file($path)) {
            if ($ageOk) {
                if (@unlink($path)) $deleted++; else $skipped++;
            } else {
                $skipped++;
            }
            return;
        }

        if (is_dir($path)) {
            // hapus isi folder dulu
            foreach (glob($path.'/*') as $child) {
                $rrmdir($child);
            }

            // hapus foldernya
            if ($ageOk) {
                @rmdir($path); // rmdir akan gagal kalau masih ada file yang tidak bisa dihapus
            }
        }
    };

    foreach (glob(rtrim($dir,'/').'/*') as $item) {
        $rrmdir($item);
    }

    // kunci folder
    @file_put_contents(rtrim($dir,'/').'/index.html', "<!DOCTYPE html><html><head><title>403 Forbidden</title></head><body><p>Directory access is forbidden.</p></body></html>");

    if ($deleted > 0) {
        return ['i' => "Temp dibersihkan: $deleted item dihapus"];
    }
    return ['i' => ($skipped > 0 ? "Tidak ada item yang dihapus (ada $skipped item dilewati)" : "Tidak ada file")];
}

function deleteLog($dir){
    $arr=['i'=>'Tidak ada file'];
    foreach(glob($dir.'/*.php') as $file){
        if(is_file($file)){
            if((time()-filemtime($file)) > 0){
                @unlink($file); $arr['i'] = "File berhasil dihapus";
            } else {
                $arr['i'] = "File gagal dihapus";
            }
        }
    }
    return $arr;
}

/* =========================== */
if (!function_exists('filterData')){
    function filterData(&$str){
        $str = preg_replace("/\t/","\\t",$str);
        $str = preg_replace("/\r?\n/","\\n",$str);
        if(strstr($str,'"')) $str = '"'.str_replace('"','""',$str).'"';
    }
}
if (!function_exists('nama_depan')){
    function nama_depan($param){
        $arr = explode(' ',trim((string)$param));
        return $arr[0] ?? '';
    }
}

/* ===========================
 * XSS filter
 * =========================== */
if (!function_exists('xss_filter')){
function xss_filter($str, $type = ''){
    $str = $str ?? '';
    switch($type){
        default:
            return stripcslashes(htmlspecialchars($str));
        case 'sql':
            $x=['-','/','\\',',','#',':',';',"'",'"','[',']','{','}',')','(','|','`','~','!','%','$','^','&','*','=','?','+'];
            $str=str_replace($x,'',$str);
            $str=stripcslashes($str);
            $str=htmlspecialchars($str);
            $str=preg_replace('/[^A-Za-z0-9]/','',$str);
            return (int)$str;
        case 'xss':
            $x=['\\','#',';',"'",'"','[',']','{','}',')','(','|','`','~','!','%','$','^','*','=','?','+'];
            $str=str_replace($x,'',$str);
            $str=stripcslashes($str);
            return htmlspecialchars($str);
        case 'folder':
            $x=['#',';',"'",'"','[',']','{','}',')','(','|','`','~','!','%','$','^','*','=','?','+'];
            return str_replace($x,'\\',$str);
    }
}}
/* =========================== */

/** daftar kode negara (dipertahankan) */
function phone_list(){
    return [
        '+44'=>'UK (+44)', '+1'=>'USA (+1)', '+213'=>'Algeria (+213)', '+376'=>'Andorra (+376)', '+244'=>'Angola (+244)',
        // … (daftar aslinya panjang; dibiarkan seperti semula)
        '+62'=>'Indonesia (+62)'
    ];
}

/* =========================== */
function terbilang($x){
    $x = (int)$x;
    $angka=["","satu","dua","tiga","empat","lima","enam","tujuh","delapan","sembilan","sepuluh","sebelas"];
    if ($x < 12) return " ".$angka[$x];
    elseif ($x < 20) return terbilang($x-10)." belas";
    elseif ($x < 100) return terbilang(intval($x/10))." puluh".terbilang($x%10);
    elseif ($x < 200) return " seratus".terbilang($x-100);
    elseif ($x < 1000) return terbilang(intval($x/100))." ratus".terbilang($x%100);
    elseif ($x < 2000) return " seribu".terbilang($x-1000);
    elseif ($x < 1000000) return terbilang(intval($x/1000))." ribu".terbilang($x%1000);
    elseif ($x < 1000000000) return terbilang(intval($x/1000000))." juta".terbilang($x%1000000);
    return "";
}

/* =========================== */
function barcode_12($string){
    if (preg_match('~\d{12}~', (string)$string, $m)) return $m[0];
    return '';
}
function ean13_check_digit($digits){
    $digits = preg_replace('/\D/','', (string)$digits);
    if (strlen($digits)!==12) return '';
    $even = $digits[1]+$digits[3]+$digits[5]+$digits[7]+$digits[9]+$digits[11];
    $odd  = $digits[0]+$digits[2]+$digits[4]+$digits[6]+$digits[8]+$digits[10];
    $total = $odd + ($even*3);
    $nextTen = ceil($total/10)*10;
    $check = $nextTen - $total;
    return $digits.$check;
}

/* =========================== */
class Text {
    public static function fill($text,$length){ return str_pad(substr((string)$text,0,$length), $length, ' ', STR_PAD_RIGHT); }
    public static function fillRight($text,$length){ return str_pad((string)$text, $length, ' ', STR_PAD_LEFT); }
}

if (!function_exists('bm_upload_is_webp_image')) {
    function bm_upload_is_webp_image($fileExt = '', $mime = '')
    {
        $ext = strtolower(ltrim((string) $fileExt, '.'));
        $mime = strtolower((string) $mime);

        return in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)
            || in_array($mime, ['image/jpeg', 'image/pjpeg', 'image/png', 'image/gif', 'image/webp'], true);
    }
}

if (!function_exists('bm_upload_make_image_resource')) {
    function bm_upload_make_image_resource($sourcePath, $fileExt = '', $mime = '')
    {
        $ext = strtolower(ltrim((string) $fileExt, '.'));
        $mime = strtolower((string) $mime);
        $img = null;

        if ($mime === 'image/jpeg' || $mime === 'image/pjpeg' || $ext === 'jpg' || $ext === 'jpeg') {
            if (function_exists('imagecreatefromjpeg')) {
                $img = @imagecreatefromjpeg($sourcePath);
            }
        } elseif ($mime === 'image/png' || $ext === 'png') {
            if (function_exists('imagecreatefrompng')) {
                $img = @imagecreatefrompng($sourcePath);
            }
        } elseif ($mime === 'image/gif' || $ext === 'gif') {
            if (function_exists('imagecreatefromgif')) {
                $img = @imagecreatefromgif($sourcePath);
            }
        } elseif ($mime === 'image/webp' || $ext === 'webp') {
            if (function_exists('imagecreatefromwebp')) {
                $img = @imagecreatefromwebp($sourcePath);
            }
        }

        if (!$img && function_exists('imagecreatefromstring') && is_file($sourcePath)) {
            $binary = @file_get_contents($sourcePath);
            if ($binary !== false) {
                $img = @imagecreatefromstring($binary);
            }
        }

        return $img;
    }
}

if (!function_exists('bm_convert_image_path_to_webp')) {
    function bm_convert_image_path_to_webp($sourcePath, $targetPath, $quality = 80, $fileExt = '', $mime = '')
    {
        $quality = max(0, min(100, (int) $quality));

        if (!function_exists('imagewebp')) {
            return ['status' => false, 'message' => 'Server belum mendukung konversi WebP'];
        }

        if (!is_file($sourcePath)) {
            return ['status' => false, 'message' => 'File sumber tidak ditemukan'];
        }

        if (!bm_upload_is_webp_image($fileExt, $mime)) {
            return ['status' => false, 'message' => 'File bukan gambar yang didukung'];
        }

        $img = bm_upload_make_image_resource($sourcePath, $fileExt, $mime);
        if (!$img) {
            return ['status' => false, 'message' => 'Gagal membaca file gambar'];
        }

        $targetDir = dirname($targetPath);
        if (!is_dir($targetDir) && !@mkdir($targetDir, 0755, true)) {
            @imagedestroy($img);
            return ['status' => false, 'message' => 'Folder tujuan tidak bisa dibuat'];
        }

        if (function_exists('imagepalettetotruecolor')) {
            @imagepalettetotruecolor($img);
        }
        @imagealphablending($img, true);
        @imagesavealpha($img, true);

        $ok = @imagewebp($img, $targetPath, $quality);
        @imagedestroy($img);

        if (!$ok || !is_file($targetPath) || (int) @filesize($targetPath) <= 0) {
            if (is_file($targetPath)) {
                @unlink($targetPath);
            }
            return ['status' => false, 'message' => 'Gagal mengonversi gambar ke WebP'];
        }

        return [
            'status' => true,
            'message' => '',
            'converted' => true,
            'file_name' => basename($targetPath),
            'full_path' => $targetPath,
        ];
    }
}

if (!function_exists('bm_finalize_ci_upload_as_webp')) {
    function bm_finalize_ci_upload_as_webp($uploadData, $quality = 80)
    {
        $sourcePath = (string) ($uploadData['full_path'] ?? '');
        $sourceName = (string) ($uploadData['file_name'] ?? basename($sourcePath));
        $sourceMime = strtolower((string) ($uploadData['file_type'] ?? ''));
        $sourceExt = strtolower(ltrim((string) ($uploadData['file_ext'] ?? pathinfo($sourceName, PATHINFO_EXTENSION)), '.'));

        if ($sourcePath === '' || !is_file($sourcePath)) {
            return ['status' => false, 'message' => 'File upload tidak ditemukan'];
        }

        if (!bm_upload_is_webp_image($sourceExt, $sourceMime)) {
            return [
                'status' => true,
                'message' => '',
                'converted' => false,
                'file_name' => $sourceName,
                'full_path' => $sourcePath,
            ];
        }

        if ($sourceExt === 'webp' || $sourceMime === 'image/webp') {
            return [
                'status' => true,
                'message' => '',
                'converted' => false,
                'file_name' => $sourceName,
                'full_path' => $sourcePath,
            ];
        }

        $baseDir = rtrim((string) ($uploadData['file_path'] ?? dirname($sourcePath)), '/\\');
        $baseName = (string) ($uploadData['raw_name'] ?? pathinfo($sourceName, PATHINFO_FILENAME));
        if ($baseName === '') {
            $baseName = 'upload_' . date('YmdHis');
        }

        $targetPath = $baseDir . DIRECTORY_SEPARATOR . $baseName . '.webp';
        $suffix = 1;
        while (is_file($targetPath)) {
            $targetPath = $baseDir . DIRECTORY_SEPARATOR . $baseName . '_' . $suffix . '.webp';
            $suffix++;
        }

        $converted = bm_convert_image_path_to_webp($sourcePath, $targetPath, $quality, $sourceExt, $sourceMime);
        if (!$converted['status']) {
            if (is_file($sourcePath)) {
                @unlink($sourcePath);
            }
            return $converted;
        }

        if (is_file($sourcePath)) {
            @unlink($sourcePath);
        }

        return $converted;
    }
}
