Saturday, 11 July 2026

ganti favicon cms sekolahku

 *convert  logo, pilih 1 perintah saja
convert logo.jpg -resize 256x256 favicon.png
magick logo.jpg -resize 256x256 favicon.png

*buat file ico
convert favicon.png -define icon:auto-resize=16,32,48,64,128,256 favicon.ico
magick favicon.png -define icon:auto-resize=16,32,48,64,128,256 favicon.ico

*salin ke server
scp favicon.ico root@stb:/var/www/cms-sekolahku/public/

*jika sudah di server
cp favicon.ico /var/www/cms-sekolahku/public/favicon.ico
chown www-data:www-data /var/www/cms-sekolahku/public/favicon.ico

*bersihkan cache cloudflared
Masuk ke Cloudflare Dashboard:
Caching → Cache → Purge Cache
Purge single file:
https://al-ihsan.jurnaldo.my.id/favicon.ico


Pasang CMS sekolahku.web.id

 *kirim file ke stb
scp ~/Downloads/cms-sekolahku-v3.0.1.zip root@stb:/root/

*ekstrak
cd /root

mkdir /root/cms-sekolahku
unzip cms-sekolahku-v3.0.1.zip -d /root/cms-sekolahku

*Buat database

mysql 

CREATE DATABASE sekolahku
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

CREATE USER 'sekolahku'@'localhost'
IDENTIFIED BY 'PasswordKuat';

GRANT ALL PRIVILEGES
ON sekolahku.*
TO 'sekolahku'@'localhost';

FLUSH PRIVILEGES;
EXIT;

*Import database
mysql sekolahku < sekolahku_db.sql

*Edit .env, pastikan:

CI_ENVIRONMENT = production

app.baseURL = 'https://ihsan.jurnaldo.my.id/'

database.default.hostname = localhost
database.default.database = sekolahku
database.default.username = sekolahku
database.default.password = PasswordKuat
database.default.DBDriver = MySQLi

