Initial commit - CMS Gov Bapenda Garut dengan EditorJS
This commit is contained in:
0
app/Models/.gitkeep
Normal file
0
app/Models/.gitkeep
Normal file
45
app/Models/AuditLogModel.php
Normal file
45
app/Models/AuditLogModel.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class AuditLogModel extends Model
|
||||
{
|
||||
protected $table = 'audit_logs';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = ['user_id', 'action', 'ip_address', 'user_agent', 'created_at'];
|
||||
|
||||
protected bool $allowEmptyInserts = false;
|
||||
|
||||
protected $useTimestamps = false;
|
||||
protected $dateFormat = 'datetime';
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = null;
|
||||
protected $deletedField = null;
|
||||
|
||||
public function logAction(string $action, ?int $userId = null): bool
|
||||
{
|
||||
$request = service('request');
|
||||
|
||||
$data = [
|
||||
'user_id' => $userId,
|
||||
'action' => $action,
|
||||
'ip_address' => $request->getIPAddress(),
|
||||
'user_agent' => $request->getUserAgent()->getAgentString(),
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
try {
|
||||
return $this->insert($data);
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Audit log insert failed: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
151
app/Models/LoginAttemptModel.php
Normal file
151
app/Models/LoginAttemptModel.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* Model untuk Login Attempts
|
||||
*
|
||||
* Digunakan untuk:
|
||||
* - Mencatat semua percobaan login
|
||||
* - Menghitung jumlah percobaan gagal
|
||||
* - Implementasi account lockout
|
||||
* - Monitoring dan audit trail
|
||||
*/
|
||||
class LoginAttemptModel extends Model
|
||||
{
|
||||
protected $table = 'login_attempts';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = ['ip_address', 'username', 'user_id', 'success', 'user_agent', 'created_at'];
|
||||
|
||||
protected bool $allowEmptyInserts = false;
|
||||
|
||||
protected $useTimestamps = true;
|
||||
protected $dateFormat = 'datetime';
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = null;
|
||||
protected $deletedField = null;
|
||||
|
||||
protected $validationRules = [];
|
||||
protected $validationMessages = [];
|
||||
protected $skipValidation = true; // Skip validation karena data dari sistem
|
||||
|
||||
protected $beforeInsert = [];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = [];
|
||||
protected $afterUpdate = [];
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
|
||||
/**
|
||||
* Mencatat percobaan login
|
||||
*
|
||||
* @param string $ipAddress IP address dari request
|
||||
* @param string|null $username Username yang dicoba
|
||||
* @param int|null $userId ID user jika login berhasil
|
||||
* @param bool $success Status login (true = berhasil, false = gagal)
|
||||
* @return bool True jika berhasil disimpan
|
||||
*/
|
||||
public function recordAttempt(string $ipAddress, ?string $username = null, ?int $userId = null, bool $success = false): bool
|
||||
{
|
||||
$request = service('request');
|
||||
|
||||
$data = [
|
||||
'ip_address' => $ipAddress,
|
||||
'username' => $username,
|
||||
'user_id' => $userId,
|
||||
'success' => $success ? 1 : 0,
|
||||
'user_agent' => $request->getUserAgent()->getAgentString(),
|
||||
];
|
||||
|
||||
try {
|
||||
return $this->insert($data) !== false;
|
||||
} catch (\Exception $e) {
|
||||
log_message('error', 'Failed to record login attempt: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menghitung jumlah percobaan gagal dalam periode tertentu
|
||||
*
|
||||
* @param string $ipAddress IP address
|
||||
* @param int $minutes Periode waktu dalam menit (default: 15)
|
||||
* @return int Jumlah percobaan gagal
|
||||
*/
|
||||
public function countFailedAttempts(string $ipAddress, int $minutes = 15): int
|
||||
{
|
||||
$timeLimit = date('Y-m-d H:i:s', strtotime("-{$minutes} minutes"));
|
||||
|
||||
return $this->where('ip_address', $ipAddress)
|
||||
->where('success', 0)
|
||||
->where('created_at >=', $timeLimit)
|
||||
->countAllResults(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menghitung jumlah percobaan gagal untuk username tertentu
|
||||
*
|
||||
* @param string $username Username
|
||||
* @param int $minutes Periode waktu dalam menit (default: 15)
|
||||
* @return int Jumlah percobaan gagal
|
||||
*/
|
||||
public function countFailedAttemptsByUsername(string $username, int $minutes = 15): int
|
||||
{
|
||||
$timeLimit = date('Y-m-d H:i:s', strtotime("-{$minutes} minutes"));
|
||||
|
||||
return $this->where('username', $username)
|
||||
->where('success', 0)
|
||||
->where('created_at >=', $timeLimit)
|
||||
->countAllResults(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menghapus record percobaan lama (cleanup)
|
||||
*
|
||||
* @param int $days Jumlah hari untuk menyimpan data (default: 30)
|
||||
* @return int Jumlah record yang dihapus
|
||||
*/
|
||||
public function cleanupOldAttempts(int $days = 30): int
|
||||
{
|
||||
$timeLimit = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
||||
|
||||
return $this->where('created_at <', $timeLimit)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mendapatkan semua percobaan login untuk IP tertentu
|
||||
*
|
||||
* @param string $ipAddress IP address
|
||||
* @param int $limit Jumlah record yang diambil
|
||||
* @return array Array of login attempts
|
||||
*/
|
||||
public function getAttemptsByIp(string $ipAddress, int $limit = 50): array
|
||||
{
|
||||
return $this->where('ip_address', $ipAddress)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->findAll($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mendapatkan semua percobaan login untuk username tertentu
|
||||
*
|
||||
* @param string $username Username
|
||||
* @param int $limit Jumlah record yang diambil
|
||||
* @return array Array of login attempts
|
||||
*/
|
||||
public function getAttemptsByUsername(string $username, int $limit = 50): array
|
||||
{
|
||||
return $this->where('username', $username)
|
||||
->orderBy('created_at', 'DESC')
|
||||
->findAll($limit);
|
||||
}
|
||||
}
|
||||
|
||||
125
app/Models/NewsModel.php
Normal file
125
app/Models/NewsModel.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class NewsModel extends Model
|
||||
{
|
||||
protected $table = 'news';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = ['title', 'slug', 'content', 'content_json', 'content_html', 'excerpt', 'status', 'published_at', 'created_by'];
|
||||
|
||||
protected bool $allowEmptyInserts = false;
|
||||
|
||||
protected $useTimestamps = true;
|
||||
protected $dateFormat = 'datetime';
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
protected $deletedField = null;
|
||||
|
||||
// Validation
|
||||
protected $validationRules = [
|
||||
'title' => 'required|min_length[3]|max_length[255]',
|
||||
'slug' => 'required|max_length[255]|is_unique[news.slug,id,{id}]',
|
||||
'content' => 'required',
|
||||
'status' => 'required|in_list[draft,published]',
|
||||
];
|
||||
|
||||
protected $validationMessages = [
|
||||
'title' => [
|
||||
'required' => 'Judul berita harus diisi.',
|
||||
'min_length' => 'Judul berita minimal 3 karakter.',
|
||||
'max_length' => 'Judul berita maksimal 255 karakter.',
|
||||
],
|
||||
'slug' => [
|
||||
'required' => 'Slug harus diisi.',
|
||||
'is_unique' => 'Slug sudah digunakan, silakan gunakan judul yang berbeda.',
|
||||
],
|
||||
'content' => [
|
||||
'required' => 'Konten berita harus diisi.',
|
||||
],
|
||||
'status' => [
|
||||
'required' => 'Status harus dipilih.',
|
||||
'in_list' => 'Status harus draft atau published.',
|
||||
],
|
||||
];
|
||||
|
||||
protected $skipValidation = false;
|
||||
protected $cleanValidationRules = true;
|
||||
|
||||
/**
|
||||
* Generate slug from title
|
||||
*/
|
||||
public function generateSlug(string $title, ?int $excludeId = null): string
|
||||
{
|
||||
// Convert to lowercase and replace spaces with hyphens
|
||||
$slug = strtolower(trim($title));
|
||||
$slug = preg_replace('/[^a-z0-9-]/', '-', $slug);
|
||||
$slug = preg_replace('/-+/', '-', $slug);
|
||||
$slug = trim($slug, '-');
|
||||
|
||||
// If slug is empty, use timestamp
|
||||
if (empty($slug)) {
|
||||
$slug = 'news-' . time();
|
||||
}
|
||||
|
||||
// Check if slug exists
|
||||
$baseSlug = $slug;
|
||||
$counter = 1;
|
||||
while ($this->slugExists($slug, $excludeId)) {
|
||||
$slug = $baseSlug . '-' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if slug exists
|
||||
*/
|
||||
protected function slugExists(string $slug, ?int $excludeId = null): bool
|
||||
{
|
||||
$builder = $this->where('slug', $slug);
|
||||
|
||||
if ($excludeId !== null) {
|
||||
$builder->where('id !=', $excludeId);
|
||||
}
|
||||
|
||||
return $builder->countAllResults() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get news with creator information
|
||||
*/
|
||||
public function getNewsWithCreator(int $limit = 10, int $offset = 0, ?string $status = null)
|
||||
{
|
||||
$builder = $this->select('news.*, users.username as creator_name')
|
||||
->join('users', 'users.id = news.created_by', 'left');
|
||||
|
||||
if ($status !== null) {
|
||||
$builder->where('news.status', $status);
|
||||
}
|
||||
|
||||
return $builder->orderBy('news.created_at', 'DESC')
|
||||
->limit($limit, $offset)
|
||||
->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count news by status
|
||||
*/
|
||||
public function countByStatus(?string $status = null): int
|
||||
{
|
||||
if ($status !== null) {
|
||||
return $this->where('status', $status)->countAllResults();
|
||||
}
|
||||
|
||||
return $this->countAllResults();
|
||||
}
|
||||
}
|
||||
|
||||
109
app/Models/PageModel.php
Normal file
109
app/Models/PageModel.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class PageModel extends Model
|
||||
{
|
||||
protected $table = 'pages';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = ['title', 'slug', 'content', 'content_json', 'content_html', 'excerpt', 'featured_image', 'status'];
|
||||
|
||||
protected bool $allowEmptyInserts = false;
|
||||
|
||||
protected $useTimestamps = true;
|
||||
protected $dateFormat = 'datetime';
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
protected $deletedField = null;
|
||||
|
||||
// Validation
|
||||
protected $validationRules = [
|
||||
'title' => 'required|min_length[3]|max_length[255]',
|
||||
'slug' => 'permit_empty|max_length[255]|is_unique[pages.slug,id,{id}]',
|
||||
'content_json' => 'permit_empty',
|
||||
'content_html' => 'permit_empty',
|
||||
'status' => 'required|in_list[draft,published]',
|
||||
];
|
||||
|
||||
protected $validationMessages = [
|
||||
'title' => [
|
||||
'required' => 'Judul halaman harus diisi.',
|
||||
'min_length' => 'Judul halaman minimal 3 karakter.',
|
||||
'max_length' => 'Judul halaman maksimal 255 karakter.',
|
||||
],
|
||||
'slug' => [
|
||||
'required' => 'Slug harus diisi.',
|
||||
'is_unique' => 'Slug sudah digunakan, silakan gunakan judul yang berbeda.',
|
||||
],
|
||||
'content' => [
|
||||
'required' => 'Konten halaman harus diisi.',
|
||||
],
|
||||
'status' => [
|
||||
'required' => 'Status harus dipilih.',
|
||||
'in_list' => 'Status harus draft atau published.',
|
||||
],
|
||||
];
|
||||
|
||||
protected $skipValidation = false;
|
||||
protected $cleanValidationRules = true;
|
||||
|
||||
/**
|
||||
* Generate slug from title
|
||||
*/
|
||||
public function generateSlug(string $title, ?int $excludeId = null): string
|
||||
{
|
||||
// Convert to lowercase and replace spaces with hyphens
|
||||
$slug = strtolower(trim($title));
|
||||
$slug = preg_replace('/[^a-z0-9-]/', '-', $slug);
|
||||
$slug = preg_replace('/-+/', '-', $slug);
|
||||
$slug = trim($slug, '-');
|
||||
|
||||
// If slug is empty, use timestamp
|
||||
if (empty($slug)) {
|
||||
$slug = 'page-' . time();
|
||||
}
|
||||
|
||||
// Check if slug exists
|
||||
$baseSlug = $slug;
|
||||
$counter = 1;
|
||||
while ($this->slugExists($slug, $excludeId)) {
|
||||
$slug = $baseSlug . '-' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if slug exists
|
||||
*/
|
||||
protected function slugExists(string $slug, ?int $excludeId = null): bool
|
||||
{
|
||||
$builder = $this->where('slug', $slug);
|
||||
|
||||
if ($excludeId !== null) {
|
||||
$builder->where('id !=', $excludeId);
|
||||
}
|
||||
|
||||
return $builder->countAllResults() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count pages by status
|
||||
*/
|
||||
public function countByStatus(?string $status = null): int
|
||||
{
|
||||
if ($status !== null) {
|
||||
return $this->where('status', $status)->countAllResults();
|
||||
}
|
||||
|
||||
return $this->countAllResults();
|
||||
}
|
||||
}
|
||||
|
||||
29
app/Models/RoleModel.php
Normal file
29
app/Models/RoleModel.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class RoleModel extends Model
|
||||
{
|
||||
protected $table = 'roles';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = ['name'];
|
||||
|
||||
protected bool $allowEmptyInserts = false;
|
||||
|
||||
protected $useTimestamps = false;
|
||||
|
||||
protected $validationRules = [
|
||||
'name' => 'required|max_length[50]|is_unique[roles.name,id,{id}]',
|
||||
];
|
||||
|
||||
protected $validationMessages = [];
|
||||
protected $skipValidation = false;
|
||||
protected $cleanValidationRules = true;
|
||||
}
|
||||
|
||||
68
app/Models/SettingsModel.php
Normal file
68
app/Models/SettingsModel.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class SettingsModel extends Model
|
||||
{
|
||||
protected $table = 'settings';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = ['key', 'value', 'description'];
|
||||
|
||||
protected bool $allowEmptyInserts = false;
|
||||
|
||||
protected $useTimestamps = true;
|
||||
protected $dateFormat = 'datetime';
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
protected $deletedField = null;
|
||||
|
||||
/**
|
||||
* Get setting value by key
|
||||
*/
|
||||
public function getSetting(string $key, ?string $default = null): ?string
|
||||
{
|
||||
$setting = $this->where('key', $key)->first();
|
||||
return $setting ? $setting['value'] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set setting value by key
|
||||
*/
|
||||
public function setSetting(string $key, ?string $value, ?string $description = null): bool
|
||||
{
|
||||
$setting = $this->where('key', $key)->first();
|
||||
|
||||
if ($setting) {
|
||||
return $this->update($setting['id'], [
|
||||
'value' => $value,
|
||||
'description' => $description ?? $setting['description'],
|
||||
]);
|
||||
} else {
|
||||
return $this->insert([
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
'description' => $description,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all settings as key-value array
|
||||
*/
|
||||
public function getAllSettings(): array
|
||||
{
|
||||
$settings = $this->findAll();
|
||||
$result = [];
|
||||
foreach ($settings as $setting) {
|
||||
$result[$setting['key']] = $setting['value'];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
66
app/Models/UserModel.php
Normal file
66
app/Models/UserModel.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class UserModel extends Model
|
||||
{
|
||||
protected $table = 'users';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = ['role_id', 'username', 'email', 'phone_number', 'password_hash', 'telegram_id', 'is_active', 'last_login_at'];
|
||||
|
||||
protected bool $allowEmptyInserts = false;
|
||||
|
||||
protected $useTimestamps = true;
|
||||
protected $dateFormat = 'datetime';
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
protected $deletedField = null;
|
||||
|
||||
protected $validationRules = [
|
||||
'username' => 'required|max_length[100]|is_unique[users.username,id,{id}]',
|
||||
'email' => 'required|valid_email|max_length[255]|is_unique[users.email,id,{id}]',
|
||||
'phone_number' => 'permit_empty|max_length[20]|is_unique[users.phone_number,id,{id}]',
|
||||
'telegram_id' => 'permit_empty|integer|is_unique[users.telegram_id,id,{id}]',
|
||||
'role_id' => 'required|integer',
|
||||
];
|
||||
|
||||
protected $validationMessages = [];
|
||||
protected $skipValidation = false;
|
||||
protected $cleanValidationRules = true;
|
||||
|
||||
protected $beforeInsert = ['hashPassword'];
|
||||
protected $beforeUpdate = ['hashPassword'];
|
||||
|
||||
protected function hashPassword(array $data)
|
||||
{
|
||||
if (isset($data['data']['password_hash']) && !empty($data['data']['password_hash'])) {
|
||||
// Only hash if it's not already hashed (check if it starts with $2y$ which is bcrypt)
|
||||
if (!preg_match('/^\$2[ayb]\$.{56}$/', $data['data']['password_hash'])) {
|
||||
$data['data']['password_hash'] = password_hash($data['data']['password_hash'], PASSWORD_DEFAULT);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getUserByUsername(string $username)
|
||||
{
|
||||
return $this->where('username', $username)->first();
|
||||
}
|
||||
|
||||
public function getUserByEmail(string $email)
|
||||
{
|
||||
return $this->where('email', $email)->first();
|
||||
}
|
||||
|
||||
public function verifyPassword(string $password, string $hash): bool
|
||||
{
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user