*
nano /etc/nginx/sites-available/cms-sekolahku
isikan:
server {
    listen 8084;
    server_name ihsan.jurnaldo.my.id;

    root /var/www/cms-sekolahku/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

*aktifkan situs
ln -s /etc/nginx/sites-available/cms-sekolahku \
      /etc/nginx/sites-enabled/
      
*cek konfigurasi
nginx -t

*reload
systemctl reload nginx

*pindahkan ke web root
mv /root/cms-sekolahku /var/www/

*set permission
chown -R www-data:www-data /var/www/cms-sekolahku
chmod -R 755 /var/www/cms-sekolahku
chmod -R 775 /var/www/cms-sekolahku/writable
chmod -R 775 /var/www/cms-sekolahku/public/uploads

chown -R www-data:www-data /var/www/cms-sekolahku
find /var/www/cms-sekolahku -type d -exec chmod 755 {} \;
find /var/www/cms-sekolahku -type f -exec chmod 644 {} \;

chmod -R 775 /var/www/cms-sekolahku/writable
chmod -R 775 /var/www/cms-sekolahku/public/uploads


*edit composer
nano /var/www/cms-sekolahku/composer.json

ubah "php": "^8.5", menjadi "php": "^8.2",

ubah:
 "platform": {
    "php": "8.5.0"
}

menjadi:
"platform": {
    "php": "8.2"
}

*hapus vendor lama
cd /var/www/cms-sekolahku
rm -rf vendor composer.lock

*jalankan pasang composer
cd /var/www/cms-sekolahku
composer install

bila
/var/www/cms-sekolahku# composer install Do not run Composer as root/super user! See https://getcomposer.org/root for details Continue as root/super user [yes]?
ketik yes dan [enter]


*bersihkan cache Code Igniter
cd /var/www/cms-sekolahku
php spark cache:clear

*reload nginx
systemctl reload nginx

*login
https://ihsan.jurnaldo.my.id/admin
username: administrator
password: 12345

**reset password jika diperlukan
buat hash baru misal:
php -r "echo password_hash('Admin123', PASSWORD_DEFAULT), PHP_EOL;"

update ke database dengan:
UPDATE users
SET password='HASIL_HASH_DARI_PERINTAH_DI_ATAS'
WHERE username='administrator';

Cara paling cepat (tanpa copy-paste hash)
Anda juga bisa menjalankan satu perintah berikut dari shell Linux:
HASH=$(php -r "echo password_hash('Admin123', PASSWORD_DEFAULT);")
mysql sekolahku -e "UPDATE users SET password='$HASH' WHERE username='administrator';"


"Apakah CMS ini aman dipasang di server yang terhubung ke internet?"

 

Audit keamanan source code jauh lebih mendalam daripada sekadar mencari eval(base64_decode()). Tujuannya adalah menjawab pertanyaan: "Apakah CMS ini aman dipasang di server yang terhubung ke internet?"

Untuk CMS cms-sekolahku-v3.0.1, saya biasanya melakukan audit dalam beberapa tahap.

TahapYang diperiksaRisiko
1Struktur proyekFile asing, backdoor
2KonfigurasiPassword, API key, debug mode
3Login & SessionBypass login, session hijacking
4SQLSQL Injection
5Upload fileUpload shell PHP
6XSSScript berbahaya
7CSRFForm tanpa token
8Hak aksesPrivilege escalation
9Library pihak ketigaVersi rentan/CVE
10Kualitas kodeError handling, sanitasi

Contoh yang saya cari

1. SQL Injection

Kode yang berbahaya misalnya:

$id = $_GET['id'];

$sql = "SELECT * FROM siswa WHERE id=$id";

$db->query($sql);

Seharusnya menggunakan prepared statement atau Query Builder.


2. Upload Shell

Misalnya:

move_uploaded_file($_FILES['file']['tmp_name'],
                   "uploads/".$_FILES['file']['name']);

tanpa validasi.

Saya akan cek:

  • hanya jpg/png/pdf?

  • MIME type

  • ukuran

  • rename file

  • blokir php

  • simpan di luar public_html bila memungkinkan


3. XSS

Misalnya

echo $_POST['nama'];

harusnya

esc($nama)

atau

htmlspecialchars(...)

4. Login

Saya akan cek

  • password di-hash?

  • pakai bcrypt/Argon2?

  • session_regenerate_id()

  • remember me aman?

  • brute force protection


5. CSRF

Apakah setiap POST mempunyai token.


6. Authorization

Misalnya URL

/admin/user/delete/5

Apakah user biasa bisa membuka URL itu.


7. Debug

Misalnya

CI_ENVIRONMENT = development

di server produksi.

Atau

display_errors = On

8. Hardcoded Secret

Saya cari

password=
apikey=
secret=
Bearer
JWT
smtp

9. Library

Misalnya CodeIgniter 4.1

padahal sekarang sudah ada patch keamanan.

Saya cek:

  • framework

  • composer

  • vendor

  • CVE yang diketahui


10. Backdoor Tersembunyi

Selain

eval(base64_decode())

saya juga mencari

assert()

preg_replace('/e')

create_function()

call_user_func()

Reflection

file_get_contents("http://")

curl_exec()

exec()

shell_exec()

passthru()

proc_open()

dan memastikan penggunaannya memang sah.


Hasil akhirnya

Saya biasanya membuat laporan seperti ini:

Security Audit CMS Sekolahku

★★★★★ Critical
- Tidak ada

★★★★ High
- 1 SQL Injection pada modul Alumni

★★★ Medium
- Upload gambar belum memeriksa MIME Type

★★ Low
- Debug masih aktif

★ Info
- Framework CodeIgniter 4.5.2
- Composer dependency OK

lalu saya sertakan

  • lokasi file

  • nomor baris

  • penyebab

  • dampak

  • cara memperbaikinya

  • contoh kode yang benar.

Saran untuk CMS Sekolahku

Karena CMS ini akan dipasang pada server sekolah dan diakses dari internet, saya menyarankan audit penuh. Saya bisa memeriksa seluruh proyek (termasuk folder app, public, vendor, dan konfigurasi) dan menghasilkan laporan keamanan yang cukup lengkap, bukan hanya mencari malware. Dengan begitu kita bisa mengetahui apakah ada celah seperti SQL Injection, XSS, upload file berbahaya, atau masalah otorisasi sebelum sistem digunakan di lingkungan produksi.

Wednesday, 8 July 2026

tahapan migrasi guruwali

 

Menurut saya, keputusan yang paling tepat untuk plugin Anda adalah berhenti menggunakan binaan.csv dan menggantinya dengan satu tabel master Guru Wali.

Saya menyarankan struktur berikut:

CREATE TABLE mdl_local_jurnalmengajar_guruwali (
    id BIGINT(10) NOT NULL AUTO_INCREMENT,
    guruid BIGINT(10) NOT NULL,
    muridid BIGINT(10) NOT NULL,
    timecreated BIGINT(10) NOT NULL,
    timemodified BIGINT(10) NOT NULL,

    PRIMARY KEY (id),

    UNIQUE KEY uniq_murid (muridid),

    KEY idx_guru (guruid),
    KEY idx_murid (muridid)
);

Kenapa saya mantap memilih desain ini?

Karena sekarang saya sudah melihat keseluruhan plugin Anda.

Guru

Semua memakai

mdl_user.id

Murid

Semua modul juga memakai

muridid = mdl_user.id

Misalnya:

  • ✅ Jurnal Guru Wali

  • ✅ Riwayat Individu

  • ✅ Pembinaan BK

  • ✅ Layanan BK

  • ✅ Pembinaan Guru Mapel

  • ✅ Rekap Kehadiran

Semuanya sudah menggunakan muridid.

Jadi Guru Wali tinggal mengikuti arsitektur yang sudah ada, bukan membuat konsep baru.


Tahapan migrasi yang saya sarankan

Tahap 1

Tambahkan tabel SQL.

Belum mengubah halaman apa pun.


Tahap 2

Buat script migrasi sekali saja.

binaan.csv
        ↓
cari userid murid dari NIS
        ↓
INSERT

Setelah selesai tabel akan berisi:

guruidmuridid
1172821
1172845
34902

Tahap 3

Ubah halaman satu per satu.

Urutannya saya sarankan:

  1. guruwali_manage.php

  2. guruwali_add.php

  3. guruwali_delete.php

  4. guruwali_view.php

  5. jurnalguruwali.php

  6. rekap_kehadiran_muridwali.php

  7. lib_notifikasi.php

Semuanya cukup membaca tabel baru.


Tahap 4

Hapus

binaan.csv

beserta semua fungsi:

jw_load_binaan_csv()
jw_find_user_by_nis()
jw_get_murid_options_from_csv()

Karena sudah tidak diperlukan lagi.


Keuntungan jangka panjang

Nanti saat awal tahun ajaran:

  1. Import User

  2. Import Cohort

  3. Guru Wali tetap

Tidak ada lagi edit CSV.

Tidak ada lagi masalah:

  • XII masih muncul

  • kelas lama masih tampil

  • nama murid tidak berubah

Karena setiap halaman tinggal melakukan:

Guru
    ↓
local_jurnalmengajar_guruwali
    ↓
muridid
    ↓
mdl_user
    ↓
cohort aktif

Sehingga nama dan kelas selalu mengikuti kondisi terbaru di Moodle.


Menurut saya, ini adalah waktu yang tepat

Karena Anda memang sedang mengerjakan proses import cohort tahunan, saya akan menganggap ini sebagai versi 2.0 dari fitur Guru Wali.

Perubahan yang dilakukan hanya pada master data Guru Wali. Seluruh tabel jurnal dan riwayat yang sudah ada tetap dipertahankan, sehingga risiko terhadap data lama sangat kecil, sementara manfaatnya besar untuk pemeliharaan plugin di tahun-tahun berikutnya.

fitur guru wali sarankan

 

Saya sudah meninjau struktur fitur Guru Wali pada plugin local_jurnalmengajar. Dari file yang ada, fiturnya sudah cukup lengkap dan membentuk satu alur kerja yang jelas.

Alur fitur Guru Wali saat ini

Import CSV binaan
        │
        ▼
binaan.csv (dataroot)
        │
        ├── guruwali_manage.php
        ├── guruwali_view.php
        ├── jurnalguruwali.php
        ├── rekap_kehadiran_muridwali.php
        └── lib_notifikasi.php

Artinya seluruh fitur Guru Wali bergantung pada satu file binaan.csv, bukan tabel database.


1. Manajemen Murid Binaan

Administrator dapat:

  • Import CSV

  • Download format CSV

  • Tambah murid binaan

  • Update murid binaan

  • Hapus murid binaan

  • Melihat daftar murid binaan setiap guru

File:

  • import_binaan.php

  • download_format_binaan.php

  • guruwali_add.php

  • guruwali_manage.php

  • guruwali_delete.php

  • guruwali_view.php


2. Input Jurnal Guru Wali

Guru wali dapat:

✔ memilih beberapa murid sekaligus

✔ mengisi

  • Topik

  • Tindak lanjut

  • Keterangan

kemudian otomatis dibuat satu record untuk setiap murid.

Misalnya memilih:

  • Ahmad

  • Budi

  • Cici

maka langsung menjadi tiga record jurnal.

Ini sudah cukup efisien.


3. Edit Jurnal

Sudah tersedia.

Guru dapat mengubah:

  • topik

  • tindak lanjut

  • keterangan

tanpa membuat data baru.


4. Riwayat

Di bawah form terdapat:

Riwayat 10 jurnal terakhir.

Bisa diedit kembali.


5. Rekap Kehadiran Murid Binaan

Halaman:

rekap_kehadiran_muridwali.php

Fitur:

  • filter tanggal

  • mode per hari

  • mode per jam

  • rekap

Hadir

Sakit

Ijin

Alpa

Dispensasi

dan bisa membuka detail per murid.


6. Notifikasi WA

Sesudah jurnal disimpan:

Guru wali cukup menginput sekali.

Plugin akan:

  • mengelompokkan berdasarkan kelas

  • membuat pesan

  • mengirim ke tujuan sesuai setting

menggunakan template

template_guru_wali

yang placeholdernya sudah lengkap.


7. Integrasi dengan Template

Sudah tersedia template yang bisa diubah admin.

Placeholder:

  • waktu

  • murid

  • kelas

  • topik

  • tindak lanjut

  • keterangan

  • guru wali

Semua sudah dipakai ketika mengirim WA.


Yang menurut saya masih kurang

Kalau melihat keseluruhan plugin, fitur Guru Wali sebenarnya sudah sekitar 75–80% lengkap. Yang masih bisa dikembangkan agar menjadi lebih berguna bagi wali kelas dan BK adalah:

1. Dashboard Murid Binaan ⭐⭐⭐⭐⭐

Saat guru membuka menu Guru Wali, langsung tampil:

Jumlah murid binaan : 18

Jurnal bulan ini : 24

Belum pernah dibina : 5

Sudah dibina : 13

Tidak perlu membuka rekap dahulu.


2. Riwayat per Murid ⭐⭐⭐⭐⭐

Klik nama murid

Muncul seluruh histori

10 Januari
Topik ...

20 Januari
Topik ...

15 Februari
Topik ...

sehingga perkembangan siswa mudah dipantau.


3. Grafik Intensitas Pembinaan ⭐⭐⭐⭐☆

Misalnya

Ahmad
█████████ 9

Budi
██ 2

Cici
██████ 6

langsung terlihat siapa yang sering dibina.


4. Pencarian Murid ⭐⭐⭐⭐☆

Saat murid binaan sudah banyak (30–40 orang),

lebih nyaman jika ada kotak pencarian nama.


5. Statistik per Topik ⭐⭐⭐⭐☆

Misalnya

Disiplin        14

Terlambat       9

Motivasi        6

Bullying        1

berguna untuk evaluasi.


6. Target Pembinaan ⭐⭐⭐⭐⭐

Misalnya sekolah menentukan

setiap murid minimal mendapat pembinaan 1 kali per semester.

Plugin dapat menampilkan:

Sudah dibina
16

Belum dibina
4

Ini akan sangat membantu monitoring.


7. Integrasi dengan Pembinaan BK ⭐⭐⭐⭐⭐

Saat membuka profil murid:

Riwayat Guru Wali

Riwayat BK

Riwayat Pembinaan Guru Mapel

Riwayat Surat Izin

Semua muncul pada satu halaman sehingga menjadi semacam profil pembinaan siswa yang terintegrasi.


Prioritas pengembangan yang saya sarankan

Jika kita mengembangkan fitur ini secara bertahap, saya menyarankan urutannya:

  1. Riwayat lengkap per murid (paling bermanfaat dan relatif mudah).

  2. Dashboard Guru Wali dengan statistik ringkas.

  3. Monitoring target pembinaan per semester.

  4. Integrasi seluruh riwayat pembinaan (Guru Wali, BK, Guru Mapel, Surat Izin) menjadi satu profil siswa.

Menurut saya, empat pengembangan tersebut akan membuat modul Guru Wali terasa jauh lebih matang dan menjadi pusat pemantauan perkembangan siswa, bukan sekadar tempat mencatat jurnal.

Saturday, 4 July 2026

mode scp simulasi kirim simulasi cek file apa yang akan terkirim

 

Ya, rsync sangat cocok untuk kebutuhan itu. Yang Anda inginkan adalah:

  • ✅ File .php di root → masuk ke /var/www/shared/local/jurnalmengajar/

  • ✅ File .php di subfolder → masuk ke subfolder yang sesuai di STB

  • ✅ File non-.php yang sudah ada di STB tetap utuh

  • ✅ File non-.php dari laptop tidak dikirim

  • ✅ Tidak menghapus file apa pun di STB

Gunakan perintah berikut dari laptop:

rsync -av \
  --include='*/' \
  --include='*.php' \
  --exclude='*' \
  ./ \
  root@stb:/var/www/shared/local/jurnalmengajar/

Karena sumbernya adalah ./, maka struktur folder akan dipertahankan.

Misalnya di laptop:

all_php_shared_stb/
├── lib.php
├── index.php
├── classes/
│   ├── helper.php
│   └── api.php
├── db/
│   └── install.php
└── cli/
    └── cron.php

Maka di STB akan menjadi:

/var/www/shared/local/jurnalmengajar/
├── lib.php            ← diperbarui
├── index.php          ← diperbarui
├── classes/
│   ├── helper.php     ← diperbarui
│   ├── api.php        ← diperbarui
│   └── style.css      ← tetap ada
├── db/
│   ├── install.php    ← diperbarui
│   └── upgrade.xml    ← tetap ada
└── cli/
    └── cron.php       ← diperbarui

File seperti:

  • style.css

  • logo.png

  • icon.svg

  • template.mustache

  • lang/id/local_jurnalmengajar.php (jika bukan PHP tidak akan terkirim)

tidak akan dihapus karena Anda tidak menggunakan --delete.


Sebelum benar-benar mengirim

Saya sarankan cek dulu dengan mode simulasi:

rsync -avn \
  --include='*/' \
  --include='*.php' \
  --exclude='*' \
  ./ \
  root@stb:/var/www/shared/local/jurnalmengajar/

Jika daftar file yang akan disalin sudah benar, jalankan kembali tanpa -n:

rsync -av \
  --include='*/' \
  --include='*.php' \
  --exclude='*' \
  ./ \
  root@stb:/var/www/shared/local/jurnalmengajar/

Perintah ini aman untuk sinkronisasi kode PHP karena hanya memperbarui file .php dan tidak menghapus file lain yang sudah ada di STB.

Disbale ipv6 di Ubuntu tapi tidak permanen

 sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1

 

enable

sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0 

daftar alias di laptop ubuntu

 

Berikut semua alias yang ada di .bashrc Anda beserta fungsinya.

AliasPerintahFungsi
lsls --color=autoMenampilkan daftar file/folder dengan warna agar lebih mudah dibaca.
grepgrep --color=autoMenyorot hasil pencarian teks dengan warna.
fgrepfgrep --color=autoVersi grep untuk pencarian string literal dengan warna.
egrepegrep --color=autoVersi grep untuk regular expression extended dengan warna.
llls -alFMenampilkan daftar file lengkap (izin, pemilik, ukuran, file tersembunyi, dll.).
lals -AMenampilkan semua file termasuk yang tersembunyi, kecuali . dan ...
lls -CFMenampilkan daftar file dalam format ringkas dengan indikator tipe file.
alertnotify-send ...Menampilkan notifikasi desktop setelah perintah yang lama selesai dijalankan.
cetakizinssh root@100.106.40.93 print_remote_suratLogin ke server melalui SSH dan menjalankan perintah print_remote_surat untuk mencetak surat izin dari server.
ytampv --no-video --ytdl-raw-options=force-ipv4= --ytdl-format=bestMemutar audio dari YouTube menggunakan MPV tanpa menampilkan video.
ytvmpv --ytdl-raw-options=force-ipv4= --ytdl-format=bestMemutar video YouTube menggunakan MPV.
serveronsudo systemctl start docker && cd /home/dhwan/immich && docker compose up -dMenyalakan layanan Docker kemudian menjalankan stack Docker Immich.
serveroffcd /home/dhwan/immich && docker compose down && sudo systemctl stop dockerMenghentikan stack Docker Immich lalu mematikan layanan Docker.
moodleonsudo systemctl start nginx php8.2-fpm mariadbMenyalakan layanan Moodle (Nginx, PHP-FPM, dan MariaDB).
moodleoffsudo systemctl stop nginx php8.2-fpm mariadbMematikan layanan Moodle (Nginx, PHP-FPM, dan MariaDB).

Ringkasan berdasarkan kategori

📁 Navigasi & File

  • ls

  • ll

  • la

  • l

🔍 Pencarian

  • grep

  • fgrep

  • egrep

🔔 Notifikasi

  • alert

🖨️ Administrasi Sekolah

  • cetakizin

▶️ Multimedia

  • yta → Dengarkan audio YouTube.

  • ytv → Putar video YouTube.

🐳 Docker / Immich

  • serveron

  • serveroff

🎓 Server Moodle

  • moodleon

  • moodleoff


Selain alias tersebut, ada satu baris yang bukan alias:

neofetch

Baris ini akan menjalankan neofetch setiap kali Anda membuka terminal untuk menampilkan informasi sistem (OS, kernel, CPU, RAM, dll.).

Friday, 3 July 2026

convert ubah hasil fet ke format csv siap import

 Mantap, Guru Duan! 🎉

php tools/fet2csv.php "/home/dhwan/FET SMA2 CONTOH/2026-2027/HASIL/jadwal-sman2kandangan-26juni-0910-6/jadwal-sman2kandangan-26juni-0910_activities_time_horizontal.html"

Menurut saya kita sudah mencapai target awal dengan baik.

Kalau nanti sumbernya pindah folder?

Tidak masalah.

****************************************
Misalnya hasil FET tahun depan ada di:

/home/dhwan/JADWAL/2027/HASIL/

cukup ganti path-nya.


php tools/fet2csv.php \
"/home/dhwan/JADWAL/2027/HASIL/jadwal_2027_activities_time_horizontal.html"

Script tidak perlu diubah sama sekali.

*****************************************

## Yang sudah berhasil dibuat

✅ Membaca file hasil FET:

```text
activities_time_horizontal.html
```

✅ Parsing seluruh aktivitas.

✅ Mengambil:

* Guru
* Hari
* Kelas
* Mata Pelajaran
* Jam

✅ Mengelompokkan aktivitas menjadi satu record per sesi.

✅ Mengekspor ke CSV UTF-8 (dengan BOM) sehingga langsung terbuka rapi di LibreOffice Calc maupun Excel.

Format akhirnya:

```csv
guru,userid,hari,kelas,jamke,mapel
Dwiyani Lestari M.Pd,,Senin,XI-B,"2,3","Bahasa Indonesia"
```

---

## Alur kerja nantinya menjadi sederhana

```text
FET Generate
      │
      ▼
activities_time_horizontal.html
      │
      ▼
php tools/fet2csv.php activities_time_horizontal.html
      │
      ▼
jadwal.csv
      │
      ▼
LibreOffice Calc
      │
      ├── isi userid (VLOOKUP/XLOOKUP)
      ▼
Import ke Moodle Jurnal Mengajar
```

---


Hitung berapa jumlah file php

 

Dari daftar yang Anda tampilkan, terdapat 141 file berekstensi .php di direktori:

/var/www/html/moodle/local/jurnalmengajar

Perhitungan tersebut tidak menghitung:

  • Direktori (amd, assets, classes, cli, css, db, lang, pix, templates, vendor, dll.)

  • File non-PHP seperti:

    • composer.json

    • composer.lock

    • jadwal_pengawas.json

  • File backup seperti:

    • export_berita_acara_pdf.php.save

    • tv.php.save

    • tv.php.save.1

Jika yang dimaksud adalah semua file PHP secara rekursif (termasuk di dalam classes/, cli/, db/, dll.), maka jumlahnya akan lebih banyak. Anda bisa mengeceknya langsung di Debian dengan:

find /var/www/html/moodle/local/jurnalmengajar -type f -name "*.php" | wc -l

atau untuk melihat daftarnya:

find /var/www/html/moodle/local/jurnalmengajar -type f -name "*.php" | sort

Thursday, 2 July 2026

cek wa notif tidak terkirim

tail -100 /var/moodledata/logs/wa_debug.log 

cek error 500

 

Karena yang muncul adalah HTTP 500 Internal Server Error, berarti ada fatal error PHP, bukan error routing. File izin_guru.php memang ada di plugin Anda, sehingga penyebabnya hampir pasti berasal dari kode yang baru diubah.

Langkah tercepat adalah melihat error sebenarnya.

Jalankan di server:

tail -50 /var/log/apache2/error.log

atau jika memakai PHP-FPM:

tail -50 /var/log/php8.2-fpm.log

atau jika menggunakan Nginx:

tail -50 /var/log/nginx/error.log

Kalau Moodle mengaktifkan debugging, Anda juga bisa:

$CFG->debug = (E_ALL | E_STRICT);
$CFG->debugdisplay = 1;

atau melalui Site administration → Development → Debugging.

Berdasarkan perubahan yang kita lakukan sebelumnya

Kemungkinan terbesar ada pada blok notifikasi WA yang baru ditambahkan, misalnya:

  • ada koma yang hilang pada array,

  • ada kurung } atau ) yang tidak seimbang,

  • memanggil fungsi yang belum ada,

  • atau ada syntax error setelah penambahan:

jm_kirim_template_auto(...)

atau

$datawa = [
    ...
];

Agar bisa langsung saya temukan

Tolong kirim salah satu dari berikut:

  1. Isi file izin_guru.php terbaru, atau minimal bagian yang baru Anda ubah (terutama blok notifikasi WA), atau

  2. Hasil:

php -l /var/www/html/moodle/local/jurnalmengajar/izin_guru.php

Perintah php -l biasanya langsung menunjukkan:

Parse error:
Fatal error:
Unexpected ...
on line ...

sehingga kita bisa menemukan letak error dalam hitungan menit.

Wednesday, 1 July 2026

Modul baru: Guru Tidak Hadir

 

Menurut saya, pengembangan yang kita lakukan kali ini bukan sekadar menambah fitur, tetapi menambahkan satu modul baru yang terintegrasi dengan sistem reminder jurnal.

1. Modul baru: Guru Tidak Hadir

Dibuat tabel baru (tanpa upgrade plugin), misalnya:

mdl_local_jurnalmengajar_kehadiran

Fungsinya menyimpan data guru yang tidak hadir.

Status yang didukung:

  • Sakit

  • Izin

  • Cuti

  • Tugas Luar

Status Alpa sengaja tidak dibuat, karena guru yang alpa tetap harus masuk ke reminder jurnal.


2. CRUD Guru Tidak Hadir

Dibuat halaman administrasi:

guru_takhadir.php

beserta CRUD:

  • guru_takhadir.php

  • guru_takhadir_add.php

  • guru_takhadir_edit.php

  • guru_takhadir_delete.php

Fitur:

  • pilih guru (role local/jurnalmengajar:submit)

  • status

  • periode mulai

  • periode selesai

  • keterangan


3. Fungsi baru di lib.php

Dibuat fungsi:

jurnalmengajar_get_status_takhadir($userid, $tanggal)

Fungsinya:

  • mengecek apakah guru sedang tidak hadir

  • mengembalikan:

    • false

    • sakit

    • izin

    • cuti

    • tugasluar

Sehingga logika pengecekan cukup dipanggil dari satu fungsi.


4. Integrasi ke notif_jurnal.php

Alur lama:

Jadwal
↓
Jam terlewat
↓
Sudah isi?
↓
Reminder

Sekarang menjadi:

Jadwal
↓
Cutoff
↓
Jam terlewat
↓
Guru Tidak Hadir?
│
├── Ya
│     ↓
│  Rekap Guru Tidak Hadir
│
└── Tidak
      ↓
 Sudah isi jurnal?
      │
      ├── Ya
      └── Belum → Reminder

Akibatnya:

  • Guru sakit tidak menerima reminder.

  • Guru izin tidak menerima reminder.

  • Guru cuti tidak menerima reminder.

  • Guru tugas luar tidak menerima reminder.

  • Guru alpa tetap menerima reminder.


5. Rekap Admin

Rekap yang sebelumnya hanya berisi:

Guru Belum Mengisi Jurnal

sekarang menjadi:

Rekap Jurnal Mengajar

Belum Mengisi Jurnal
...

Guru Tidak Hadir
...

Sehingga admin dapat membedakan:

  • siapa yang memang lupa mengisi jurnal,

  • siapa yang memang tidak masuk.


6. Template WhatsApp

Ditambahkan placeholder baru:

{tidakhadir}

Template default juga diperbarui agar menampilkan dua bagian:

  • Belum Mengisi Jurnal

  • Guru Tidak Hadir


7. Debug CLI

Ditambahkan debug baru:

FILLED:
TAKHADIR:

Contoh:

TAKHADIR: Ahmad | XI-A | Sakit
TAKHADIR: Ahmad | XI-B | Sakit

Sehingga proses pengecekan lebih mudah ditelusuri saat menjalankan:

php local/jurnalmengajar/cli/notif_jurnal.php

Hasil akhir

Sekarang sistem reminder jurnal mampu membedakan tiga kondisi guru:

Kondisi GuruReminder WARekap Admin
Sudah mengisi jurnal❌ Tidak❌ Tidak
Belum mengisi jurnal✅ Ya✅ Ya
Sakit / Izin / Cuti / Tugas Luar❌ Tidak✅ Ya

Dampak bagi plugin

Menurut saya, ini adalah peningkatan yang cukup signifikan karena sebelumnya sistem hanya mengenal dua kondisi:

  • Sudah mengisi jurnal.

  • Belum mengisi jurnal.

Sekarang sistem mengenal tiga kondisi operasional:

  1. Sudah mengisi jurnal → tidak perlu tindakan.

  2. Belum mengisi jurnal → dikirim reminder.

  3. Guru tidak hadir (sakit, izin, cuti, tugas luar) → tidak dikirim reminder, tetapi tetap tercatat dalam rekap admin.

Dengan demikian, reminder menjadi lebih akurat dan rekap yang diterima admin lebih informatif serta sesuai dengan kondisi pembelajaran di sekolah.

debug diagnostik rekap reminder

 <?php
define('CLI_SCRIPT', true);
require_once(__DIR__.'/../../../config.php');

require_once(__DIR__.'/../jam_pelajaran_lib.php');
require_once(__DIR__.'/../jadwal_acuan_lib.php');
require_once(__DIR__.'/../lib.php'); 
require_once(__DIR__.'/../lib_notifikasi.php');// fungsi kirim WA

global $DB;

$cohortmap = [];
$cohorts = $DB->get_records('cohort', null, '', 'id,name');
foreach ($cohorts as $c) {
    $cohortmap[$c->id] = $c->name;
}

$today = date('Y-m-d');
$hariIndo = jurnalmengajar_get_hari_ini();
$current = time();
$todayLabel = tanggal_indo(time());

$jamrekap = '19:50';
$jamsekarang = date('H:i');
//$isrekap = ($jamsekarang >= $jamrekap);
$isrekap = true; //test mode rekap

// ===== Cek hari sekolah =====
$hariSekolah = get_config('local_jurnalmengajar', 'harisekolah');

if (empty($hariSekolah)) {
    $hariSekolah = 'Senin,Selasa,Rabu,Kamis,Jumat';
}

$hariSekolah = array_map('trim', explode(',', $hariSekolah));

if (!in_array($hariIndo, $hariSekolah)) {
    mtrace("Hari $hariIndo bukan hari sekolah.");
    exit(0);
}

// ===== Cek tanggal libur =====
if (jurnalmengajar_cek_libur($today)) {
    mtrace("Hari ini tanggal libur.");
    exit(0);
}

// ===== Cek tanggal asesmen =====
$tanggalasesmen = trim(get_config('local_jurnalmengajar', 'tanggalasesmen'));

if (!empty($tanggalasesmen)) {

    if (preg_match('/(\d{4}-\d{2}-\d{2})\s*s\/d\s*(\d{4}-\d{2}-\d{2})/i',
        $tanggalasesmen,
        $match)) {

        $mulai  = strtotime($match[1]);
        $selesai = strtotime($match[2]);
        $hariini = strtotime($today);

        if ($hariini >= $mulai && $hariini <= $selesai) {
            mtrace("Hari ini berada dalam rentang asesmen.");
            exit(0);
        }
    }
}

mtrace("=== Notifikasi Jurnal Rekap ===");
mtrace("Hari: $hariIndo");

// ===== Ambil jam pelajaran =====
$jam_pelajaran = jurnalmengajar_generate_jam();

// ===== Tentukan jam yang sudah selesai =====
$jam_terlewat = [];
foreach ($jam_pelajaran as $jamke => $jam) {
    $selesai = $jam['selesai'];
    if ($current > strtotime("$today $selesai")) {
        $jam_terlewat[] = $jamke;
    }
}

if (empty($jam_terlewat)) {
    mtrace("Belum ada jam pelajaran yang terlewat.");
    exit(0);
}

mtrace("Jam terlewat: " . implode(',', $jam_terlewat));

// ===== Ambil jurnal hari ini =====
$starttoday = strtotime("$today 00:00:00");
$endtoday   = strtotime("$today 23:59:59");

$jurnaltoday = $DB->get_records_sql("
    SELECT id, userid, kelas, jamke
    FROM {local_jurnalmengajar}
    WHERE timecreated BETWEEN :starttoday AND :endtoday
", [
    'starttoday' => $starttoday,
    'endtoday'   => $endtoday
]);

$filled = [];
foreach ($jurnaltoday as $row) {
    foreach (explode(',', $row->jamke) as $j) {
        $j = (int)trim($j);

        // Samakan kelas dengan jadwal
        $kelas = $row->kelas;
        if (isset($cohortmap[$kelas])) {
            $kelas = $cohortmap[$kelas];
        }

        $key = $row->userid . '-' . $kelas . '-' . $j;
        $filled[$key] = true;

        // Debug
        mtrace("FILLED: " . $key);
    }
}
//

// ===== Ambil jadwal dari database =====
$jadwal_db = $DB->get_records_sql("
    SELECT j.id, j.userid, j.kelas, j.jamke, u.lastname
    FROM {local_jurnalmengajar_jadwal} j
    JOIN {user} u ON u.id = j.userid
    WHERE j.hari = :hari
", [
    'hari' => $hariIndo
]);

$jadwal = [];

foreach ($jadwal_db as $j) {
    $jadwal[] = [
        'userid'   => $j->userid,
        'lastname' => $j->lastname,
        'kelas'    => $j->kelas,
        'jamke'    => $j->jamke
    ];
}

if (empty($jadwal)) {
    mtrace("Tidak ada jadwal di database untuk hari $hariIndo");
    exit(0);
}
mtrace("=== JADWAL ===");
foreach ($jadwal as $j) {
    $k = $j['userid'].'-'.$j['kelas'].'-'.$j['jamke'];
    mtrace("JADWAL: " . $k);
}

// ===== Group jurnal yang belum diisi =====
$pending = [];
$tidakhadir = [];
$cutoff_cache = [];
foreach ($jadwal as $j) {

// 🔥 FILTER CUT OFF MULTI KELAS
$kelas_level = null;

// deteksi kelas (VI, IX, XII)
if (preg_match('/\b(VI|IX|XII)\b/i', $j['kelas'], $match)) {
    $kelas_level = strtoupper($match[1]);
}

if ($kelas_level) {

    if (!isset($cutoff_cache[$kelas_level])) {
        $cutoff_cache[$kelas_level] = jurnalmengajar_get_cutoff_by_kelas($kelas_level, $current);
    }

    $cutoff = $cutoff_cache[$kelas_level];

    if ($cutoff && $current >= $cutoff) {
        continue;
    }
}

// ===== Lewati jika jam belum selesai =====
if (!in_array((int)$j['jamke'], $jam_terlewat)) {
    continue;
}

// ===== Cek Guru Tidak Hadir =====
$status = jurnalmengajar_get_status_takhadir(
    $j['userid'],
    $today
);

if ($status !== false) {

    if (!isset($tidakhadir[$j['userid']])) {

        $tidakhadir[$j['userid']] = [
            'lastname' => $j['lastname'],
            'status'   => $status
        ];
    }

    // Debug: tampilkan semua kelas yang sudah terlewat
    mtrace(
        "TAKHADIR: {$j['lastname']} | {$j['kelas']} | " .
        ucfirst($status)
    );

    continue;
}
    $key = $j['userid'] . '-' . $j['kelas'] . '-' . (int)$j['jamke'];

if (isset($filled[$key])) {
    continue;
}

    if (!isset($pending[$j['userid']])) {
        $pending[$j['userid']] = [
            'lastname' => $j['lastname'],
            'kelasjam' => []
        ];
    }

    if (!isset($pending[$j['userid']]['kelasjam'][$j['kelas']])) {
        $pending[$j['userid']]['kelasjam'][$j['kelas']] = [];
    }

    $pending[$j['userid']]['kelasjam'][$j['kelas']][] = (int)$j['jamke'];
}

if (empty($pending) && empty($tidakhadir)) {
    mtrace("Semua jurnal sudah diisi.");
    exit(0);
}
// ===== Kirim WA per guru =====
$mengirim = 0;

if (!$isrekap) {

    mtrace("Mode: Reminder Guru");

    foreach ($pending as $userid => $info) {

//    $user = $DB->get_record('user', ['id'=>$userid], 'id, firstname, lastname');

    // Ambil nomor WA
    $nowa = $DB->get_field_sql("
        SELECT d.data
        FROM {user_info_data} d
        JOIN {user_info_field} f ON f.id = d.fieldid
        WHERE d.userid = :userid AND f.shortname = 'nowa'
    ", ['userid' => $userid]);

    if (empty($nowa)) {
        mtrace("Tidak ada nomor WA untuk {$info['lastname']}");
        continue;
    }

    $nomor = preg_replace('/[^0-9]/', '', $nowa);

    // Urutkan berdasarkan jam pertama
$urut = [];

foreach ($info['kelasjam'] as $kelas => $jamlist) {
    $jamlist = array_unique($jamlist);
    sort($jamlist);
    $urut[$kelas] = $jamlist;
}

// Sort berdasarkan jam pertama
uasort($urut, function($a, $b) {
    return $a[0] <=> $b[0];
});

$listkelas = "";
$ringkasParts = [];

foreach ($urut as $kelas => $jamlist) {
    $listkelas .= "$kelas jam ke " . implode(',', $jamlist) . "\n";
    $ringkasParts[] = $kelas . ':' . implode(',', $jamlist);
}

    $ringkas = implode('; ', $ringkasParts);

    $datawa = [
        '{guru}'     => $info['lastname'],
        '{tanggal}'  => $todayLabel,
        '{kelasjam}' => trim($listkelas)
    ];

        $res = jm_kirim_template(
        'reminder_jurnal',
        $nomor,
        $datawa
    );

    $pending[$userid]['ringkas'] = $ringkas;
    
    mtrace("Kirim ke $nomor ({$info['lastname']}) -> $res");
    if ($res) {
    $mengirim++;
}

    // ===== Log TXT =====
$logtxt = __DIR__ . '/notif_log_' . date('Y-m-d') . '.txt';

$logstatus = $res ? 'BERHASIL' : 'GAGAL';

$line = date('Y-m-d H:i:s')
      . " | Guru: {$info['lastname']}"
      . " | Nomor: $nomor"
      . " | Kelas/Jam: $ringkas"
      . " | Status: $logstatus"
      . "\n";

file_put_contents($logtxt, $line, FILE_APPEND);
}

} else {

    // Mode rekap: tidak kirim WA ke guru,
    // hanya membuat ringkasan.
    mtrace("Mode: Rekap Admin");

    foreach ($pending as $userid => $info) {

        $urut = [];

        foreach ($info['kelasjam'] as $kelas => $jamlist) {
            $jamlist = array_unique($jamlist);
            sort($jamlist);
            $urut[$kelas] = $jamlist;
        }

        uasort($urut, function($a, $b) {
            return $a[0] <=> $b[0];
        });

        $ringkasParts = [];

        foreach ($urut as $kelas => $jamlist) {
            $ringkasParts[] = $kelas . ':' . implode(',', $jamlist);
        }

        $pending[$userid]['ringkas'] = implode('; ', $ringkasParts);
    }
}

if ($isrekap) {

$daftar = '';
$daftartakhadir = '';

if (empty($tidakhadir)) {
    $daftartakhadir = '-';
}
    
    foreach ($pending as $info) {
        $daftar .= "• {$info['lastname']} - {$info['ringkas']}\n";
    }

foreach ($tidakhadir as $info) {

    $daftartakhadir .=
        "• {$info['lastname']} - " .
        ucfirst($info['status']) .
        "\n";
}

    $datawa = [
        '{tanggal}' => $todayLabel,
        '{daftar}'  => trim($daftar),
        '{jumlah}'  => count($pending),
        '{tidakhadir}'  => trim($daftartakhadir)
    ];

// DEBUG
$config = get_config(
    'local_jurnalmengajar',
    'tujuan_rekap_reminder'
);

mtrace("CONFIG TUJUAN : " . $config);

$nomor = jm_get_nomor_tujuan(
    'rekap_reminder',
    $datawa
);

mtrace("NOMOR TUJUAN:");
print_r($nomor);

$template = jm_get_template('rekap_reminder');

mtrace("TEMPLATE:");
mtrace(empty($template) ? "(KOSONG)" : "ADA");

$res = jm_kirim_template_auto(
    'rekap_reminder',
    $datawa
);

if ($res) {
    mtrace("Rekap reminder dikirim.");
} else {
    mtrace("Rekap reminder gagal.");
}
}

mtrace("Selesai. Total notifikasi dikirim: $mengirim");

Tuesday, 30 June 2026

tambahkan 15 hari

 

Karena kolom program di database menggunakan ENUM, maka ada 2 langkah.

1. Ubah ENUM di database

Cek sekarang:

DESC jamaah;

Lalu ubah menjadi:

ALTER TABLE jamaah
MODIFY program ENUM(
    '3_hari',
    '10_hari',
    '15_hari',
    '40_hari',
    '4_bulan',
    'lainnya'
);

2. Ubah semua <select name="program">

Di tambah.php dan edit.php, cari bagian:

<option value="10_hari"
<?= (($_POST['program'] ?? '') == '10_hari') ? 'selected' : '' ?>>

10 Hari

</option>

<option value="40_hari"
<?= (($_POST['program'] ?? '') == '40_hari') ? 'selected' : '' ?>>

40 Hari

</option>

Sisipkan di tengah:

<option
value="15_hari"
<?= (($_POST['program'] ?? '') == '15_hari') ? 'selected' : '' ?>>

15 Hari

</option>

Sehingga urutannya menjadi:

3 Hari
10 Hari
15 Hari
40 Hari
4 Bulan
Lainnya

Jangan lupa

Kalau ada halaman lain yang menampilkan program (misalnya index.php, rute.php, rekap.php), tidak perlu diubah karena yang disimpan tetap string 15_hari. Yang berubah hanya pilihan pada form dan nilai ENUM di database.

Monday, 29 June 2026

Whatsapp WEB PWA

 

Bagus, sekarang sudah jelas. Anda tidak menggunakan Chrome + Chromium, melainkan dua profil Google Chrome:

  • Default → kemungkinan WhatsApp pribadi

  • Profile_1 → kemungkinan WhatsApp Business

Ini justru lebih bagus daripada memakai Chromium.

Cek mana yang mana

Lihat isi masing-masing launcher:

grep '^Name=' ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Default.desktop

grep '^Name=' ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Profile_1.desktop

Sekarang ubah namanya.

WhatsApp pribadi

nano ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Default.desktop

Ubah:

Name=WhatsApp

WhatsApp Business

nano ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Profile_1.desktop

Ubah:

Name=WhatsApp Business

Simpan, lalu jalankan:

update-desktop-database ~/.local/share/applications

Logout-login, atau tekan Alt+F2, ketik r (jika menggunakan X11), atau cukup tutup dan buka kembali menu aplikasi.


Lebih bagus lagi: bedakan ikonnya

Mari kita lihat isi launcher yang kedua:

cat ~/.local/share/applications/chrome-hnpfjngllnobngcgfapefoaidbinmjnm-Profile_1.desktop

Terutama saya ingin melihat baris:

Icon=

Kalau Chrome menggunakan ikon lokal, kita bisa menggantinya dengan logo WhatsApp Business sehingga nanti di Dock tampil seperti ini:

  • 🟢 WhatsApp

  • 🟢🅱 WhatsApp Business

Jadi tidak akan pernah tertukar.

Menurut saya ini adalah konfigurasi yang paling rapi untuk penggunaan sehari-hari.

Thursday, 25 June 2026

FATAL_ERROR: Exiting! mocp

  pkill 5050
dhwan@dhwan-Aspire-A514-51K:~$ mocp
Running the server...

It seems that the server is already running with pid 5050.
If it is not true, remove the pid file (/home/dhwan/.moc/pid) and try again.

FATAL_ERROR: Exiting!


FATAL_ERROR: Server exited!



Pesan itu berarti proses MOC server masih dianggap berjalan dengan PID 5050, tetapi kemungkinan prosesnya sudah mati (stale PID).

Coba periksa dulu apakah proses 5050 benar-benar masih ada:

ps -p 5050

Jika hasilnya kosong, misalnya:

PID TTY          TIME CMD

atau tidak ada proses, hapus file PID:

rm ~/.moc/pid

Lalu jalankan lagi:

mocp

Jika proses 5050 memang masih ada

Matikan dengan benar:

kill 5050

Jika masih membandel:

kill -9 5050

Lalu jalankan lagi:

mocp

Kalau kill sudah dilakukan tetapi tetap muncul

Cek apakah ada beberapa proses MOC:

pgrep -a mocp
pgrep -a moc
ps aux | grep moc

Tolong kirimkan hasil dari ketiga perintah berikut:

ps -p 5050
ls -l ~/.moc/pid
cat ~/.moc/pid

Dari situ bisa diketahui apakah ini proses yang masih hidup atau hanya file PID yang tertinggal.



Sunday, 21 June 2026

Kekurangan Guru Kelebihan Jam hitung 2026/2027

 Kurang Guru
1 Al Quran = 42 JP 
2 Sejarah = 27 JP 
3 Bahasa Inggris = 19 JP 
4 PJOK = 19 JP 
5 Bahasa Indonesia = 18 JP 
6 KWU = 18 JP 
7 Matematika = 13 JP 
8 Biologi = 10 JP 
9 Ekonomi = 8 JP 
10 Fisika = 7 JP 
11 Sosiologi = 6 JP 
12 Seni = 4 JP 
13 Geografi = 4 JP 
14 PKN = 4 JP 
15 Informatika = 2 JP

bila dipakai untuk wakasek
Seni    6
Matematika    6
Kewirausahaan (KWU)    7
Sosiologi    4
Ekonomi    6

menjadi
1 Al Quran = 42 JP 
2 Sejarah = 27 JP 
3 Bahasa Inggris = 19 JP 
4 PJOK = 19 JP 
5 Bahasa Indonesia = 18 JP 
6 KWU = 18 JP - seni 2 = 16 JP - KWU 7 = 11 JP
7 Matematika = 13 JP - matematika 6 = 7 JP
8 Biologi = 10 JP 
9 Ekonomi = 8 JP - ekonomi 6 = 2 JP
10 Fisika = 7 JP 
11 Sosiologi = 6 JP - sosiologi 4 = 2 JP
12 Seni = 4 JP     - seni 4 = 0
13 Geografi = 4 JP 
14 PKN = 4 JP 
15 Informatika = 2 JP

Kesimpulan
Kelebihan jam
1 Al Quran = 42 JP 
2 Sejarah = 27 JP 
3 Bahasa Inggris = 19 JP 
4 PJOK = 19 JP 
5 Bahasa Indonesia = 18 JP 
6 KWU = 11 JP
7 Matematika = 7 JP
8 Biologi = 10 JP 
9 Ekonomi = 2 JP
10 Fisika = 7 JP 
11 Sosiologi =  2 JP
12 Geografi = 4 JP 
13 PKN = 4 JP 
14 Informatika = 2 JP




Friday, 19 June 2026

Locking detail penjelasan kunci

 

Menu Timetable → Locking digunakan setelah FET berhasil membuat jadwal.

Fungsinya untuk mengunci hasil jadwal supaya ketika Generate ulang, aktivitas tertentu tidak dipindah lagi.


1. Lock all activities of the current timetable

Lock all activities of the current timetable

Mengunci seluruh jadwal yang sudah dihasilkan.

Contoh:

Senin JP1 Matematika X-A
Senin JP2 Fisika X-A
...

semuanya dikunci.

Saat Generate lagi, FET harus mempertahankan posisi tersebut.


2. Unlock all activities of the current timetable

Unlock all activities of the current timetable

Membuka semua kunci.

FET bebas menyusun ulang dari awal.


3. Lock all activities of a specified day

Lock all activities of a specified day

Mengunci seluruh aktivitas pada hari tertentu.

Misalnya:

Senin

maka semua jadwal Senin tetap.

FET hanya boleh mengubah Selasa–Jumat.


4. Unlock all activities of a specified day

Kebalikan dari nomor 3.


5. Lock all activities which end students day

Lock all activities which end students day

Mengunci aktivitas yang berada pada jam terakhir siswa.

Jarang dipakai.


6. Lock all activities with a specified activity tag

Misalnya Anda punya tag:

Praktikum
Agama
Lab

Semua aktivitas dengan tag tersebut dikunci.


7. Lock all activities selected with an advanced filter

Ini yang paling berguna.

Misalnya Anda ingin mengunci semua:

Matematika

atau

Guru tertentu

atau

Kelas X-A

tanpa mengunci yang lain.


Kapan digunakan?

Misalnya:

  1. Jadwal sudah bagus 95%.

  2. Hanya guru Matematika yang perlu diperbaiki.

Maka:

Lock all activities of current timetable

lalu buka (unlock) hanya aktivitas Matematika.

Kemudian Generate lagi.

FET hanya mengubah Matematika, yang lain tetap.


Untuk kasus SMA N 2 Kandangan sekarang

Saya sarankan jangan gunakan Locking dulu.

Karena Anda masih:

  • Menambah constraint MGMP

  • Mengatur jam pengawas

  • Mengatur batas jam guru

Biarkan FET menyusun ulang secara bebas sampai semua constraint final.

Locking biasanya dipakai setelah jadwal hampir selesai 100% dan hanya ada sedikit perbaikan.

Thursday, 18 June 2026

atur jadwal mengajar

  • Hanya 3 JP: 4 guru
  • Hanya 2 JP: 12 guru
  • 2 JP dan 3 JP: 24 guru
  • Memiliki 1 JP: Misran S.Pd dan Syamdani S.Pd

Jumlah keseluruhan = 42 guru.

Guru yang memiliki aktivitas 2 JP dan juga 3 JP (minimal satu aktivitas 2 JP dan minimal satu aktivitas 3 JP) adalah:

  1. Ahmad Hafie S.Pd
  2. Anna Herlina S.Pd
  3. Dra. Hj. Tri Krisnawati M.Pd
  4. Drs. Muhammad Rafi'i M.Pd
  5. Fajri Wahyudi S.Pd
  6. Farindra Ramadhan P. S.Pd
  7. Fitria Muallifah S.Pd
  8. Hartati Jum'ah S.Pd
  9. Hj. Mahliyani S.Pd
  10. Hj. Samnah Hayati M.Pd
  11. Hj. Silfiana Hasni S.Pd
  12. Indah Wiratesta Manik S.Th
  13. Khusnul Khotimah S.Pd
  14. Mariatul Qibtiah S.Pd
  15. Muhammad Naufal Akbar M.Pd
  16. Muhammad Saubari S.Pd.I
  17. Mukhtar Fuaddi S.Pd
  18. Nadya Rahmi Sari S.Pd
  19. Nini Rahmini S.Pd.I
  20. Noor Ridhwan S.Pd M.A
  21. Risyandi S.Pd
  22. Sri Hidayati S.Pd
  23. Virawati S.Pd
  24. Yekti Hanani S.Pd MA

Total: 24 guru.

Guru yang hanya memiliki aktivitas 2 JP

  1. Ainayya Almadiyati S.Pd
  2. Ari Deviyanti S.Pd.
  3. Dra. Hj. Marfuah M.Pd
  4. Dwiyani Lestari M.Pd
  5. Ema Susi Darmawati S.Pd
  6. Fatia Elfira Luthfyari S.Pd
  7. Hj. Rianawati S.Pd
  8. Hj. St. Faridah S.Pd
  9. M. Riduan S.Pd
  10. Muhammad Noor S.Pd.
  11. Muhammad Supiani S.IP
  12. Sri Heldawati S.Pd

Total: 12 guru.

 

Guru yang seluruh aktivitasnya hanya berdurasi 3 JP (tidak memiliki aktivitas 1 JP, 2 JP, atau durasi lain) adalah:

  1. Fathah Arsyad S.Pd
  2. Muhammad Wildan Ariandi S.Pd
  3. Rakhmad Taufik S.Pd
  4. Sri Muliyatini S.Pd

======================================= 

Max 7 jam/hari

Drs. Muhammad Rafi'i M.Pd      35
Muhammad Saubari S.Pd.I        35
Nini Rahmini S.Pd.I            35
Ainayya Almadiyati S.Pd        34
Fathah Arsyad S.Pd             33
Muhammad Wildan Ariandi S.Pd   33
Ahmad Hafie S.Pd              32
Fajri Wahyudi S.Pd            32

Max 6 jam/hari

Indah Wiratesta Manik S.Th     30
Rakhmad Taufik S.Pd            30
Sri Muliyatini S.Pd            30
Hj. Silfiana Hani S.Pd         30
Virawati S.Pd                  29
Dra. Hj. Marfuah M.Pd          28
Dwiyani Lestari M.Pd           28
Hartati Jum'ah S.Pd            28
M. Riduan S.Pd                 28
Risyandi S.Pd                  28
Muhammad Naufal Akbar M.Pd     27
Hj. Maliyani S.Pd              26
Hj. Samnah Hayati M.Pd         26
Mariatul Qibtiah S.Pd          26
Sri Heldawati S.Pd             26
Sri Hidayati S.Pd              26

Max 5 jam/hari

Anna Herlina S.Pd              25
Noor Ridhwan S.Pd M.A          25
Dra. Hj. Tri Krisnawati M.Pd   24
Ema Susi Darmawati S.Pd        24
Faridra Ramadhan P.S.Pd        24
Hj. St. Faridah S.Pd           24
Yekti Hanani S.Pd MA           24

Max 4 jam/hari

Mukhtar Fuaddi S.Pd            18
Fatia Elfira Luthfyari S.Pd    16
Hj. Rianawati S.Pd             16
Muhammad Supiani S.IP          16
Nadya Rahmi Sari S.Pd          16
Khusnul Khotimah S.Pd          15

Max 2 jam/hari

Muhammad Noor S.Pd              3
Misan S.Pd                      2
Syamdani S.Pd                   1
Ari Deviyanti S.Pd              1

Namun saya menyarankan jangan langsung memasukkan semua constraint ini.

Mulailah dari yang berat dulu:

35 JP -> max 7
34 JP -> max 7
33 JP -> max 7
32 JP -> max 7

Generate.

Kalau masih sukses, lanjut kelompok 30 JP (max 6).

Kalau langsung diberi constraint ketat untuk 42 guru sekaligus, FET bisa menjadi sulit menemukan solusi. Biasanya saya menambahkan bertahap sampai ketemu batas yang masih feasible.

 

HariGuru
Seninguru Seni, PJOK, Ekonomi
Selasaguru Sosiologi, Sejarah, PPKn, Bahasa Indonesia
Rabuguru Inggris, Geografi, Matematika, Agama
Kamisguru Fisika, Kimia, Biologi, BK

 

Saat Kenaikan Kelas

 Saat kenaikan kelas

  1.   Jalankan simpan_riwayatkelas.php.
  2. Keluarkan alumni XII dari cohort.
  3. Upload CSV siswa aktif untuk memperbarui firstname dan cohort sesuai kelas baru.
  4. Perbarui membership cohort sesuai pembagian kelas baru.
  5. Ganti setting tahun ajaran ke 2026/2027.
  6. Jalankan simpan_riwayatkelas.php.

 

Ya, karena Anda sudah berhasil mengekspor 461 siswa aktif ke CSV, maka Anda sudah punya cadangan data keanggotaan kelas saat ini.

Urutan yang saya sarankan sekarang:

1. Simpan file CSV hasil ekspor

Jangan diubah dulu. Simpan sebagai arsip:

siswa_aktif_20260618.csv

2. Kosongkan cohort aktif

Saat ini cohort yang masih berisi siswa adalah:

75  X-A
76  X-B
77  X-C
78  X-D
79  X-E
80  X-F
81  X-G

82  XI-A
83  XI-B
84  XI-C
85  XI-D
86  XI-E
87  XI-F
88  XI-G

Cek dulu jumlah yang akan dihapus:

SELECT COUNT(*)
FROM mdl_cohort_members
WHERE cohortid IN (
75,76,77,78,79,80,81,
82,83,84,85,86,87,88
);

Harusnya hasil:

461

Kalau benar, lanjut:

DELETE FROM mdl_cohort_members
WHERE cohortid IN (
75,76,77,78,79,80,81,
82,83,84,85,86,87,88
);

3. Verifikasi

SELECT COUNT(*)
FROM mdl_cohort_members;

Target:

0

Artinya seluruh siswa sudah tidak punya cohort.


4. Edit CSV

Misalnya:

X-A  → XI-C
X-B  → XI-A
XI-A → XII-B
XI-B → XII-D

Isi kolom:

firstname
cohort1

dengan kelas baru.


5. Upload kembali

Moodle:

Site administration
→ Users
→ Upload users

Pilih:

Update existing users only

dan aktifkan:

Allow cohort assignments

Maka:

  • firstname berubah menjadi kelas baru.

  • User masuk ke cohort baru.

  • Tidak ada sisa cohort lama karena sudah dikosongkan.


Satu hal yang perlu dicek dulu

Sebelum menghapus 461 anggota cohort, pastikan cohort X-A s.d XII-E tetap ada (yang dikosongkan hanya anggotanya).

Cek:

SELECT id,name
FROM mdl_cohort
ORDER BY name;

Kalau cohortnya masih ada, maka aman untuk mengosongkan mdl_cohort_members dan mengisi ulang melalui CSV.

 

Wednesday, 10 June 2026

Convert PDF lebih dari 2MB menjadi maksimal 2MB

 find . -maxdepth 1 -name "*.pdf" -size +2M -print0 |
while IFS= read -r -d '' f; do
echo "Memproses: $f"

gs \
-sDEVICE=pdfwrite \
-dCompatibilityLevel=1.4 \
-dPDFSETTINGS=/screen \
-dNOPAUSE \
-dQUIET \
-dBATCH \
-sOutputFile="${f%.pdf}-2mb.pdf" \
"$f"
done



Tuesday, 9 June 2026

Web Ui modem Huawei Reloading terus

 

Kalau Firefox normal terus reload (loading berputar terus) tetapi Firefox Private Mode bisa membuka 192.168.8.1 dengan normal, hampir pasti masalahnya bukan di modem E3276s, melainkan di browser.

Penyebab yang paling sering:

1. Cache WebUI Huawei rusak

Firefox menyimpan JavaScript dan file CSS lama dari WebUI Huawei.

Coba:

  1. Buka:

    about:preferences#privacy
    
  2. Cari Cookies and Site Data

  3. Klik Manage Data

  4. Cari:

    192.168.8.1
    
  5. Hapus data situs tersebut.

Atau lebih cepat:

  • Tekan Ctrl+Shift+Delete

  • Pilih Cached Web Content

  • Clear.


2. Service Worker atau Local Storage bermasalah

WebUI Huawei suka menyimpan data lokal yang membuat halaman looping.

Tekan:

F12

→ Storage

→ Hapus:

  • Local Storage

  • Session Storage

  • IndexedDB

untuk 192.168.8.1


3. Add-on Firefox

Karena Private Mode biasanya menonaktifkan sebagian add-on.

Coba:

about:addons

sementara nonaktifkan:

  • uBlock Origin

  • Privacy Badger

  • NoScript

  • AdGuard

  • Cookie AutoDelete

Lalu buka lagi 192.168.8.1.


4. DNS over HTTPS Firefox

Kadang Firefox mencoba melakukan hal aneh pada alamat lokal.

Buka:

about:preferences#general

Network Settings → Settings

Matikan:

Enable DNS over HTTPS

5. Refresh Firefox

Jika hanya WebUI Huawei yang bermasalah:

Buka:

Ctrl+Shift+R

(Hard Reload)

atau

Ctrl+F5

Tes cepat

Di Firefox normal, buka:

http://192.168.8.1/api/device/information

atau

http://192.168.8.1/html/home.html

Jika tetap looping tetapi Private Mode lancar, kemungkinan cache/cookie WebUI Huawei yang korup.

Karena tadi Guru sempat mengubah band dan modem menggunakan WebUI modifikasi (ada tombol WatchCat), saya paling curiga cache JavaScript WebUI lama tersimpan di Firefox.

Coba hapus data situs khusus 192.168.8.1 dulu, tidak perlu menghapus cache seluruh Firefox. Biasanya langsung normal kembali.

cari file yang diubah hari ini dan kemarin



Kesimpulan untuk plugin Jurnal Mengajar

Jika Guru ingin melihat:

File yang diubah hari ini

find /var/www/html/moodle/local/jurnalmengajar -type f -daystart -mtime 0

File yang diubah kemarin

find /var/www/html/moodle/local/jurnalmengajar -type f -daystart -mtime 1

File yang diubah 2 hari terakhir (berdasarkan kalender)

find /var/www/html/moodle/local/jurnalmengajar -type f -daystart -mtime -2

File yang diubah 48 jam terakhir (rolling time)

find /var/www/html/moodle/local/jurnalmengajar -type f -mtime -2

Untuk audit perubahan plugin Moodle, saya biasanya lebih memilih:

find /var/www/html/moodle/local/jurnalmengajar \
-type f \
-daystart \
-mtime -2 \
-printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort

karena langsung terlihat file mana yang kita ubah hari ini dan kemarin berdasarkan tanggal kalender, yang biasanya lebih mudah dicocokkan dengan riwayat pekerjaan Guru Duan.

Convert jpg ke pdf massal

 cd "/mnt/GURU/GURU ONE/Surat Berharga/Ijazah"

for f in *.jpg; do
    convert "$f" \
        -resize 1200x1200\> \
        -density 100 \
        -quality 40 \
        "${f%.jpg}.pdf"
done

 

Tentu Guru Duan. Script tersebut akan mengubah setiap file JPG menjadi PDF secara otomatis.

cd "/mnt/GURU/GURU ONE/Surat Berharga/Ijazah"

for f in *.jpg; do
    convert "$f" \
        -resize 1200x1200\> \
        -density 100 \
        -quality 40 \
        "${f%.jpg}.pdf"
done

1. Pindah ke folder

cd "/mnt/GURU/GURU ONE/Surat Berharga/Ijazah"

Masuk ke folder tempat file JPG berada.


2. Perulangan semua file JPG

for f in *.jpg; do

Artinya:

  • Cari semua file yang berakhiran .jpg

  • Simpan nama file satu per satu ke variabel f

Contoh:

Putaran pertama:

f="IJAZAH.jpg"

Putaran kedua:

f="IJAZAH MTs.jpg"

Putaran ketiga:

f="nem_SD.jpg"

dan seterusnya sampai semua file selesai.


3. Convert JPG ke PDF

convert "$f"

Misalnya:

convert "IJAZAH.jpg"

ImageMagick membaca gambar tersebut.


4. Resize gambar

-resize 1200x1200\>

Tanda > sangat penting.

Artinya:

Jika ukuran gambar lebih besar dari 1200×1200, perkecil.

Jika sudah lebih kecil, jangan dibesarkan.

Contoh:

Ukuran asliHasil
4000×30001200×900
2400×17001200×850
800×600tetap 800×600

Tujuannya agar ukuran PDF lebih kecil.


5. Density

-density 100

Menentukan resolusi PDF.

Umumnya:

DensityKualitas
72sangat kecil
100cukup jelas
150bagus
300kualitas scan tinggi

Untuk arsip dan upload online, 100 biasanya cukup.


6. Quality

-quality 40

Kompresi JPEG.

QualityHasil
100hampir tanpa kompresi
80bagus
60cukup
40kecil
20sangat kecil

Semakin kecil angkanya, ukuran file semakin kecil tetapi tulisan bisa mulai buram.


7. Nama PDF hasil

"${f%.jpg}.pdf"

Bagian:

${f%.jpg}

berarti:

Hapus .jpg dari nama file.

Contoh:

File JPGPDF hasil
IJAZAH.jpgIJAZAH.pdf
nem_SD.jpgnem_SD.pdf
IJAZAH MTs.jpgIJAZAH MTs.pdf

Contoh nyata

Saat:

f="IJAZAH.jpg"

Perintah yang dijalankan menjadi:

convert "IJAZAH.jpg" \
    -resize 1200x1200\> \
    -density 100 \
    -quality 40 \
    "IJAZAH.pdf"

Kemudian lanjut ke file berikutnya.


Perkiraan hasil untuk file Guru Duan

Misalnya:

JPG AsliPDF Hasil
3.6 MB300–700 KB
2.8 MB250–600 KB
1.1 MB100–300 KB

Jadi seluruh dokumen ijazah/transkrip kemungkinan akan berada jauh di bawah batas 2 MB per file setelah dikonversi dengan pengaturan tersebut.

 

Saturday, 6 June 2026

Ganti kode plugin situs tunggal untuk shared kode


lakukan penggantian:

find . -type f -name "*.php" -exec sed -i \
"s|require_once(__DIR__ . '/../../config.php');|require_once(\$_SERVER['DOCUMENT_ROOT'] . '/config.php');|g" {} +

find . -type f -name "*.php" -exec sed -i \
"s|require_once(__DIR__ . '/../../config.php');|require_once(\$_SERVER['DOCUMENT_ROOT'] . '/config.php');|g" {} +


verifikasi:
grep -R "DOCUMENT_ROOT" .

pastikan tidak ada"
grep -R "__DIR__ . '/../../config.php'" .
cek 
require(__DIR__ . '/../../config.php');
require('../../config.php');
require_once('../../config.php');


ganti yang masih pakai:
find . -maxdepth 1 -type f -name "*.php" \
-exec sed -i \
"s|require(__DIR__ . '/../../config.php');|require_once(\$_SERVER['DOCUMENT_ROOT'] . '/config.php');|g" {} +

verifikasi lagi:
grep -R "config.php" . --include="*.php"


find . -maxdepth 1 -type f -name "*.php" -exec sed -i \
"s|require_once('../../config.php');|require_once(\$_SERVER['DOCUMENT_ROOT'] . '/config.php');|g" {} +

cek syntax error:
php -l index.php

dan
find . -name "*.php" -exec php -l {} \; | grep -v "No syntax errors"


**kirim ke stb:**
rsync -avz --delete \
/home/dhwan/moodle_all/jurnaldo/SIMG-mai/SIMG-main/ \
root@stb:/var/www/shared/local/jurnalmengajar/

**sesudah transfer:**
cd /var/www/shared/local/jurnalmengajar
find . -name "*.php" -exec php -l {} \; | grep -v "No syntax errors"

Ganti Label dan tambah jenis Pembinaan oleh Guru Mata Pelajaran

 nano /var/www/html/moodle/local/jurnalmengajar/classes/form/jurnal_form.php

 

Mudah Guru Duan.

1. Ganti label Catatan → Masalah

Cari:

$mform->addElement(
    'textarea',
    'catatan_pembinaan',
    'Catatan',
    'rows="3" cols="60"'
);

Ganti menjadi:

$mform->addElement(
    'textarea',
    'catatan_pembinaan',
    'Masalah',
    'rows="3" cols="60"'
);

2. Ganti label Tindak Lanjut → Tindak Lanjut / Solusi

Cari:

$mform->addElement(
    'textarea',
    'tindaklanjut_pembinaan',
    'Tindak Lanjut',
    'rows="3" cols="60"'
);

Ganti menjadi:

$mform->addElement(
    'textarea',
    'tindaklanjut_pembinaan',
    'Tindak Lanjut / Solusi',
    'rows="3" cols="60"'
);

3. Menambah pilihan Jenis Pembinaan

Sekarang kemungkinan seperti ini:

$jenispembinaan = [
    '' => '-- Pilih Jenis --',
    'disiplin' => 'Disiplin',
    'sikap' => 'Sikap',
    'akademik' => 'Akademik',
    'kerapian' => 'Kerapian',
    'lainnya' => 'Lainnya'
];

Tinggal tambah item baru:

$jenispembinaan = [
    '' => '-- Pilih Jenis --',

    'disiplin' => 'Disiplin',
    'sikap' => 'Sikap',
    'akademik' => 'Akademik',
    'kerapian' => 'Kerapian',

    'kehadiran' => 'Kehadiran',
    'etika' => 'Etika',
    'ibadah' => 'Ibadah',
    'pergaulan' => 'Pergaulan',
    'motivasi' => 'Motivasi Belajar',
    'tugas' => 'Tidak Mengerjakan Tugas',
    'gadged' => 'Penggunaan HP/Gadget',

    'lainnya' => 'Lainnya'
];

Rekomendasi untuk SMA

Kalau saya melihat kebutuhan Log Perkembangan Murid di sekolah, dropdown yang lebih berguna:

$jenispembinaan = [
    '' => '-- Pilih Jenis --',

    'kehadiran' => 'Kehadiran',
    'disiplin' => 'Disiplin',
    'kerapian' => 'Kerapian',
    'sikap' => 'Sikap',

    'akademik' => 'Akademik',
    'tugas' => 'Tugas',
    'motivasi' => 'Motivasi Belajar',

    'pergaulan' => 'Pergaulan',
    'ibadah' => 'Ibadah',

    'lainnya' => 'Lainnya'
];

Karena nanti saat direkap di Log Perkembangan akan lebih mudah melihat pola masalah siswa:

Kehadiran        : 5 kali
Disiplin         : 3 kali
Akademik         : 2 kali
Pergaulan        : 1 kali

dan ini sangat membantu wali kelas serta BK.

 

Pengaturan Warna BG dan Teks Bootsrap Beranda Guru

 teks gelap background terang
<a class="btn fw-bold w-100" style="background:#7fc7f5;color:#000;"

EKSTRA BG KUNING
<a class="btn fw-bold w-100" style="background:#fde668;color:#000;"

teks putih background gelap
BG BIRU TUA TEKS PUTIH WALI KELAS
<a class="btn text-white fw-bold w-100" style="background:#0a1347;"

BG HIJAU TEKS PUTIH
<a class="btn text-white fw-bold w-100" style="background:#2e7d32;"

SURAT IZIN MURID DI PENGAWAS [MERAH BG]
            <a class="btn btn-danger btn-block w-100"
               href="izin_murid.php">
                📝 Isi Surat Izin Murid


Friday, 5 June 2026

sites available wates dan anjir 2 situ dalam armbian

 root@aml-s9xx-box:~# ls -lh /etc/nginx/sites-enabled/
total 0
lrwxrwxrwx 1 root root 37 Jun  4 20:33 data-murid -> /etc/nginx/sites-available/data-murid
lrwxrwxrwx 1 root root 34 May 10 11:25 situs_a -> /etc/nginx/sites-available/situs_a
lrwxrwxrwx 1 root root 34 May 10 18:00 situs_b -> /etc/nginx/sites-available/situs_b
root@aml-s9xx-box:~# cat /etc/nginx/sites-enabled/situs_a
server {
    listen 80;
    listen [::]:80;
    server_name anjir.simsdn.my.id;

    root /var/www/situs_a/moodle;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php(/|$) {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi_params;

        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;

        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }
}
root@aml-s9xx-box:~# cat /etc/nginx/sites-enabled/situs_b
server {
    listen 8080;
    server_name sman1wates.jurnaldo.my.id;

    root /var/www/situs_b/moodle;
    index index.php index.html;

    # 🔥 WAJIB untuk Moodle
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # 🔥 FIX utama (support slasharguments)
    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi_params;

        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;

        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }

    # 🔥 Optional (biar static file lebih cepat)
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
        expires max;
        log_not_found off;
    }
}
root@aml-s9xx-box:~# cat /etc/nginx/sites-available/data-murid
server {
    listen 8081;
    server_name murid_paduan.jurnaldo.my.id;

    root /var/www/data-murid;
    index index.html;
   
    access_log /var/log/nginx/data-murid-access.log;
    error_log  /var/log/nginx/data-murid-error.log;

    location / {
        try_files $uri $uri/ =404;
    }
}

Thursday, 4 June 2026

Sync github ke data murid situs

 

Untuk kondisi Guru Duan saat ini, saya akan memilih tahapan bertahap, jangan langsung webhook.

Tahap 1 (Sekarang) — Deploy Manual dengan Script

Buat script:

nano /opt/update-data-murid.sh

Isi:

#!/bin/bash

echo "=== UPDATE DATA MURID ==="

cd /opt/data-murid || exit

git pull

rsync -av --delete \
--exclude='.git' \
/opt/data-murid/ \
/var/www/data-murid/

echo "=== UPDATE SELESAI ==="

Simpan:

chmod +x /opt/update-data-murid.sh

Tahap 2 — Uji

Saat ada perubahan di GitHub:

cd /opt/data-murid
git status

lalu:

/opt/update-data-murid.sh

Keuntungan:

  • Sangat sederhana.

  • Tidak ada service tambahan.

  • Tidak ada port tambahan.

  • Tidak ada risiko webhook disalahgunakan.


Tahap 3 — Tambahkan Backup

Karena ini data alumni yang berharga, saya justru lebih fokus ke backup daripada otomatisasi.

Buat:

mkdir -p /backup/data-murid

Backup harian:

cp /var/www/data-murid/alumni.json \
/backup/data-murid/alumni-$(date +%F).json

Atau pakai cron seminggu sekali.


Tahap 4 — Pisahkan Log

Karena Guru Duan sudah memakai GoAccess, saya sarankan mempertahankan:

access_log /var/log/nginx/data-murid-access.log;
error_log  /var/log/nginx/data-murid-error.log;

Nanti statistik hanya untuk situs alumni.


Tahap 5 — Kalau Sudah Sering Update

Baru pertimbangkan:

Opsi A: Cron

Misalnya tiap jam:

0 * * * * /opt/update-data-murid.sh

Ini cukup aman dan sederhana.

Opsi B: Webhook GitHub

Saya baru akan memasang webhook jika:

  • Guru Duan mengubah data setiap hari.

  • Ada beberapa situs yang dikelola.

  • Ingin benar-benar otomatis setelah git push.


Yang Saya Pilih Jika Ini Server Saya

Untuk repository yang isinya hanya:

alumni.json
index.html
script.js

saya akan tetap memakai:

/opt/update-data-murid.sh

manual.

Alasannya:

  • Repository hanya ±500 KB.

  • Update tidak setiap jam.

  • HG680P sudah menjalankan beberapa situs.

  • Semakin sedikit service tambahan, semakin mudah dirawat.

Jadi alurnya:

Laptop Ubuntu
    ↓
git push
    ↓
Armbian
/opt/update-data-murid.sh
    ↓
Website langsung ter-update

Menurut saya itu sudah ideal untuk ukuran proyek data-murid saat ini. Jika nanti Guru Duan ingin menghubungkannya langsung dengan Moodle (misalnya ekspor JSON alumni otomatis dari database Moodle), barulah kita bisa membuat deploy yang lebih otomatis.

Monday, 1 June 2026

Install composer untuk keperluan export xlsx di moodle

 
Baru install Composer di STB:
apt install composer

Setelah selesai:
cd /var/www/shared/local/jurnalmengajar
composer install

prompt Hari Pancasila

PROMPT
Buat poster sinematik ultra-detail bertema Hari Lahir Pancasila – 1 Juni dengan gaya double exposure cinematic movie poster, kualitas visual setara game AAA dan film blockbuster sejarah Indonesia.
Komposisi utama menggunakan siluet besar seorang lelaki Indonesia berpeci hitam. Wajah tampak realistis, elegan, berwibawa, penuh harapan, menghadap ke arah cahaya masa depan. Detail kulit sangat realistis, pencahayaan dramatis, tekstur kain premium.
Di dalam komposisi double exposure tampil berbagai elemen sejarah dan kebangsaan Indonesia:
Potret realistis Ir. Soekarno sedang berpidato dengan mikrofon klasik.
Ribuan rakyat Indonesia mengibarkan bendera merah putih.
Burung Garuda Pancasila berukuran besar dengan sayap terbentang megah.
Monumen Nasional (Monas) berdiri gagah di latar belakang.
Pemandangan Indonesia modern dan beragam budaya Nusantara.
Cahaya matahari keemasan menembus awan sebagai simbol harapan bangsa.
Percikan debu sinematik, asap tipis, dan efek atmosfer epik.
Gunakan perspektif ultra-wide cinematic dengan kedalaman visual yang kuat:
Objek dekat berukuran besar.
Kerumunan dan elemen sejarah semakin kecil ke kejauhan.
Layer berlapis seperti poster film Hollywood.
Dominasi warna:
Merah bendera Indonesia.
Putih terang.
Emas hangat.
Sedikit nuansa hitam untuk kontras sinematik.
Tipografi besar elegan di bagian bawah:
SELAMAT MEMPERINGATI
HARI LAHIR PANCASILA
1 JUNI
Tambahkan kutipan besar:
"Pancasila bukan hanya dasar negara, tetapi jiwa bangsa yang mempersatukan kita dalam keberagaman."
— Ir. Soekarno
Tambahkan teks kecil:
"Memperkokoh Ideologi Pancasila, Menuju Indonesia Raya."
Gaya visual:
movie poster
double exposure masterpiece
epic patriotic artwork
hyper realistic
volumetric lighting
cinematic atmosphere
national hero theme
historical montage
ultra detailed
photorealistic
award winning composition
depth of field
dramatic lighting
8K
masterpiece
vertical poster 9:16
Tambahkan watermark kecil di bagian bawah:
noor.ridhwan@ymail.com

Friday, 29 May 2026

Menampilkan teks biasa di halaman php moodle

Kalau ingin menampilkan teks biasa, harus memakai:

echo

atau HTML.

Yang benar:

echo html_writer::start_div();

echo '<b>Pilih opsi berikut untuk Cetak Harian Bulanan</b><br><br>';

echo html_writer::label('Bulan', 'bulan') . ' ';

Atau lebih rapi:

echo html_writer::start_div(
    null,
    null,
    ['style' => 'margin-top:10px']
);

echo html_writer::tag(
    'div',
    'Pilih opsi berikut untuk Cetak Harian Bulanan',
    [
        'style' => '
            font-weight:bold;
            margin-bottom:8px;
            color:#0f4c81;
        '
    ]
);

echo html_writer::label('Bulan', 'bulan') . ' ';

Hasilnya nanti muncul seperti judul kecil di atas dropdown.

Thursday, 28 May 2026

Berapa kali kata "wali" disebut dalam Pengajian malam Jumat

ulun berkeksperimen hendak mengetahui kata "wali" yang disebut pada video: Majelis RGA | LIVE PENGAJIAN MALAM JUM'AT 17 JULI 2025.

dengan menggunakan script otomatis di Linux
$ yt-dlp --write-auto-sub --sub-lang id --skip-download "https://www.youtube.com/live/OvUTwCa6100?si=XkoQ-j5yRy9n3z4V"
[youtube] Extracting URL: https://www.youtube.com/live/OvUTwCa6100?si=XkoQ-j5yRy9n3z4V
[youtube] OvUTwCa6100: Downloading webpage
[youtube] OvUTwCa6100: Downloading tv client config
[youtube] OvUTwCa6100: Downloading tv player API JSON
[youtube] OvUTwCa6100: Downloading ios player API JSON
[youtube] OvUTwCa6100: Downloading m3u8 information
[info] OvUTwCa6100: Downloading subtitles: id
[info] OvUTwCa6100: Downloading 1 format(s): 136+251
[info] Writing video subtitles to: Majelis RGA | LIVE PENGAJIAN MALAM JUM'AT  17 JULI 2025 [OvUTwCa6100].id.vtt
[download] Destination: Majelis RGA | LIVE PENGAJIAN MALAM JUM'AT  17 JULI 2025 [OvUTwCa6100].id.vtt
[download] 100% of  501.79KiB in 00:00:02 at 168.78KiB/s

$ ls *.vtt
'Majelis RGA | LIVE PENGAJIAN MALAM JUM'\''AT  17 JULI 2025 [OvUTwCa6100].id.vtt'

$ mv *.vtt ceramah.vtt

$ grep -o -i "wali" *.vtt | wc -l
280

$ grep -Eo '[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}' ceramah.vtt | tail -n 1
01:49:51.719

didapat hasilnya: 
Kata "wali" disebut sebanyak 280 kali pada ceramah dengan durasi 1 jam 49 menit.

*Data*
Jumlah kata "wali" = 280 kali
Durasi ceramah = 1 jam 49 menit = (1×60 + 49) = 109 menit

→ Rata-rata 2–3 kali kata "wali" disebut setiap menit.
→ Dalam setiap 10 menit, kata "wali" disebut sekitar 26 kali.


*Interpretasi*
Kata "wali" cukup dominan pada ceramah ini, disebut hampir 3 kali setiap menit.
Jika konteks ceramah adalah tentang wali (misalnya Wali Allah, Wali Songo, atau Wali kelas), maka porsi bahasannya memang fokus ke topik tersebut.
Secara umum, dalam ceramah 1–2 jam, menyebut satu kata 280 kali menunjukkan kata itu menjadi inti tema.

Friday, 22 May 2026

Ekspor dan Import User Murid dari SiM ke CBT moodle

 

1. Ambil data kelas XI

SELECT 
    u.username,
    u.firstname,
    u.lastname,
    u.email,
    nis.data AS profile_field_nis,
    pw.data AS profile_field_pwexam,
    c.name AS cohort1

INTO OUTFILE '/tmp/user_xi.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'

FROM mdl_user u
JOIN mdl_cohort_members cm ON cm.userid = u.id
JOIN mdl_cohort c ON c.id = cm.cohortid

LEFT JOIN mdl_user_info_field fnis 
    ON fnis.shortname = 'nis'

LEFT JOIN mdl_user_info_data nis 
    ON nis.fieldid = fnis.id 
    AND nis.userid = u.id

LEFT JOIN mdl_user_info_field fpw 
    ON fpw.shortname = 'pwexam'

LEFT JOIN mdl_user_info_data pw 
    ON pw.fieldid = fpw.id 
    AND pw.userid = u.id

WHERE c.name IN (
    'XI-A','XI-B','XI-C',
    'XI-D','XI-E','XI-F','XI-G'
)

AND u.deleted = 0
AND u.suspended = 0

ORDER BY c.name, u.lastname;

2. Ambil data kelas X

SELECT 
    u.username,
    u.firstname,
    u.lastname,
    u.email,
    nis.data AS profile_field_nis,
    pw.data AS profile_field_pwexam,
    c.name AS cohort1

INTO OUTFILE '/tmp/user_x.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'

FROM mdl_user u
JOIN mdl_cohort_members cm ON cm.userid = u.id
JOIN mdl_cohort c ON c.id = cm.cohortid

LEFT JOIN mdl_user_info_field fnis 
    ON fnis.shortname = 'nis'

LEFT JOIN mdl_user_info_data nis 
    ON nis.fieldid = fnis.id 
    AND nis.userid = u.id

LEFT JOIN mdl_user_info_field fpw 
    ON fpw.shortname = 'pwexam'

LEFT JOIN mdl_user_info_data pw 
    ON pw.fieldid = fpw.id 
    AND pw.userid = u.id

WHERE c.name IN (
    'X-A','X-B','X-C',
    'X-D','X-E','X-F','X-G'
)

AND u.deleted = 0
AND u.suspended = 0

ORDER BY c.name, u.lastname;

3. Ambil file dari laptop dengan SCP

Kelas XI:

scp dhwan@192.168.200.20:/tmp/user_xi.csv ~/Downloads/

Kelas X:

scp dhwan@192.168.200.20:/tmp/user_x.csv ~/Downloads/

Kalau gagal karena secure_file_priv, cek:

SHOW VARIABLES LIKE 'secure_file_priv';

Lalu ganti path /tmp/ sesuai folder yang ditampilkan MariaDB, misalnya:

/var/lib/mysql-files/