Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58445b2a48 | |||
| 0f8a1fa0b1 | |||
| fee07bcceb | |||
| 70a6e2c104 | |||
| 11c93d3e82 | |||
| 7c5028a76d | |||
| aa1260afd6 | |||
| 35b9cba882 |
@@ -63,7 +63,7 @@ session.expiration = 43200
|
||||
database.default.hostname = 127.0.0.1
|
||||
database.default.database = school
|
||||
database.default.username = root
|
||||
database.default.password =
|
||||
database.default.password = rootpassword
|
||||
database.default.DBDriver = MySQLi
|
||||
database.default.DBPrefix =
|
||||
database.default.port = 3306
|
||||
|
||||
+46
-38
@@ -9,43 +9,51 @@ class Database extends Config
|
||||
public string $filesPath = APPPATH . 'Database' . DIRECTORY_SEPARATOR;
|
||||
public string $defaultGroup = 'default';
|
||||
|
||||
public array $default = [
|
||||
'DSN' => '',
|
||||
'hostname' => 'localhost',
|
||||
'username' => 'u280815660_melabidi',
|
||||
'password' => '>tNxlRzP/W8',
|
||||
'database' => 'u280815660_school',
|
||||
'DBDriver' => 'MySQLi',
|
||||
'DBPrefix' => '',
|
||||
'pConnect' => false,
|
||||
'DBDebug' => (ENVIRONMENT !== 'development'),
|
||||
'charset' => 'utf8',
|
||||
'DBCollat' => 'utf8_general_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
];
|
||||
public array $default = [];
|
||||
public array $tests = [];
|
||||
|
||||
public array $tests = [
|
||||
'DSN' => '',
|
||||
'hostname' => 'localhost',
|
||||
'username' => 'u280815660_melabidi',
|
||||
'password' => '>tNxlRzP/W8',
|
||||
'database' => 'u280815660_school',
|
||||
'DBDriver' => 'MySQLi',
|
||||
'DBPrefix' => 'db_',
|
||||
'pConnect' => false,
|
||||
'DBDebug' => true,
|
||||
'charset' => 'utf8',
|
||||
'DBCollat' => 'utf8_general_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => 3306,
|
||||
];
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->default = [
|
||||
'DSN' => '',
|
||||
'hostname' => env('database.default.hostname'),
|
||||
'username' => env('database.default.username'),
|
||||
'password' => env('database.default.password'),
|
||||
'database' => env('database.default.database'),
|
||||
'DBDriver' => env('database.default.DBDriver', 'MySQLi'),
|
||||
'DBPrefix' => '',
|
||||
'pConnect' => false,
|
||||
'DBDebug' => (ENVIRONMENT !== 'development'),
|
||||
'charset' => 'utf8',
|
||||
'DBCollat' => 'utf8_general_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => (int) env('database.default.port', 3306),
|
||||
];
|
||||
|
||||
$this->tests = [
|
||||
'DSN' => '',
|
||||
'hostname' => env('database.tests.hostname', env('database.default.hostname')),
|
||||
'username' => env('database.tests.username', env('database.default.username')),
|
||||
'password' => env('database.tests.password', env('database.default.password')),
|
||||
'database' => env('database.tests.database', env('database.default.database')),
|
||||
'DBDriver' => env('database.tests.DBDriver', env('database.default.DBDriver', 'MySQLi')),
|
||||
'DBPrefix' => env('database.tests.DBPrefix', 'db_'),
|
||||
'pConnect' => false,
|
||||
'DBDebug' => true,
|
||||
'charset' => 'utf8',
|
||||
'DBCollat' => 'utf8_general_ci',
|
||||
'swapPre' => '',
|
||||
'encrypt' => false,
|
||||
'compress' => false,
|
||||
'strictOn' => false,
|
||||
'failover' => [],
|
||||
'port' => (int) env('database.tests.port', env('database.default.port', 3306)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,10 @@ $routes->get('reset_password', 'View\UserController::resetPassword');
|
||||
|
||||
//$routes->get('/blocked', 'View\UserController::blocked');
|
||||
|
||||
$routes->get('confirm_authorized_user', 'View\AuthorizedUsersController::confirm');
|
||||
$routes->get('set_authorized_user_password/(:num)', 'View\AuthorizedUsersController::setPassword/$1');
|
||||
$routes->post('set_authorized_user_password/(:num)', 'View\AuthorizedUsersController::savePassword/$1');
|
||||
|
||||
$routes->post('assign_class_student', 'View\StudentController::assignClassStudent');
|
||||
$routes->post('remove_class_student', 'View\StudentController::removeClassStudent');
|
||||
$routes->post('administrator/remove_class_student', 'View\StudentController::removeClassStudent'); // alias to avoid 404s
|
||||
@@ -1003,6 +1007,8 @@ $routes->group('family', static function ($routes) {
|
||||
$routes->get('index', 'View\FamilyAdminController::index');
|
||||
$routes->get('search', 'View\FamilyAdminController::search');
|
||||
$routes->get('card', 'View\FamilyAdminController::card');
|
||||
$routes->get('compose-email', 'View\FamilyAdminController::composeEmail');
|
||||
$routes->post('compose-email/send', 'View\FamilyAdminController::sendComposeEmail');
|
||||
});
|
||||
// Convenience alias
|
||||
$routes->get('family', 'View\FamilyAdminController::index');
|
||||
|
||||
@@ -469,6 +469,7 @@ class AuthController extends Controller
|
||||
// Generate a secure token for the password reset
|
||||
helper('text');
|
||||
$token = bin2hex(random_bytes(48));
|
||||
$tokenHash = hash('sha256', $token);
|
||||
|
||||
// Calculate the expiration time for the token (1 hour from now)
|
||||
$expires_at = Time::now()->addHours(1);
|
||||
@@ -477,7 +478,7 @@ class AuthController extends Controller
|
||||
$passwordResetModel = new PasswordResetModel();
|
||||
$passwordResetModel->insert([
|
||||
'email' => $email,
|
||||
'token' => $token,
|
||||
'token' => $tokenHash,
|
||||
'created_at' => Time::now(),
|
||||
'expires_at' => $expires_at,
|
||||
]);
|
||||
|
||||
@@ -416,8 +416,10 @@ class AdministratorController extends BaseController
|
||||
$totalStudents = (int) (
|
||||
$this->db->table('student_class')
|
||||
->select('COUNT(DISTINCT student_class.student_id) AS cnt')
|
||||
->join('students', 'students.id = student_class.student_id', 'inner')
|
||||
->where('student_class.school_year', $this->schoolYear)
|
||||
->where('student_class.class_section_id IS NOT NULL', null, false)
|
||||
->where('students.is_active', 1)
|
||||
->get()
|
||||
->getRow('cnt')
|
||||
?? 0
|
||||
|
||||
@@ -10,6 +10,8 @@ use CodeIgniter\I18n\Time;
|
||||
|
||||
class AuthorizedUsersController extends ResourceController
|
||||
{
|
||||
private const TOKEN_TTL_HOURS = 24;
|
||||
|
||||
protected $userModel;
|
||||
protected $authorizedUserModel;
|
||||
|
||||
@@ -18,6 +20,30 @@ class AuthorizedUsersController extends ResourceController
|
||||
$this->userModel = new UserModel();
|
||||
$this->authorizedUserModel = new AuthorizedUserModel();
|
||||
}
|
||||
|
||||
private function requireLogin()
|
||||
{
|
||||
if (!session()->get('is_logged_in')) {
|
||||
return $this->failUnauthorized('Authentication required.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function requireOwnership(array $authorizedUser)
|
||||
{
|
||||
$userId = (int) session()->get('user_id');
|
||||
if ($userId <= 0 || (int) ($authorizedUser['user_id'] ?? 0) !== $userId) {
|
||||
return $this->failForbidden('You do not have access to this resource.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function hashToken(string $token): string
|
||||
{
|
||||
return hash('sha256', $token);
|
||||
}
|
||||
/**
|
||||
* Return a list of authorized users for the logged-in main user.
|
||||
*
|
||||
@@ -25,7 +51,10 @@ class AuthorizedUsersController extends ResourceController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
||||
if ($resp = $this->requireLogin()) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$userId = session()->get('user_id');
|
||||
$authorizedUsers = $this->authorizedUserModel->where('user_id', $userId)->findAll();
|
||||
|
||||
@@ -40,12 +69,20 @@ class AuthorizedUsersController extends ResourceController
|
||||
*/
|
||||
public function show($id = null)
|
||||
{
|
||||
if ($resp = $this->requireLogin()) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$authorizedUser = $this->authorizedUserModel->find($id);
|
||||
|
||||
if (!$authorizedUser) {
|
||||
return $this->failNotFound('Authorized user not found.');
|
||||
}
|
||||
|
||||
if ($resp = $this->requireOwnership($authorizedUser)) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
return $this->respond($authorizedUser);
|
||||
}
|
||||
|
||||
@@ -56,6 +93,10 @@ class AuthorizedUsersController extends ResourceController
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
if ($resp = $this->requireLogin()) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$email = strtolower($this->request->getPost('email'));
|
||||
|
||||
// Validate email
|
||||
@@ -66,19 +107,20 @@ class AuthorizedUsersController extends ResourceController
|
||||
$user = $this->userModel->where('email', $email)->first();
|
||||
|
||||
if (!$user) {
|
||||
return $this->failNotFound('No user found with this email.');
|
||||
return $this->respondCreated(['message' => 'Authorized user added. A confirmation email has been sent.']);
|
||||
}
|
||||
|
||||
// Generate a token for confirmation
|
||||
helper('text');
|
||||
$token = bin2hex(random_bytes(48));
|
||||
$tokenHash = $this->hashToken($token);
|
||||
|
||||
// Add entry to the authorized_users table
|
||||
$this->authorizedUserModel->insert([
|
||||
'user_id' => session()->get('user_id'), // Main user ID
|
||||
'authorized_user_id' => $user['id'],
|
||||
'email' => $email,
|
||||
'token' => $token,
|
||||
'token' => $tokenHash,
|
||||
'status' => 'Pending'
|
||||
]);
|
||||
|
||||
@@ -96,6 +138,10 @@ class AuthorizedUsersController extends ResourceController
|
||||
*/
|
||||
public function update($id = null)
|
||||
{
|
||||
if ($resp = $this->requireLogin()) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
// Fetch the authorized user
|
||||
$authorizedUser = $this->authorizedUserModel->find($id);
|
||||
|
||||
@@ -103,6 +149,10 @@ class AuthorizedUsersController extends ResourceController
|
||||
return $this->failNotFound('Authorized user not found.');
|
||||
}
|
||||
|
||||
if ($resp = $this->requireOwnership($authorizedUser)) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
// Update the authorized user’s information (e.g., email)
|
||||
$email = strtolower($this->request->getPost('email'));
|
||||
if ($email && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
@@ -122,12 +172,20 @@ class AuthorizedUsersController extends ResourceController
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
if ($resp = $this->requireLogin()) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$authorizedUser = $this->authorizedUserModel->find($id);
|
||||
|
||||
if (!$authorizedUser) {
|
||||
return $this->failNotFound('Authorized user not found.');
|
||||
}
|
||||
|
||||
if ($resp = $this->requireOwnership($authorizedUser)) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
// Delete the authorized user record
|
||||
$this->authorizedUserModel->delete($id);
|
||||
|
||||
@@ -147,16 +205,28 @@ class AuthorizedUsersController extends ResourceController
|
||||
return $this->fail('Invalid confirmation link.');
|
||||
}
|
||||
|
||||
$authorizedUser = $this->authorizedUserModel->where('token', $token)->first();
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$authorizedUser = $this->authorizedUserModel
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->where('created_at >=', Time::now()->subHours(self::TOKEN_TTL_HOURS)->toDateTimeString())
|
||||
->first();
|
||||
|
||||
if (!$authorizedUser) {
|
||||
return $this->fail('Invalid or expired confirmation link.');
|
||||
}
|
||||
|
||||
// Mark the authorized user as active
|
||||
$this->authorizedUserModel->update($authorizedUser['id'], ['status' => 'Active', 'token' => null]);
|
||||
// Mark the authorized user as active and rotate token for password setup
|
||||
$nextToken = bin2hex(random_bytes(48));
|
||||
$nextTokenHash = $this->hashToken($nextToken);
|
||||
$this->authorizedUserModel->update($authorizedUser['id'], [
|
||||
'status' => 'Active',
|
||||
'token' => $nextTokenHash,
|
||||
]);
|
||||
|
||||
return redirect()->to('/set_authorized_user_password/' . $authorizedUser['authorized_user_id']);
|
||||
return redirect()->to('/set_authorized_user_password/' . $authorizedUser['authorized_user_id'] . '?token=' . $nextToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,13 +237,36 @@ class AuthorizedUsersController extends ResourceController
|
||||
*/
|
||||
public function setPassword($authorizedUserId)
|
||||
{
|
||||
$token = (string) $this->request->getGet('token');
|
||||
if ($token === '') {
|
||||
return $this->fail('Invalid confirmation link.');
|
||||
}
|
||||
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$authorizedUser = $this->authorizedUserModel
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->where('authorized_user_id', $authorizedUserId)
|
||||
->where('status', 'Active')
|
||||
->where('updated_at >=', Time::now()->subHours(self::TOKEN_TTL_HOURS)->toDateTimeString())
|
||||
->first();
|
||||
|
||||
if (!$authorizedUser) {
|
||||
return $this->fail('Invalid or expired confirmation link.');
|
||||
}
|
||||
|
||||
$user = $this->userModel->find($authorizedUserId);
|
||||
|
||||
if (!$user) {
|
||||
return $this->failNotFound('User not found.');
|
||||
}
|
||||
|
||||
return view('user/set_authorized_user_password', ['userId' => $authorizedUserId]);
|
||||
return view('user/set_authorized_user_password', [
|
||||
'userId' => $authorizedUserId,
|
||||
'token' => $token,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,38 +274,59 @@ class AuthorizedUsersController extends ResourceController
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
/*
|
||||
public function savePassword()
|
||||
public function savePassword($authorizedUserId = null)
|
||||
{
|
||||
// Validate the request
|
||||
$validation = \Config\Services::validation();
|
||||
$validation->setRules([
|
||||
'password' => 'required|min_length[6]',
|
||||
'password' => [
|
||||
'label' => 'Password',
|
||||
'rules' => 'required|min_length[8]|regex_match[/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@\\-=\\+*#$%&!?])[A-Za-z\\d@\\-=\\+*#$%&!?]{8,}$/]',
|
||||
],
|
||||
'password_confirm' => 'required|matches[password]',
|
||||
'user_id' => 'required|integer'
|
||||
'user_id' => 'required|integer',
|
||||
'token' => 'required',
|
||||
]);
|
||||
|
||||
if (!$this->validate($validation->getRules())) {
|
||||
return $this->failValidationErrors($validation->getErrors());
|
||||
}
|
||||
|
||||
// Get the validated input
|
||||
$userId = $this->request->getPost('user_id');
|
||||
$password = $this->request->getPost('password');
|
||||
$userId = (int) $this->request->getPost('user_id');
|
||||
$token = (string) $this->request->getPost('token');
|
||||
$authorizedUserId = $authorizedUserId !== null ? (int) $authorizedUserId : $userId;
|
||||
|
||||
$model = new UserModel();
|
||||
$user = $model->find($userId);
|
||||
if ($userId <= 0 || $authorizedUserId <= 0 || $userId !== $authorizedUserId) {
|
||||
return $this->fail('Invalid request.');
|
||||
}
|
||||
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$authorizedUser = $this->authorizedUserModel
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->where('authorized_user_id', $authorizedUserId)
|
||||
->where('status', 'Active')
|
||||
->where('updated_at >=', Time::now()->subHours(self::TOKEN_TTL_HOURS)->toDateTimeString())
|
||||
->first();
|
||||
|
||||
if (!$authorizedUser) {
|
||||
return $this->fail('Invalid or expired confirmation link.');
|
||||
}
|
||||
|
||||
$user = $this->userModel->find($authorizedUserId);
|
||||
if (!$user) {
|
||||
return $this->failNotFound('User not found.');
|
||||
}
|
||||
|
||||
// Save the password
|
||||
$model->update($userId, ['password' => password_hash($password, PASSWORD_DEFAULT)]);
|
||||
$password = (string) $this->request->getPost('password');
|
||||
$hashedPassword = pbkdf2_hash($password);
|
||||
|
||||
$this->userModel->update($authorizedUserId, ['password' => $hashedPassword]);
|
||||
$this->authorizedUserModel->update($authorizedUser['id'], ['token' => null]);
|
||||
|
||||
return $this->respond(['message' => 'Password has been successfully set.']);
|
||||
}
|
||||
*/
|
||||
/**
|
||||
* Sends a confirmation email to the authorized user.
|
||||
*
|
||||
@@ -242,4 +356,4 @@ class AuthorizedUsersController extends ResourceController
|
||||
log_message('error', 'Failed to send authorized user confirmation email to ' . $email);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,4 +406,82 @@ class FamilyAdminController extends BaseController
|
||||
|
||||
return service('response')->setBody(view('family/card', ['f' => $family]));
|
||||
}
|
||||
|
||||
public function composeEmail()
|
||||
{
|
||||
$to = trim((string)$this->request->getGet('to'));
|
||||
$name = trim((string)$this->request->getGet('name'));
|
||||
$returnUrl = trim((string)$this->request->getGet('return_url'));
|
||||
if ($returnUrl === '') {
|
||||
$returnUrl = trim((string)$this->request->getServer('HTTP_REFERER'));
|
||||
}
|
||||
if ($returnUrl === '') {
|
||||
$returnUrl = site_url('family');
|
||||
}
|
||||
|
||||
return view('family/compose_email', [
|
||||
'to' => $to,
|
||||
'name' => $name,
|
||||
'return_url' => $returnUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendComposeEmail()
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
return redirect()->to(site_url('family'));
|
||||
}
|
||||
|
||||
$to = trim((string)$this->request->getPost('to'));
|
||||
$subject = trim((string)$this->request->getPost('subject'));
|
||||
$html = (string)($this->request->getPost('html') ?? '');
|
||||
$returnUrl = trim((string)$this->request->getPost('return_url'));
|
||||
|
||||
if ($returnUrl === '' || !$this->isLocalReturnUrl($returnUrl)) {
|
||||
$returnUrl = site_url('family');
|
||||
}
|
||||
|
||||
if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
|
||||
return redirect()->back()->withInput()->with('error', 'Please enter a valid email address.');
|
||||
}
|
||||
if ($subject === '') {
|
||||
return redirect()->back()->withInput()->with('error', 'Subject is required.');
|
||||
}
|
||||
if (trim($html) === '') {
|
||||
return redirect()->back()->withInput()->with('error', 'Email body is required.');
|
||||
}
|
||||
|
||||
$wrapped = view('emails/custom_html', [
|
||||
'subject' => $subject,
|
||||
'body_html' => $html,
|
||||
]);
|
||||
|
||||
$mailer = new \App\Controllers\View\EmailController();
|
||||
$ok = $mailer->sendEmail($to, $subject, $wrapped, 'communication');
|
||||
|
||||
if ($ok) {
|
||||
return redirect()->to($returnUrl)->with('status', 'Email sent.');
|
||||
}
|
||||
|
||||
return redirect()->to($returnUrl)->with('error', 'Unable to send email.');
|
||||
}
|
||||
|
||||
private function isLocalReturnUrl(string $url): bool
|
||||
{
|
||||
$url = trim($url);
|
||||
if ($url === '') return false;
|
||||
|
||||
$parts = parse_url($url);
|
||||
if ($parts === false) return false;
|
||||
|
||||
if (!empty($parts['scheme']) || !empty($parts['host'])) {
|
||||
$base = parse_url(site_url('/'));
|
||||
if (!$base || empty($base['host'])) return false;
|
||||
$hostMatch = strcasecmp((string)($parts['host'] ?? ''), (string)$base['host']) === 0;
|
||||
return $hostMatch;
|
||||
}
|
||||
|
||||
// Relative URL (e.g., /family?x=1 or family?x=1)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1283,10 +1283,11 @@ public function financialReport()
|
||||
$parentIds = array_values(array_unique(array_map(static fn($r) => (int)($r['parent_id'] ?? 0), $rows)));
|
||||
$hasPayments = [];
|
||||
$paidTotals = [];
|
||||
$paymentCounts = [];
|
||||
if (!empty($parentIds)) {
|
||||
try {
|
||||
$pRows = $db->table('payments')
|
||||
->select('parent_id, SUM(paid_amount) AS total_paid')
|
||||
->select('parent_id, SUM(paid_amount) AS total_paid, COUNT(*) AS payment_count')
|
||||
->whereIn('parent_id', $parentIds)
|
||||
->where('school_year', $schoolYear)
|
||||
->groupBy('parent_id')
|
||||
@@ -1296,6 +1297,7 @@ public function financialReport()
|
||||
if ($pid > 0) {
|
||||
$hasPayments[$pid] = true;
|
||||
$paidTotals[$pid] = (float)($pr['total_paid'] ?? 0);
|
||||
$paymentCounts[$pid] = (int)($pr['payment_count'] ?? 0);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -1303,7 +1305,7 @@ public function financialReport()
|
||||
}
|
||||
}
|
||||
|
||||
$dataRows = array_map(function(array $r) use ($hasPayments, $paidTotals, $nextInstallmentYmd) {
|
||||
$dataRows = array_map(function(array $r) use ($hasPayments, $paidTotals, $paymentCounts, $nextInstallmentYmd) {
|
||||
$pid = (int)($r['parent_id'] ?? 0);
|
||||
$name = trim((string)($r['firstname'] ?? '') . ' ' . (string)($r['lastname'] ?? ''));
|
||||
return [
|
||||
@@ -1317,6 +1319,7 @@ public function financialReport()
|
||||
'installment_amount' => (float)($r['installment_amount'] ?? 0),
|
||||
'type' => isset($hasPayments[$pid]) ? 'installment' : 'no_payment',
|
||||
'total_paid' => isset($paidTotals[$pid]) ? (float)$paidTotals[$pid] : 0.0,
|
||||
'payment_count' => isset($paymentCounts[$pid]) ? (int)$paymentCounts[$pid] : 0,
|
||||
'has_installment'=> isset($hasPayments[$pid]) ? 1 : 0,
|
||||
'next_installment' => $nextInstallmentYmd,
|
||||
];
|
||||
|
||||
@@ -421,17 +421,45 @@ class FlagController extends Controller
|
||||
log_message('debug', 'Flag state: ' . $this->request->getPost('flag_state'));
|
||||
|
||||
$currentFlagModel = new CurrentFlagModel();
|
||||
$userId = session()->get('user_id');
|
||||
|
||||
// Get the new flag state from the form
|
||||
$newState = $this->request->getPost('flag_state');
|
||||
$stateDescription = (string) ($this->request->getPost('state_description') ?? '');
|
||||
$actionTaken = (string) ($this->request->getPost('action_taken') ?? '');
|
||||
|
||||
if (!$newState) {
|
||||
session()->setFlashdata('error', 'incident state not provided.');
|
||||
return $this->index();
|
||||
}
|
||||
|
||||
$update = ['flag_state' => $newState];
|
||||
if ($newState === 'Closed') {
|
||||
$update['updated_by_closed'] = $userId;
|
||||
if ($stateDescription !== '') {
|
||||
$update['close_description'] = $stateDescription;
|
||||
}
|
||||
if ($actionTaken !== '') {
|
||||
$update['action_taken'] = $actionTaken;
|
||||
}
|
||||
} elseif ($newState === 'Canceled') {
|
||||
$update['updated_by_canceled'] = $userId;
|
||||
if ($stateDescription !== '') {
|
||||
$update['cancel_description'] = $stateDescription;
|
||||
}
|
||||
if ($actionTaken !== '') {
|
||||
$update['action_taken'] = $actionTaken;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the flag state in the database
|
||||
if ($currentFlagModel->update($id, ['flag_state' => $newState])) {
|
||||
if ($currentFlagModel->update($id, $update)) {
|
||||
if ($newState === 'Closed' || $newState === 'Canceled') {
|
||||
$flagData = $currentFlagModel->find($id);
|
||||
if ($flagData) {
|
||||
return $this->moveToHistory($flagData);
|
||||
}
|
||||
}
|
||||
session()->setFlashdata('success', 'Incident state updated successfully!');
|
||||
} else {
|
||||
$errors = $currentFlagModel->errors();
|
||||
|
||||
@@ -28,6 +28,7 @@ use App\Models\PlacementLevelModel;
|
||||
use App\Models\PlacementBatchModel;
|
||||
use App\Models\PlacementScoreModel;
|
||||
use App\Models\GradingLockModel;
|
||||
use App\Services\NavbarService;
|
||||
|
||||
//use App\Models\ScoreModel;
|
||||
|
||||
@@ -1079,11 +1080,14 @@ class GradingController extends Controller
|
||||
$schoolYears = $this->getSchoolYearsForScores($schoolYear);
|
||||
$rows = $this->fetchBelowSixtyRows($schoolYear, $semester);
|
||||
|
||||
$canViewGrading = $this->userHasMenuUrl('grading');
|
||||
|
||||
return view('grading/below_sixty', [
|
||||
'rows' => $rows,
|
||||
'semester' => $semester,
|
||||
'schoolYear' => $schoolYear,
|
||||
'schoolYears' => $schoolYears,
|
||||
'canViewGrading' => $canViewGrading,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1212,6 +1216,14 @@ class GradingController extends Controller
|
||||
|
||||
$flagModel = new CurrentFlagModel();
|
||||
$semKey = strtolower(trim($semester));
|
||||
$redirectUrl = base_url('grading/below-60');
|
||||
$query = http_build_query([
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
]);
|
||||
if ($query !== '') {
|
||||
$redirectUrl .= '?' . $query;
|
||||
}
|
||||
|
||||
$existing = $flagModel
|
||||
->where('student_id', $studentId)
|
||||
@@ -1222,11 +1234,14 @@ class GradingController extends Controller
|
||||
|
||||
$userId = (int)(session()->get('user_id') ?? 0) ?: null;
|
||||
$now = utc_now();
|
||||
$ok = true;
|
||||
|
||||
if ($existing) {
|
||||
$data = [
|
||||
'flag_state' => $status,
|
||||
'flag_datetime' => $now,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
if ($status === 'Open') {
|
||||
@@ -1242,7 +1257,7 @@ class GradingController extends Controller
|
||||
$data['close_description'] = trim($prev . PHP_EOL . $note);
|
||||
}
|
||||
}
|
||||
$flagModel->update((int)$existing['id'], $data);
|
||||
$ok = (bool) $flagModel->update((int)$existing['id'], $data);
|
||||
} else {
|
||||
$row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
|
||||
$studentName = trim((string)($row['firstname'] ?? '') . ' ' . (string)($row['lastname'] ?? ''));
|
||||
@@ -1265,10 +1280,21 @@ class GradingController extends Controller
|
||||
$data['updated_by_closed'] = $userId;
|
||||
if ($note !== '') $data['close_description'] = $note;
|
||||
}
|
||||
$flagModel->insert($data);
|
||||
$ok = (bool) $flagModel->insert($data);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('status', 'Status updated.');
|
||||
if (!$ok) {
|
||||
log_message('error', 'updateBelowSixtyStatus failed', [
|
||||
'student_id' => $studentId,
|
||||
'semester' => $semester,
|
||||
'school_year' => $schoolYear,
|
||||
'status' => $status,
|
||||
'errors' => $flagModel->errors(),
|
||||
]);
|
||||
return redirect()->to($redirectUrl)->with('error', 'Failed to update status.');
|
||||
}
|
||||
|
||||
return redirect()->to($redirectUrl)->with('status', 'Status updated.');
|
||||
}
|
||||
|
||||
public function scheduleBelowSixty()
|
||||
@@ -1793,9 +1819,10 @@ class GradingController extends Controller
|
||||
}
|
||||
|
||||
$statusMap = [];
|
||||
$noteMap = [];
|
||||
if (!empty($studentIds)) {
|
||||
$flagRows = $this->db->table('current_flag')
|
||||
->select('student_id, flag_state')
|
||||
->select('student_id, flag_state, open_description, close_description')
|
||||
->where('flag', 'grade')
|
||||
->where('school_year', $schoolYear)
|
||||
->where("LOWER(TRIM(semester))", $semesterKey)
|
||||
@@ -1806,6 +1833,12 @@ class GradingController extends Controller
|
||||
$sid = (int)($row['student_id'] ?? 0);
|
||||
if ($sid <= 0) continue;
|
||||
$statusMap[$sid] = (string)($row['flag_state'] ?? '');
|
||||
$openNote = trim((string)($row['open_description'] ?? ''));
|
||||
$closeNote = trim((string)($row['close_description'] ?? ''));
|
||||
$noteMap[$sid] = [
|
||||
'open' => $openNote,
|
||||
'closed' => $closeNote,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1814,6 +1847,15 @@ class GradingController extends Controller
|
||||
$row['comment'] = $commentMap[$sid] ?? '';
|
||||
$flagState = strtolower(trim((string)($statusMap[$sid] ?? '')));
|
||||
$row['status'] = ($flagState === 'closed' || $flagState === 'canceled') ? 'Closed' : 'Open';
|
||||
$noteBag = $noteMap[$sid] ?? ['open' => '', 'closed' => ''];
|
||||
$rawNote = $row['status'] === 'Closed' ? (string)$noteBag['closed'] : (string)$noteBag['open'];
|
||||
if ($rawNote !== '') {
|
||||
$lines = preg_split('/\R/', $rawNote);
|
||||
$lines = array_values(array_filter(array_map('trim', $lines), static fn($val) => $val !== ''));
|
||||
$row['note'] = $lines ? end($lines) : '';
|
||||
} else {
|
||||
$row['note'] = '';
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
@@ -1863,6 +1905,54 @@ class GradingController extends Controller
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function userHasMenuUrl(string $needle): bool
|
||||
{
|
||||
$needle = strtolower(trim($needle));
|
||||
if ($needle === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rawRole = session()->get('role');
|
||||
$roles = is_array($rawRole) ? $rawRole : [$rawRole ?? 'guest'];
|
||||
$roles = array_values(array_filter(array_map('strval', $roles)));
|
||||
if (empty($roles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$service = new NavbarService();
|
||||
$menu = $service->getMenuForRoles($roles);
|
||||
if (empty($menu)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$normalize = static function (string $url) use ($needle): string {
|
||||
$url = strtolower(trim($url));
|
||||
if ($url === '') return '';
|
||||
$url = preg_replace('#^https?://[^/]+/#i', '', $url);
|
||||
$url = ltrim($url, '/');
|
||||
return $url;
|
||||
};
|
||||
|
||||
$target = $normalize($needle);
|
||||
$stack = $menu;
|
||||
while (!empty($stack)) {
|
||||
$node = array_shift($stack);
|
||||
if (!empty($node['url'])) {
|
||||
$url = $normalize((string)$node['url']);
|
||||
if ($url !== '' && $url === $target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!empty($node['children']) && is_array($node['children'])) {
|
||||
foreach ($node['children'] as $child) {
|
||||
$stack[] = $child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function fetchBelowSixtyParentName(int $studentId): string
|
||||
{
|
||||
$parentName = 'Parent/Guardian';
|
||||
|
||||
@@ -717,6 +717,7 @@ class ParentController extends BaseController
|
||||
|
||||
// Step 1: Generate a secure token for email verification
|
||||
$token = bin2hex(random_bytes(48));
|
||||
$tokenHash = hash('sha256', $token);
|
||||
|
||||
// Step 2: Determine user type based on relationship
|
||||
$userType = in_array(strtolower($relationToStudent), ['wife', 'husband']) ? 'Secondary' : 'Tertiary';
|
||||
@@ -776,7 +777,7 @@ class ParentController extends BaseController
|
||||
'state' => strtoupper($userData['state']),
|
||||
'zip' => $userData['zip'],
|
||||
'accept_school_policy' => $userData['accept_school_policy'] ?? 0,
|
||||
'token' => $token,
|
||||
'token' => $tokenHash,
|
||||
'is_verified' => 0,
|
||||
'status' => 'Inactive',
|
||||
'user_type' => $userType,
|
||||
|
||||
@@ -173,16 +173,8 @@ class RegisterController extends Controller
|
||||
$existingUser = $this->userModel->where('email', $post['email'])->first();
|
||||
|
||||
if ($existingUser) {
|
||||
// Step 2: Check if the user has a token (i.e., not verified yet)
|
||||
if (!empty($existingUser['token']) && $existingUser['is_verified'] == 0) {
|
||||
// User exists and is unverified
|
||||
return redirect()->back()->withInput()->with('error',
|
||||
'This email address is already registered and is pending activation. Please check your email to activate your account.');
|
||||
} else {
|
||||
// User exists and is already active or has no token
|
||||
return redirect()->back()->withInput()->with('error',
|
||||
'The email address you entered is already in use. Please try a different one.');
|
||||
}
|
||||
return redirect()->back()->withInput()->with('error',
|
||||
'This email address is already registered. Please check your email or log in.');
|
||||
}
|
||||
|
||||
/* ───────────── 6. Determine role ───────────── */
|
||||
@@ -194,6 +186,7 @@ class RegisterController extends Controller
|
||||
|
||||
/* ───────────── 7. Build & insert user ───────────── */
|
||||
$token = bin2hex(random_bytes(48));
|
||||
$tokenHash = hash('sha256', $token);
|
||||
$userData = [
|
||||
'firstname' => $post['firstname'],
|
||||
'lastname' => $post['lastname'],
|
||||
@@ -205,7 +198,7 @@ class RegisterController extends Controller
|
||||
'city' => $post['city'],
|
||||
'state' => $post['state'],
|
||||
'zip' => $post['zip'],
|
||||
'token' => $token,
|
||||
'token' => $tokenHash,
|
||||
'is_verified'=> 0,
|
||||
'accept_school_policy' => (int) $post['accept_school_policy'],
|
||||
'status' => 'Inactive',
|
||||
@@ -357,4 +350,4 @@ class RegisterController extends Controller
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ class SubjectCurriculumController extends BaseController
|
||||
->orderBy('classes.class_name', 'ASC')
|
||||
->orderBy('subject', 'ASC')
|
||||
->orderBy('unit_number', 'ASC')
|
||||
->orderBy("CAST(SUBSTRING_INDEX(subject_curriculum_items.chapter_name, '.', 1) AS UNSIGNED)", 'ASC', false)
|
||||
->orderBy('chapter_name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
@@ -21,6 +21,7 @@ require_once APPPATH . 'Helpers/pbkdf2_helper.php';
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
private const ACTIVATION_TTL_HOURS = 48;
|
||||
protected $userModel;
|
||||
protected $roleModel;
|
||||
protected $userRoleModel;
|
||||
@@ -49,6 +50,37 @@ class UserController extends BaseController
|
||||
$this->resetRequestModel = new PasswordResetRequestModel();
|
||||
}
|
||||
|
||||
private function denyAccess(string $message)
|
||||
{
|
||||
if ($this->request->isAJAX() || $this->request->getHeaderLine('Accept') === 'application/json') {
|
||||
return service('response')
|
||||
->setStatusCode(403)
|
||||
->setJSON(['status' => 'error', 'message' => $message]);
|
||||
}
|
||||
|
||||
session()->setFlashdata('error', $message);
|
||||
return redirect()->to('/access_denied');
|
||||
}
|
||||
|
||||
private function requirePermission(string $permission)
|
||||
{
|
||||
if (!session()->get('is_logged_in')) {
|
||||
return redirect()->to('/login');
|
||||
}
|
||||
|
||||
$userId = (int) session()->get('user_id');
|
||||
if ($userId <= 0 || !has_permission($userId, $permission)) {
|
||||
return $this->denyAccess("You don't have permission to use this feature.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function hashToken(string $token): string
|
||||
{
|
||||
return hash('sha256', $token);
|
||||
}
|
||||
|
||||
// Method to show the home page
|
||||
public function home()
|
||||
{
|
||||
@@ -75,6 +107,10 @@ class UserController extends BaseController
|
||||
|
||||
public function userList()
|
||||
{
|
||||
if ($resp = $this->requirePermission('read_user')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
helper('url');
|
||||
|
||||
return view('user/user_list', [
|
||||
@@ -85,6 +121,10 @@ class UserController extends BaseController
|
||||
// Method to show the list of users
|
||||
public function index()
|
||||
{
|
||||
if ($resp = $this->requirePermission('read_user')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
// Fetch users along with their assigned roles
|
||||
$builder = $this->db->table('users');
|
||||
$builder->select('users.id, users.firstname, users.lastname, users.email, user_roles.role_id, roles.name as role, users.status, users.updated_at');
|
||||
@@ -122,6 +162,10 @@ class UserController extends BaseController
|
||||
|
||||
public function userListData()
|
||||
{
|
||||
if ($resp = $this->requirePermission('read_user')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
return $this->response->setJSON([
|
||||
'users' => $this->buildUsersWithRoles(),
|
||||
]);
|
||||
@@ -253,6 +297,10 @@ class UserController extends BaseController
|
||||
// Method to store a new user
|
||||
public function store()
|
||||
{
|
||||
if ($resp = $this->requirePermission('edit_user')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
// Validate input data
|
||||
$validation = \Config\Services::validation();
|
||||
$validation->setRules([
|
||||
@@ -315,6 +363,10 @@ class UserController extends BaseController
|
||||
// Method to show the form for editing an existing user
|
||||
public function edit($id)
|
||||
{
|
||||
if ($resp = $this->requirePermission('edit_user')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$data['user'] = $this->userModel->find($id);
|
||||
$data['roles'] = $this->roleModel->findAll();
|
||||
$userRoles = $this->userRoleModel->where('user_id', $id)->findAll();
|
||||
@@ -327,6 +379,10 @@ class UserController extends BaseController
|
||||
// Method to delete an existing user
|
||||
public function delete($id)
|
||||
{
|
||||
if ($resp = $this->requirePermission('edit_user')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$this->userModel->delete($id);
|
||||
|
||||
// Delete the user's roles from the user_roles table
|
||||
@@ -369,27 +425,21 @@ class UserController extends BaseController
|
||||
$email = strtolower($this->request->getPost('email'));
|
||||
$user = $this->userModel->where('email', $email)->first();
|
||||
|
||||
// --- Handle unknown email ---
|
||||
if (!$user) {
|
||||
session()->setFlashdata('error', 'If this email is registered, you will receive a reset link.');
|
||||
log_message('info', "Password reset requested for non-existing user {$email}");
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
// --- Handle unverified accounts ---
|
||||
if ((int) $user['is_verified'] === 0) {
|
||||
session()->setFlashdata('error', 'Please check your email and complete the account activation process before resetting your password.');
|
||||
log_message('info', "Password reset blocked for unverified user {$email}");
|
||||
// --- Handle unknown or unverified email ---
|
||||
if (!$user || (int) $user['is_verified'] === 0) {
|
||||
session()->setFlashdata('success', 'If this email is registered, you will receive a reset link.');
|
||||
log_message('info', "Password reset requested for {$email} (user missing or unverified).");
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
// --- Verified user: continue with reset ---
|
||||
$token = bin2hex(random_bytes(48));
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$expires_at = Time::now()->addHours(1);
|
||||
|
||||
$this->passwordResetModel->insert([
|
||||
'email' => $email,
|
||||
'token' => $token,
|
||||
'token' => $tokenHash,
|
||||
'created_at' => Time::now(),
|
||||
'expires_at' => $expires_at,
|
||||
]);
|
||||
@@ -447,7 +497,12 @@ class UserController extends BaseController
|
||||
}
|
||||
|
||||
// You may want to validate the token here
|
||||
$resetEntry = $this->passwordResetModel->where('token', $token)
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$resetEntry = $this->passwordResetModel
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->where('expires_at >=', Time::now())
|
||||
->first();
|
||||
|
||||
@@ -462,6 +517,10 @@ class UserController extends BaseController
|
||||
//This function processes the new password submission, validating the token, updating the user's password, and cleaning up the reset entry.
|
||||
public function processResetPassword()
|
||||
{
|
||||
if (strtolower($this->request->getMethod()) !== 'post') {
|
||||
return redirect()->to('/')->with('error', 'Invalid request.');
|
||||
}
|
||||
|
||||
$token = $this->request->getPost('token');
|
||||
$newPassword = $this->request->getPost('password');
|
||||
$passConfirm = $this->request->getPost('pass_confirm');
|
||||
@@ -490,7 +549,12 @@ class UserController extends BaseController
|
||||
}
|
||||
|
||||
// Find the password reset entry
|
||||
$resetEntry = $this->passwordResetModel->where('token', $token)
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$resetEntry = $this->passwordResetModel
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->where('expires_at >=', Time::now())
|
||||
->first();
|
||||
|
||||
@@ -519,7 +583,12 @@ class UserController extends BaseController
|
||||
]);
|
||||
|
||||
// Delete the used token from the password reset table
|
||||
$this->passwordResetModel->where('token', $token)->delete();
|
||||
$this->passwordResetModel
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->delete();
|
||||
|
||||
// Retrieve the user's IP address from the request
|
||||
$ipAddress = $this->request->getIPAddress();
|
||||
@@ -546,10 +615,16 @@ class UserController extends BaseController
|
||||
|
||||
public function confirm($token)
|
||||
{
|
||||
log_message('info', 'Processing email confirmation with token: ' . $token);
|
||||
log_message('info', 'Processing email confirmation.');
|
||||
|
||||
|
||||
$user = $this->userModel->where('token', $token)->first();
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$user = $this->userModel
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->where('created_at >=', Time::now()->subHours(self::ACTIVATION_TTL_HOURS)->toDateTimeString())
|
||||
->first();
|
||||
|
||||
if (!$user || $user['is_verified'] == 1) {
|
||||
return redirect()->to('/invalid_token');
|
||||
@@ -570,7 +645,14 @@ class UserController extends BaseController
|
||||
{
|
||||
//echo "Reached setPassword with token: " . esc($token);
|
||||
//echo "Token received: " . $token;
|
||||
$user = $this->userModel->where('token', $token)->first();
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$user = $this->userModel
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->where('created_at >=', Time::now()->subHours(self::ACTIVATION_TTL_HOURS)->toDateTimeString())
|
||||
->first();
|
||||
|
||||
if (!$user || $user['is_verified'] == 1) {
|
||||
return redirect()->to('/invalid_token');
|
||||
@@ -584,6 +666,10 @@ class UserController extends BaseController
|
||||
|
||||
public function savePassword()
|
||||
{
|
||||
if (strtolower($this->request->getMethod()) !== 'post') {
|
||||
return redirect()->to('/')->with('error', 'Invalid request.');
|
||||
}
|
||||
|
||||
$validation = \Config\Services::validation();
|
||||
$validation->setRules([
|
||||
'password' => [
|
||||
@@ -615,9 +701,17 @@ class UserController extends BaseController
|
||||
$token = $this->request->getPost('token');
|
||||
$password = $this->request->getPost('password');
|
||||
|
||||
$user = $this->userModel->where('id', $userId)->where('token', $token)->first();
|
||||
$tokenHash = $this->hashToken($token);
|
||||
$user = $this->userModel
|
||||
->where('id', $userId)
|
||||
->groupStart()
|
||||
->where('token', $tokenHash)
|
||||
->orWhere('token', $token)
|
||||
->groupEnd()
|
||||
->where('created_at >=', Time::now()->subHours(self::ACTIVATION_TTL_HOURS)->toDateTimeString())
|
||||
->first();
|
||||
|
||||
log_message('debug', "Attempting to set password for user $userId with token $token");
|
||||
log_message('debug', "Attempting to set password for user $userId");
|
||||
|
||||
if (!$user || $user['is_verified'] == 1) {
|
||||
return redirect()->to('/invalid_token');
|
||||
@@ -670,20 +764,39 @@ class UserController extends BaseController
|
||||
$roleKey = (string) $this->request->getPost('role');
|
||||
log_message('info', 'Role selected: ' . $roleKey);
|
||||
|
||||
$roleModel = new RoleModel();
|
||||
$route = $roleModel->getRouteByNameOrSlug($roleKey);
|
||||
|
||||
if ($route === null) {
|
||||
log_message('error', 'Invalid or inactive role selected: ' . $roleKey);
|
||||
return redirect()->back()->with('error', 'Invalid role selected.');
|
||||
}
|
||||
|
||||
$userId = (int) session()->get('user_id');
|
||||
log_message('info', 'User ID: ' . $userId);
|
||||
|
||||
if ($userId <= 0) {
|
||||
return $this->denyAccess("You don't have permission to use this feature.");
|
||||
}
|
||||
|
||||
$roleRow = $this->db->table('user_roles ur')
|
||||
->join('roles r', 'r.id = ur.role_id', 'inner')
|
||||
->select('r.name, r.slug, r.dashboard_route')
|
||||
->where('ur.user_id', $userId)
|
||||
->where('r.is_active', 1)
|
||||
->groupStart()
|
||||
->where('LOWER(r.name)', strtolower($roleKey))
|
||||
->orWhere('LOWER(r.slug)', strtolower($roleKey))
|
||||
->groupEnd()
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
if (empty($roleRow)) {
|
||||
log_message('error', 'Invalid or unassigned role selected: ' . $roleKey);
|
||||
return redirect()->back()->with('error', 'Invalid role selected.');
|
||||
}
|
||||
|
||||
$route = $roleRow['dashboard_route'] ?? null;
|
||||
if ($route === null) {
|
||||
log_message('error', 'No dashboard route configured for role: ' . $roleKey);
|
||||
return redirect()->back()->with('error', 'Invalid role selected.');
|
||||
}
|
||||
|
||||
// Persist the *exact* role name or slug—choose your convention.
|
||||
// If you want to store the canonical name, fetch the row and use $row['name'].
|
||||
$this->userModel->update($userId, ['role' => $roleKey]);
|
||||
// Store the canonical name to avoid arbitrary role strings.
|
||||
$this->userModel->update($userId, ['role' => $roleRow['name']]);
|
||||
log_message('info', 'Role updated in database.');
|
||||
|
||||
log_message('info', 'Redirecting to role dashboard: ' . $route);
|
||||
@@ -702,6 +815,10 @@ class UserController extends BaseController
|
||||
|
||||
public function delete_role($roleId)
|
||||
{
|
||||
if ($resp = $this->requirePermission('edit_user')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
// Fetch the role to be deleted
|
||||
$role = $this->roleModel->find($roleId);
|
||||
if (!$role) {
|
||||
@@ -731,6 +848,10 @@ class UserController extends BaseController
|
||||
|
||||
public function loginActivity()
|
||||
{
|
||||
if ($resp = $this->requirePermission('view_login_activity')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
helper('url');
|
||||
|
||||
$perPage = (int) ($this->request->getGet('per_page') ?? 25);
|
||||
@@ -743,6 +864,10 @@ class UserController extends BaseController
|
||||
|
||||
public function loginActivityData()
|
||||
{
|
||||
if ($resp = $this->requirePermission('view_login_activity')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
$perPage = (int) ($this->request->getGet('per_page') ?? 25);
|
||||
$page = (int) ($this->request->getGet('page') ?? 1);
|
||||
|
||||
@@ -752,6 +877,10 @@ class UserController extends BaseController
|
||||
// Method to update an existing user
|
||||
public function updateUser()
|
||||
{
|
||||
if ($resp = $this->requirePermission('edit_user')) {
|
||||
return $resp;
|
||||
}
|
||||
|
||||
if (strtolower($this->request->getMethod()) !== 'post') {
|
||||
return redirect()->to(site_url('user/user_list'))->with('error', 'Invalid request.');
|
||||
}
|
||||
@@ -800,9 +929,6 @@ class UserController extends BaseController
|
||||
'status' => trim((string)$this->request->getPost('status')),
|
||||
'is_suspended' => $toBool('is_suspended'),
|
||||
'is_verified' => $toBool('is_verified'),
|
||||
'token' => trim((string)$this->request->getPost('token')),
|
||||
'updated_at' => $toDT('updated_at'),
|
||||
'created_at' => $toDT('created_at'),
|
||||
];
|
||||
|
||||
// Validation
|
||||
|
||||
@@ -22,11 +22,13 @@ class ConfigurationModel extends Model
|
||||
*/
|
||||
public function getConfigValueByKey(string $key)
|
||||
{
|
||||
// Deterministic read in case historical duplicates exist
|
||||
$result = $this->where('config_key', $key)
|
||||
// Use a fresh builder to avoid stale state from shared model builder.
|
||||
$builder = $this->db->table($this->table);
|
||||
$result = $builder->where('config_key', $key)
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
return $result ? $result['config_value'] : null;
|
||||
->get(1)
|
||||
->getRowArray();
|
||||
return $result['config_value'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ class SubjectCurriculumModel extends Model
|
||||
return $this->where('class_id', $classId)
|
||||
->where('subject', $subject)
|
||||
->orderBy('unit_number', 'ASC')
|
||||
->orderBy("CAST(SUBSTRING_INDEX(chapter_name, '.', 1) AS UNSIGNED)", 'ASC', false)
|
||||
->orderBy('chapter_name', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
@@ -167,8 +167,13 @@ class TimeService
|
||||
return null;
|
||||
}
|
||||
|
||||
$sourceTz = $sourceTz ?: $this->serverTimezone;
|
||||
$targetTz = $targetTz ?: $this->userTimezone();
|
||||
if ($sourceTz === null && $this->isDateOnlyString($value)) {
|
||||
// Date-only strings should not shift across timezones.
|
||||
$sourceTz = $targetTz;
|
||||
} else {
|
||||
$sourceTz = $sourceTz ?: $this->serverTimezone;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($value instanceof Time) {
|
||||
@@ -204,4 +209,14 @@ class TimeService
|
||||
{
|
||||
return (string) ($this->toUTC($value, $fromTz, $format) ?? '');
|
||||
}
|
||||
|
||||
private function isDateOnlyString($value): bool
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = trim($value);
|
||||
return (bool) preg_match('/^\d{4}-\d{2}-\d{2}$/', $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,37 +517,28 @@
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-4 text-center">Statistics</h2>
|
||||
<div class="stats-row">
|
||||
<!-- Students -->
|
||||
<div class="stat-circle circle-primary">
|
||||
<div class="stat-icon"><i class="bi bi-people-fill"></i></div>
|
||||
<div class="stat-icon"><i class="fas fa-user-graduate"></i></div>
|
||||
<div class="stat-value" data-stat="students">—</div>
|
||||
<div class="stat-title">Students</div>
|
||||
</div>
|
||||
|
||||
<!-- Teachers -->
|
||||
<div class="stat-circle circle-info">
|
||||
<div class="stat-icon"><i class="bi bi-person-video2"></i></div>
|
||||
<div class="stat-icon"><i class="fas fa-chalkboard-teacher"></i></div>
|
||||
<div class="stat-value" data-stat="teachers">—</div>
|
||||
<div class="stat-title">Teachers</div>
|
||||
</div>
|
||||
|
||||
<!-- Teacher Assistants -->
|
||||
<div class="stat-circle circle-success">
|
||||
<div class="stat-icon"><i class="bi bi-person-badge-fill"></i></div>
|
||||
<div class="stat-icon"><i class="fas fa-user-cog"></i></div>
|
||||
<div class="stat-value" data-stat="teacherAssistants">—</div>
|
||||
<div class="stat-title">Teachers' Assistants</div>
|
||||
<div class="stat-title">TAs</div>
|
||||
</div>
|
||||
|
||||
<!-- Admins -->
|
||||
<div class="stat-circle circle-warning">
|
||||
<div class="stat-icon"><i class="bi bi-shield-lock-fill"></i></div>
|
||||
<div class="stat-icon"><i class="fas fa-user-shield"></i></div>
|
||||
<div class="stat-value" data-stat="admins">—</div>
|
||||
<div class="stat-title">Admins</div>
|
||||
</div>
|
||||
|
||||
<!-- Parents -->
|
||||
<div class="stat-circle circle-light">
|
||||
<div class="stat-icon"><i class="bi bi-people"></i></div>
|
||||
<div class="stat-icon"><i class="fas fa-users"></i></div>
|
||||
<div class="stat-value" data-stat="parents">—</div>
|
||||
<div class="stat-title">Parents</div>
|
||||
</div>
|
||||
|
||||
@@ -71,9 +71,20 @@
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
.attn-pie-wrap {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.attn-analysis-half { grid-column: span 12; }
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
.attn-pie-wrap {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
@@ -239,6 +250,54 @@
|
||||
$analysisSectionTotals[] = (int)($s['total_students'] ?? 0);
|
||||
}
|
||||
|
||||
// ---- Student absences/late list ----
|
||||
$sectionLabelByKey = [];
|
||||
foreach ($grades as $classId => $sections) {
|
||||
foreach ($sections as $section) {
|
||||
$sectionKey = (string)($section['class_section_id'] ?? ($section['id'] ?? ''));
|
||||
if ($sectionKey === '') continue;
|
||||
$secNameRaw = trim((string)($section['class_section_name'] ?? ''));
|
||||
$sectionLabelByKey[$sectionKey] = $secNameRaw !== '' ? $secNameRaw : ('Section ' . $sectionKey);
|
||||
}
|
||||
}
|
||||
|
||||
$studentIssueRows = [];
|
||||
foreach ($studentsBySection as $sectionKey => $students) {
|
||||
foreach ($students as $stu) {
|
||||
$sid = (int)($stu['id'] ?? 0);
|
||||
if ($sid <= 0) continue;
|
||||
$entries = $attendanceData[$sectionKey][$sid] ?? [];
|
||||
if (!is_array($entries)) continue;
|
||||
$abs = 0;
|
||||
$late = 0;
|
||||
foreach ($entries as $e) {
|
||||
$d = substr((string)($e['date'] ?? ''), 0, 10);
|
||||
if ($d === '') continue;
|
||||
if ($filterStart !== '' && $d < $filterStart) continue;
|
||||
if ($filterEnd !== '' && $d > $filterEnd) continue;
|
||||
$st = strtolower(trim((string)($e['status'] ?? '')));
|
||||
if ($st === 'absent') {
|
||||
$abs++;
|
||||
} elseif ($st === 'late') {
|
||||
$late++;
|
||||
}
|
||||
}
|
||||
if (($abs + $late) <= 0) continue;
|
||||
$studentIssueRows[] = [
|
||||
'name' => trim((string)($stu['firstname'] ?? '') . ' ' . (string)($stu['lastname'] ?? '')),
|
||||
'section' => $sectionLabelByKey[(string)$sectionKey] ?? ('Section ' . $sectionKey),
|
||||
'absent' => $abs,
|
||||
'late' => $late,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
usort($studentIssueRows, static function ($a, $b) {
|
||||
$sec = strcmp($a['section'], $b['section']);
|
||||
if ($sec !== 0) return $sec;
|
||||
return strcmp($a['name'], $b['name']);
|
||||
});
|
||||
|
||||
$totalDaysForPercent = 0;
|
||||
if ($filterStart === '' && $filterEnd === '' && !empty($totalPassedDays)) {
|
||||
$totalDaysForPercent = (int)$totalPassedDays;
|
||||
@@ -291,7 +350,9 @@
|
||||
<div class="attn-analysis-grid">
|
||||
<div class="attn-analysis-card attn-analysis-half">
|
||||
<h6>Overall Distribution</h6>
|
||||
<canvas id="attnPieChart" height="50" aria-label="Overall attendance pie chart"></canvas>
|
||||
<div class="attn-pie-wrap">
|
||||
<canvas id="attnPieChart" aria-label="Overall attendance pie chart"></canvas>
|
||||
</div>
|
||||
<table class="attn-analysis-table mt-3 no-mgmt-sticky" data-no-mgmt-sticky>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -360,6 +421,37 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attn-analysis-card attn-analysis-wide">
|
||||
<h6>Students With Absences / Late</h6>
|
||||
<div class="attn-analysis-scroll">
|
||||
<table id="studentIssueTable" class="attn-analysis-table no-mgmt-sticky" data-no-mgmt-sticky>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Student Name</th>
|
||||
<th>Class Section</th>
|
||||
<th class="text-end">Nbr of ABS</th>
|
||||
<th class="text-end">Nbr of LATE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($studentIssueRows)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted">No absences or late records in the selected range.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($studentIssueRows as $row): ?>
|
||||
<tr>
|
||||
<td><?= esc($row['name']) ?></td>
|
||||
<td><?= esc($row['section']) ?></td>
|
||||
<td class="text-end"><?= (int)$row['absent'] ?></td>
|
||||
<td class="text-end"><?= (int)$row['late'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -415,6 +507,8 @@
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
@@ -465,6 +559,13 @@
|
||||
info: false,
|
||||
order: [[0, 'asc']]
|
||||
});
|
||||
$('#studentIssueTable').DataTable({
|
||||
paging: true,
|
||||
searching: true,
|
||||
info: true,
|
||||
order: [[1, 'asc'], [0, 'asc']],
|
||||
pageLength: 25
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?= $this->extend('layout/email_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
<?= $body_html ?? '' ?>
|
||||
<?= $this->endSection() ?>
|
||||
@@ -22,6 +22,10 @@ if ($title === '' || preg_match('/^\s*Family\s+of\s+User\s*\d+\s*$/i', $title))
|
||||
}
|
||||
$title = $lastName !== '' ? ('Family of ' . $lastName) : 'Family';
|
||||
}
|
||||
$returnUrl = trim((string)service('request')->getServer('HTTP_REFERER'));
|
||||
if ($returnUrl === '') {
|
||||
$returnUrl = site_url('family');
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="family-card-root" data-family-title="<?= esc($title) ?>">
|
||||
@@ -187,7 +191,14 @@ if ($title === '' || preg_match('/^\s*Family\s+of\s+User\s*\d+\s*$/i', $title))
|
||||
<td class="small" data-label="Contact">
|
||||
<div>
|
||||
<i class="bi bi-envelope me-1 text-muted"></i>
|
||||
<?= !empty($g['email']) ? ('<a href="mailto:'.esc($g['email']).'">'.esc($g['email']).'</a>') : '<span class="text-muted">—</span>' ?>
|
||||
<?php
|
||||
$gEmail = trim((string)($g['email'] ?? ''));
|
||||
$gName = trim((string)($g['firstname'] ?? '') . ' ' . (string)($g['lastname'] ?? ''));
|
||||
$composeUrl = $gEmail !== ''
|
||||
? site_url('family/compose-email?to=' . rawurlencode($gEmail) . '&name=' . rawurlencode($gName) . '&return_url=' . rawurlencode($returnUrl))
|
||||
: '';
|
||||
?>
|
||||
<?= $gEmail !== '' ? ('<a href="' . esc($composeUrl) . '" target="_blank" rel="noopener">' . esc($gEmail) . '</a>') : '<span class="text-muted">—</span>' ?>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<i class="bi bi-telephone me-1 text-muted"></i>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="container my-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
|
||||
<div>
|
||||
<h2 class="h4 mb-1">Compose Email</h2>
|
||||
<?php if (!empty($name)): ?>
|
||||
<div class="text-muted"><?= esc($name) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= esc((string)($return_url ?? site_url('family'))) ?>">Back</a>
|
||||
</div>
|
||||
|
||||
<form class="card shadow-sm" method="post" action="<?= site_url('family/compose-email/send') ?>">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="html" id="compose_html" value="">
|
||||
<input type="hidden" name="return_url" value="<?= esc((string)($return_url ?? '')) ?>">
|
||||
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="composeTo">To</label>
|
||||
<input type="email" class="form-control" id="composeTo" name="to" value="<?= esc((string)($to ?? '')) ?>" placeholder="parent@example.com" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="composeSubject">Subject</label>
|
||||
<input type="text" class="form-control" id="composeSubject" name="subject" placeholder="Subject" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="composeEditor">Body (Rich Text)</label>
|
||||
<textarea class="form-control" id="composeEditor" rows="14"></textarea>
|
||||
<div class="form-text">Use the toolbar to format your message before sending.</div>
|
||||
<noscript>
|
||||
<div class="alert alert-warning mt-2">
|
||||
JavaScript is disabled. The message will be sent from the hidden <code>html</code> field.
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<button type="submit" class="btn btn-primary">Send Email</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<script src="<?= base_url('assets/tinymce/tinymce.min.js') ?>"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const form = document.querySelector('form[action$="family/compose-email/send"]');
|
||||
const hiddenHtml = document.getElementById('compose_html');
|
||||
|
||||
tinymce.init({
|
||||
selector: '#composeEditor',
|
||||
base_url: '<?= base_url('assets/tinymce') ?>',
|
||||
suffix: '.min',
|
||||
license_key: 'gpl',
|
||||
height: 420,
|
||||
menubar: true,
|
||||
branding: false,
|
||||
promotion: false,
|
||||
plugins: 'advlist autolink lists link image charmap preview anchor ' +
|
||||
'searchreplace visualblocks code fullscreen insertdatetime media table ' +
|
||||
'help wordcount emoticons codesample',
|
||||
toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough forecolor backcolor | ' +
|
||||
'alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | ' +
|
||||
'link image media table | emoticons codesample | removeformat | preview code',
|
||||
convert_urls: false,
|
||||
paste_data_images: true,
|
||||
image_caption: true,
|
||||
content_style: 'body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; font-size: 14px; }',
|
||||
setup(editor) {
|
||||
editor.on('keyup change undo redo SetContent', function () {
|
||||
if (hiddenHtml) hiddenHtml.value = editor.getContent({ format: 'html' });
|
||||
});
|
||||
if (form) {
|
||||
form.addEventListener('submit', function () {
|
||||
if (hiddenHtml) hiddenHtml.value = editor.getContent({ format: 'html' });
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
@@ -107,7 +107,10 @@
|
||||
<td><?= esc($flag['flag_state']) ?></td>
|
||||
|
||||
<td>
|
||||
<form id="flagForm_<?= $flag['id'] ?>" method="post">
|
||||
<form id="flagForm_<?= $flag['id'] ?>" method="post"
|
||||
action="<?= site_url('flags/update_state/' . (int) $flag['id']) ?>"
|
||||
data-action-close="<?= site_url('flags/closeFlag/' . (int) $flag['id']) ?>"
|
||||
data-action-cancel="<?= site_url('flags/cancelFlag/' . (int) $flag['id']) ?>">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<select name="flag_state" class="form-select" id="flag_state_<?= $flag['id'] ?>"
|
||||
@@ -347,9 +350,9 @@
|
||||
|
||||
// Set form action based on flag state
|
||||
if (flagState === "Closed") {
|
||||
form.action = `/flags/closeFlag/${currentFlagId}`;
|
||||
form.action = form.dataset.actionClose || form.action;
|
||||
} else if (flagState === "Canceled") {
|
||||
form.action = `/flags/cancelFlag/${currentFlagId}`;
|
||||
form.action = form.dataset.actionCancel || form.action;
|
||||
}
|
||||
|
||||
console.log("Description set for form submission:", description); // For debugging
|
||||
@@ -357,6 +360,10 @@
|
||||
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('descriptionModal'));
|
||||
modal.hide();
|
||||
|
||||
if (form && form.action) {
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('flagStateDescription').addEventListener('input', function() {
|
||||
|
||||
@@ -11,9 +11,13 @@
|
||||
<div class="text-muted">
|
||||
<?= esc(ucfirst($semester ?? '')) ?> • <?= esc($schoolYear ?? '') ?>
|
||||
</div>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= base_url('grading') ?>">
|
||||
Back to Grading
|
||||
</a>
|
||||
<?php if (!empty($canViewGrading)): ?>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="<?= base_url('grading') ?>">
|
||||
Back to Grading
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span class="text-muted small">You do not have access to the Grading page.</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
@@ -89,7 +93,7 @@
|
||||
<option value="Open" <?= ($row['status'] ?? 'Open') === 'Open' ? 'selected' : '' ?>>Open</option>
|
||||
<option value="Closed" <?= ($row['status'] ?? '') === 'Closed' ? 'selected' : '' ?>>Closed</option>
|
||||
</select>
|
||||
<input type="text" name="note" class="form-control form-control-sm" style="width: 140px;" placeholder="Note (optional)">
|
||||
<input type="text" name="note" class="form-control form-control-sm" style="width: 140px;" placeholder="Note (optional)" value="<?= esc((string)($row['note'] ?? '')) ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-secondary">Update</button>
|
||||
</form>
|
||||
</td>
|
||||
|
||||
+173
-14
@@ -322,6 +322,72 @@
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.home-stats {
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.08);
|
||||
padding: 2.5rem 1.5rem;
|
||||
}
|
||||
|
||||
.home-stats .stats-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.home-stats .stat-circle {
|
||||
width: 190px;
|
||||
height: 190px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.home-stats .stat-value {
|
||||
font-size: 1.8rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.home-stats .stat-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: .2rem;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
.home-stats .stat-title {
|
||||
font-size: 1.1rem;
|
||||
margin-top: .25rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.home-stats .circle-primary {
|
||||
background: radial-gradient(circle at 30% 30%, #0d6efd, #0044aa);
|
||||
}
|
||||
|
||||
.home-stats .circle-success {
|
||||
background: radial-gradient(circle at 30% 30%, #198754, #0c4f2c);
|
||||
}
|
||||
|
||||
.home-stats .circle-warning {
|
||||
background: radial-gradient(circle at 30% 30%, #daa544ff, #a66f00);
|
||||
}
|
||||
|
||||
.home-stats .circle-info {
|
||||
background: radial-gradient(circle at 30% 30%, #0dcaf0, #047e9c);
|
||||
}
|
||||
|
||||
.home-stats .circle-light {
|
||||
background: radial-gradient(circle at 30% 30%, #63af4cff, #00eb7dff);
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.image-column {
|
||||
min-height: 300px;
|
||||
@@ -332,6 +398,13 @@
|
||||
border-radius: 0 0 10px 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.home-stats .stat-circle {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -448,8 +521,43 @@
|
||||
<h4 class="fw-bold my-0"><u>Join Al Rahma family today! Click here for a quick guide on how to create an account.</u></h4>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- Statistics Start -->
|
||||
<div class="container-xxl py-2 content-section">
|
||||
<div class="container">
|
||||
<div class="home-stats" data-dashboard-endpoint="<?= site_url('api/administrator/dashboard') ?>">
|
||||
<h1 class="d-flex justify-content-center p-md-1">Active Participants</h1>
|
||||
<br>
|
||||
<div class="stats-row">
|
||||
<div class="stat-circle circle-primary">
|
||||
<div class="stat-icon"><i class="fa-solid fa-user-graduate"></i></div>
|
||||
<div class="stat-value" data-stat="students">—</div>
|
||||
<div class="stat-title">Students</div>
|
||||
</div>
|
||||
<div class="stat-circle circle-info">
|
||||
<div class="stat-icon"><i class="fa-solid fa-chalkboard-user"></i></div>
|
||||
<div class="stat-value" data-stat="teachers">—</div>
|
||||
<div class="stat-title">Teachers</div>
|
||||
</div>
|
||||
<div class="stat-circle circle-success">
|
||||
<div class="stat-icon"><i class="fa-solid fa-user-gear"></i></div>
|
||||
<div class="stat-value" data-stat="teacherAssistants">—</div>
|
||||
<div class="stat-title">TAs</div>
|
||||
</div>
|
||||
<div class="stat-circle circle-warning">
|
||||
<div class="stat-icon"><i class="fa-solid fa-user-shield"></i></div>
|
||||
<div class="stat-value" data-stat="admins">—</div>
|
||||
<div class="stat-title">Admins</div>
|
||||
</div>
|
||||
<div class="stat-circle circle-light">
|
||||
<div class="stat-icon"><i class="fa-solid fa-people-group"></i></div>
|
||||
<div class="stat-value" data-stat="parents">—</div>
|
||||
<div class="stat-title">Parents</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Statistics End -->
|
||||
<div class="container-xxl py-2 content-section">
|
||||
<div class="container">
|
||||
<div class="row bg-light squared align-items-center">
|
||||
@@ -461,18 +569,18 @@
|
||||
<p class="mb-4">Under the umbrella of ISGL, Al Rahma Sunday School has been serving the community for over thirty years. Throughout the decades, it has provided students with a strong foundation in Islamic Studies and Quran, fostering both knowledge and character. With its dedicated teachers and well-rounded academic program, the school continues to guide generations of Muslim youth in their faith, values and practice of Islam.</p>
|
||||
<p>For more information, click below to visit ISGL official website.</p>
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#28a745" style="border-radius:6px;">
|
||||
<a href="https://isgl.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style="display:inline-block;padding:12px 18px;color:#ffffff;text-decoration:none;font-weight:600;">
|
||||
ISGL Official Website
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<tr>
|
||||
<td align="center" bgcolor="#28a745" style="border-radius:6px;">
|
||||
<a href="https://isgl.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style="display:inline-block;padding:12px 18px;color:#ffffff;text-decoration:none;font-weight:600;">
|
||||
ISGL Official Website
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Column - Fixed with correct path -->
|
||||
@@ -677,6 +785,57 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const container = document.querySelector('.home-stats[data-dashboard-endpoint]');
|
||||
if (!container) return;
|
||||
const endpoint = container.dataset.dashboardEndpoint;
|
||||
const statElements = container.querySelectorAll('[data-stat]');
|
||||
const numberFormatter = new Intl.NumberFormat();
|
||||
let isLoading = false;
|
||||
|
||||
const loadStats = function() {
|
||||
if (isLoading) return;
|
||||
isLoading = true;
|
||||
fetch(endpoint, {
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then(function(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Request failed');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(function(payload) {
|
||||
const counts = payload && typeof payload === 'object' && payload.counts ?
|
||||
payload.counts :
|
||||
{};
|
||||
statElements.forEach(function(element) {
|
||||
const key = element.dataset.stat;
|
||||
const value = counts[key];
|
||||
element.textContent = Number.isFinite(value) ?
|
||||
numberFormatter.format(value) :
|
||||
'—';
|
||||
});
|
||||
})
|
||||
.catch(function() {})
|
||||
.finally(function() {
|
||||
isLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
loadStats();
|
||||
setInterval(loadStats, 60000);
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (document.visibilityState === 'visible') {
|
||||
loadStats();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
/** @var string $school_year */
|
||||
/** @var array<string> $schoolYears */
|
||||
/** @var array<int,array{parent_id:int,parent_name:string,email:string,total_invoice:float,total_balance:float,total_discount:float,total_paid:float,remaining_installments:int,installment_amount:float,type:string,has_installment?:int,next_installment?:string}> $rows */
|
||||
/** @var array<int,array{parent_id:int,parent_name:string,email:string,total_invoice:float,total_balance:float,total_discount:float,total_paid:float,payment_count:int,remaining_installments:int,installment_amount:float,type:string,has_installment?:int,next_installment?:string}> $rows */
|
||||
?>
|
||||
|
||||
<?= $this->extend('layout/management_layout') ?>
|
||||
@@ -15,8 +15,6 @@
|
||||
.table thead th { background: var(--mgmt-thead-bg, #f1f3f5); }
|
||||
.actions { white-space: nowrap; }
|
||||
.actions .btn { --bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; }
|
||||
.email-cell { max-width: 280px; overflow: hidden; text-overflow: ellipsis; }
|
||||
@media (max-width: 576px){ .email-cell { max-width: 180px; } }
|
||||
/* Disable sticky header for this table to avoid overlap */
|
||||
table.no-mgmt-sticky thead th { position: static !important; }
|
||||
</style>
|
||||
@@ -54,7 +52,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parent</th>
|
||||
<th>Email</th>
|
||||
<th class="text-center">Nbr of Installements</th>
|
||||
<th>Type</th>
|
||||
<th class="text-end">Invoice Amount</th>
|
||||
<th class="text-end">Applied Discount</th>
|
||||
@@ -86,7 +84,7 @@
|
||||
</a>
|
||||
<small class="text-muted">#<?= (int)$r['parent_id'] ?></small>
|
||||
</td>
|
||||
<td class="email-cell"><a href="mailto:<?= esc($r['email']) ?>"><?= esc($r['email']) ?></a></td>
|
||||
<td class="text-center"><?= (int)($r['payment_count'] ?? 0) ?></td>
|
||||
<td>
|
||||
<?php if (($r['type'] ?? '') === 'no_payment'): ?>
|
||||
<span class="badge bg-danger badge-type">no payment</span>
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<?= $this->extend('layout/register_layout') ?>
|
||||
<?= $this->section('content') ?>
|
||||
|
||||
<div class="registration-form container mt-5 mb-5">
|
||||
<form method="post" action="<?= base_url('set_authorized_user_password/' . $userId) ?>" onsubmit="return validatePassword()" autocomplete="off">
|
||||
<?= csrf_field(); ?>
|
||||
<div class="text-center mb-4">
|
||||
<a href="<?= base_url('/') ?>">
|
||||
<img src="<?= base_url('assets/images/alrahma_logo.png') ?>" alt="" style="width: 180px; height: 120px;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h3 class="text-center text-success" style="font-family: Arial, sans-serif;">Create Your Password</h3>
|
||||
<br>
|
||||
<input type="hidden" name="user_id" value="<?= esc($userId); ?>" required>
|
||||
<input type="hidden" name="token" value="<?= esc($token ?? ''); ?>" required>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="input-with-icon">
|
||||
<input type="password"
|
||||
class="form-control item"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Enter new password"
|
||||
maxlength="40"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
oncopy="return false"
|
||||
oncut="return false"
|
||||
onpaste="return false">
|
||||
<span class="toggle-password" onclick="togglePassword('password', this)">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
</span>
|
||||
</div>
|
||||
<small id="passwordHelp" class="text-danger d-none">
|
||||
Password must be at least 8 characters long, contain a number, an uppercase letter, a lowercase letter, and one special character: @, -, =, +, *, #, $, %, &, !
|
||||
</small>
|
||||
<small id="passwordCopyWarning" class="text-muted d-none">
|
||||
Copy and paste are disabled for security reasons.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="input-with-icon">
|
||||
<input type="password"
|
||||
class="form-control item"
|
||||
id="password_confirm"
|
||||
name="password_confirm"
|
||||
placeholder="Confirm new password"
|
||||
maxlength="40"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
oncopy="return false"
|
||||
oncut="return false"
|
||||
onpaste="return false">
|
||||
<span class="toggle-password" onclick="togglePassword('password_confirm', this)">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
</span>
|
||||
</div>
|
||||
<small id="confirmPasswordHelp" class="text-danger d-none">
|
||||
Passwords do not match.
|
||||
</small>
|
||||
<small id="confirmCopyWarning" class="text-muted d-none">
|
||||
Copy and paste are disabled for security reasons.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 d-grid">
|
||||
<button type="submit" class="btn btn-success item">Save Password</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('scripts') ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const showWarning = (inputId, warningId) => {
|
||||
const input = document.getElementById(inputId);
|
||||
const warning = document.getElementById(warningId);
|
||||
|
||||
['copy', 'paste', 'cut'].forEach(eventName => {
|
||||
input.addEventListener(eventName, (e) => {
|
||||
e.preventDefault();
|
||||
warning.classList.remove('d-none');
|
||||
|
||||
if (warning.timeout) clearTimeout(warning.timeout);
|
||||
warning.timeout = setTimeout(() => {
|
||||
warning.classList.add('d-none');
|
||||
}, 4000);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
showWarning('password', 'passwordCopyWarning');
|
||||
showWarning('password_confirm', 'confirmCopyWarning');
|
||||
});
|
||||
|
||||
function togglePassword(fieldId, iconContainer) {
|
||||
const input = document.getElementById(fieldId);
|
||||
const icon = iconContainer.querySelector('i');
|
||||
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
icon.classList.remove('fa-eye');
|
||||
icon.classList.add('fa-eye-slash');
|
||||
} else {
|
||||
input.type = 'password';
|
||||
icon.classList.remove('fa-eye-slash');
|
||||
icon.classList.add('fa-eye');
|
||||
}
|
||||
}
|
||||
|
||||
function validatePassword() {
|
||||
const password = document.getElementById('password').value;
|
||||
const passwordConfirm = document.getElementById('password_confirm').value;
|
||||
const passwordHelp = document.getElementById('passwordHelp');
|
||||
const confirmPasswordHelp = document.getElementById('confirmPasswordHelp');
|
||||
|
||||
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@\-=\+*#$%&!?])[A-Za-z\d@\-=\+*#$%&!?]{8,}$/;
|
||||
|
||||
let valid = true;
|
||||
|
||||
if (!passwordRegex.test(password)) {
|
||||
passwordHelp.classList.remove('d-none');
|
||||
valid = false;
|
||||
} else {
|
||||
passwordHelp.classList.add('d-none');
|
||||
}
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
confirmPasswordHelp.classList.remove('d-none');
|
||||
valid = false;
|
||||
} else {
|
||||
confirmPasswordHelp.classList.add('d-none');
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
+253
-258
@@ -1,259 +1,254 @@
|
||||
Grade,Unit,Unit Title,chapter
|
||||
1,1,Aqaid: Our Belief,Allahﷻ: Our Creator
|
||||
1,1,Aqaid: Our Belief,Islam
|
||||
1,1,Aqaid: Our Belief,Our Faith
|
||||
1,1,Aqaid: Our Belief,Nabi Muhammadﷺ
|
||||
1,1,Aqaid: Our Belief,The Qur’an
|
||||
1,2,Knowing Allahﷻ,Allahﷻ Loves Us
|
||||
1,2,Knowing Allahﷻ,Remembering Allahﷻ
|
||||
1,2,Knowing Allahﷻ,Allahﷻ Rewards Us
|
||||
1,3,Our Ibadat,Five Pillars of Islam
|
||||
1,3,Our Ibadat,Shahadah: The First Pillar
|
||||
1,3,Our Ibadat,Salah: The Second Pillar
|
||||
1,3,Our Ibadat,Zakat: The Third Pillar
|
||||
1,3,Our Ibadat,Fasting: The Fourth Pillar
|
||||
1,3,Our Ibadat,Hajj: The Fifth Pillar
|
||||
1,4,Messengers of Allah,Adam (A): The First Nabi
|
||||
1,4,Messengers of Allah,Nuh (A): Saved From Flood
|
||||
1,4,Messengers of Allah,Ibrahim (A): Never Listen to Shaitan
|
||||
1,4,Messengers of Allah,Musa (A): Challenging A Bad Ruler
|
||||
1,4,Messengers of Allah,Isa (A): A Great Nabi of Allahﷻ
|
||||
1,5,Other Basics of Islam,Angels: They Always Work for Allahﷻ
|
||||
1,5,Other Basics of Islam,Shaitan: Our Enemy
|
||||
1,5,Other Basics of Islam,Makkah and Madinah
|
||||
1,5,Other Basics of Islam,Eid: Two Festivals
|
||||
1,6,Akhlaq and Adab in Islam,Good Manners
|
||||
1,6,Akhlaq and Adab in Islam,Kindness and Sharing
|
||||
1,6,Akhlaq and Adab in Islam,Respect
|
||||
1,6,Akhlaq and Adab in Islam,Forgiveness
|
||||
1,6,Akhlaq and Adab in Islam,Thanking Allahﷻ
|
||||
2,1,The Creator—His Message,Allahﷻ: Our Creator
|
||||
2,1,The Creator—His Message,How Does Allahﷻ Create?
|
||||
2,1,The Creator—His Message,Allahﷻ: What Does He Do?
|
||||
2,1,The Creator—His Message,What Does Allahﷻ Not Do
|
||||
2,1,The Creator—His Message,The Qur’an
|
||||
2,1,The Creator—His Message,Hadith and Sunnah
|
||||
2,2,Our Ibadat,Shahadah: The First Pillar
|
||||
2,2,Our Ibadat,Salah: The Second Pillar
|
||||
2,2,Our Ibadat,Zakah: The Third Pillar
|
||||
2,2,Our Ibadat,Sawm: The Fourth Pillar
|
||||
2,2,Our Ibadat,Hajj: The Fifth Pillar
|
||||
2,2,Our Ibadat,Wudu: Keeping Our Bodies Clean
|
||||
2,3,Messengers of Allah,Ibrahim (A): A Friend of Allah
|
||||
2,3,Messengers of Allah,Yaqub (A) and Yusuf (A)
|
||||
2,3,Messengers of Allah,Musa (A) and Harun (A)
|
||||
2,3,Messengers of Allah,Yunus (A)
|
||||
2,3,Messengers of Allah,Nabi Muhammadﷺ
|
||||
2,4,Learning About Islam,Obey Allahﷻ Obey Rasulﷺ
|
||||
2,4,Learning About Islam,Day of Judgment
|
||||
2,4,Learning About Islam,Our Masjid
|
||||
2,4,Learning About Islam,Islamic Phrases
|
||||
2,4,Learning About Islam,Food that We May Eat
|
||||
2,5,Akhlaq and Adab in Islam,Truthfulness
|
||||
2,5,Akhlaq and Adab in Islam,Kindness
|
||||
2,5,Akhlaq and Adab in Islam,Respect
|
||||
2,5,Akhlaq and Adab in Islam,Responsibility
|
||||
2,5,Akhlaq and Adab in Islam,Obedience
|
||||
2,5,Akhlaq and Adab in Islam,Cleanliness
|
||||
2,5,Akhlaq and Adab in Islam,Honesty
|
||||
3,1,Knowing About Allah,What Does Allahﷻ Do?
|
||||
3,1,Knowing About Allah,What Allahﷻ Is and Is Not
|
||||
3,1,Knowing About Allah,Allahﷻ: The Most-Merciful
|
||||
3,1,Knowing About Allah,Allahﷻ: The Best Judge
|
||||
3,2,What Islam Says,We Are Muslims: We Have ‘Iman
|
||||
3,2,What Islam Says,What Does Allahﷻ Want Us to Do?
|
||||
3,2,What Islam Says,Hadith
|
||||
3,2,What Islam Says,Jinn
|
||||
3,2,What Islam Says,Muslims in North America
|
||||
3,2,What Islam Says,The Right Path: The Straight Path
|
||||
3,3,Why Do We Worship,Shahadah: Allahﷻ is One
|
||||
3,3,Why Do We Worship,Types of Salat
|
||||
3,3,Why Do We Worship,Why We Make Salat
|
||||
3,3,Why Do We Worship,Why Do We pay Zakat?
|
||||
3,3,Why Do We Worship,Why Do We Fast?
|
||||
3,3,Why Do We Worship,Why Do We Go for Hajj?
|
||||
3,4,Life of Nabi Muhammadﷺ,The Nabiﷺ in Makkah
|
||||
3,4,Life of Nabi Muhammadﷺ,The Nabiﷺ in Madinah
|
||||
3,4,Life of Nabi Muhammadﷺ,How Rasulullahﷺ Treated Others
|
||||
3,5,Messengers of Allah,Isma‘il (A) and Ishaq (A)
|
||||
3,5,Messengers of Allah,Dawud (A): A Nabi of Allahﷻ
|
||||
3,5,Messengers of Allah,‘Isa (A): A Nabi of Allahﷻ
|
||||
3,6,Akhlaq and Adab in Islam,Being Kind: A Virtue of the Believers
|
||||
3,6,Akhlaq and Adab in Islam,Forgiveness: A Good Quality
|
||||
3,6,Akhlaq and Adab in Islam,Good Deeds: A Duty of the Believers
|
||||
3,6,Akhlaq and Adab in Islam,Cleanliness: A Quality of Believers
|
||||
3,6,Akhlaq and Adab in Islam,A Muslim Family
|
||||
3,6,Akhlaq and Adab in Islam,Perseverance: Never Give Up
|
||||
3,6,Akhlaq and Adab in Islam,Punctuality: Doing Things on Time
|
||||
4,1,Knowing the Creator,Rewards of Allahﷻ: Everybody Receives Them
|
||||
4,1,Knowing the Creator,Discipline of Allahﷻ
|
||||
4,1,Knowing the Creator,Names of Allahﷻ
|
||||
4,1,Knowing the Creator,Books of Allahﷻ
|
||||
4,2,How Islam Changed Arabia,Pre-Islamic Arabia
|
||||
4,2,How Islam Changed Arabia,The Year of the Elephant
|
||||
4,2,How Islam Changed Arabia,Early Life of Muhammadﷺ
|
||||
4,2,How Islam Changed Arabia,Life Before Becoming a Nabi
|
||||
4,2,How Islam Changed Arabia,First Revelation
|
||||
4,2,How Islam Changed Arabia,Makkah Period
|
||||
4,2,How Islam Changed Arabia,Hijrat to Madinah
|
||||
4,2,How Islam Changed Arabia,Madinah Period
|
||||
4,3,The Rightly Guided Khalifah,Abu Bakr: The First Khalifah
|
||||
4,3,The Rightly Guided Khalifah,‘Umar ibn al-Khattab
|
||||
4,3,The Rightly Guided Khalifah,‘Uthman ibn ‘Affan
|
||||
4,3,The Rightly Guided Khalifah,‘Ali ibn Abu Talib
|
||||
4,4,The Messengers of Allah,Hud (A): Struggle to Guide People
|
||||
4,4,The Messengers of Allah,Salih (A): To Guide the Misguided
|
||||
4,4,The Messengers of Allah,Musa (A): His Life and Actions
|
||||
4,4,The Messengers of Allah,Sulaiman (A): A Humble King
|
||||
4,5,Fiqh of Salat,Preparation for Salat
|
||||
4,5,Fiqh of Salat,Requirements of Salat
|
||||
4,5,Fiqh of Salat,Mubtilat us-Salat
|
||||
4,5,Fiqh of Salat,How to Pray Behind an Imam
|
||||
4,6,General Islamic Topics,Compilers of Hadith
|
||||
4,6,General Islamic Topics,Shaitan’s Mode of Operation
|
||||
4,6,General Islamic Topics,Day of Judgment
|
||||
4,6,General Islamic Topics,Eid: Its Significance
|
||||
4,6,General Islamic Topics,Truthfulness: A Quality of Muslim
|
||||
4,6,General Islamic Topics,Perseverance: Keep on Trying
|
||||
5,1,"The Creator, His Message","Tawhid, Kafir, Kufr, Shirk, Nifaq"
|
||||
5,1,"The Creator, His Message",Why Should We Worship Allahﷻ?
|
||||
5,1,"The Creator, His Message",Revelation of the Qur’an
|
||||
5,1,"The Creator, His Message",Characteristics of the Messengers
|
||||
5,2,"The Battles, Developments",Pledges of ‘Aqabah
|
||||
5,2,"The Battles, Developments",The Battle of Badr
|
||||
5,2,"The Battles, Developments",The Battle of Uhud
|
||||
5,2,"The Battles, Developments",The Battle of the Trench
|
||||
5,2,"The Battles, Developments",The Treaty of Hudaibiyah
|
||||
5,2,"The Battles, Developments",Liberation of Makkah
|
||||
5,3,The Messengers of Allah,Adam (A): The Creation of Mankind
|
||||
5,3,The Messengers of Allah,Ibrahim (A) Debate with Polytheists
|
||||
5,3,The Messengers of Allah,Ibrahim (A): Plan Against Idols
|
||||
5,3,The Messengers of Allah,Luqman (A): A Wise Man’s Lifelong Teachings
|
||||
5,3,The Messengers of Allah,Yusuf (A): His Childhood
|
||||
5,3,The Messengers of Allah,Yusuf (A): His Righteousness
|
||||
5,3,The Messengers of Allah,Yusuf (A): Dream Comes True
|
||||
5,3,The Messengers of Allah,"Ayyub (A): Patience, Perseverance"
|
||||
5,3,The Messengers of Allah,"Zakariyyah (A), Yahya (A)"
|
||||
5,4,Islam in the World,Major Masajid in the World
|
||||
5,5,"Islamic Values, Teachings",Upholding Truth: A Duty for All Believers
|
||||
5,5,"Islamic Values, Teachings",Responsibility and Punctuality
|
||||
5,5,"Islamic Values, Teachings",My Mind My Body
|
||||
5,5,"Islamic Values, Teachings",Kindness and Forgiveness
|
||||
5,5,"Islamic Values, Teachings",The Middle Path: Ways to Avoid Two Extremes
|
||||
5,5,"Islamic Values, Teachings",Salat: Its Significance
|
||||
5,5,"Islamic Values, Teachings",Sawm: Its Significance
|
||||
5,5,"Islamic Values, Teachings",Zakat and Sadaqah: Similarities and Differences
|
||||
6,1,The Creator—His Message,Attributes of Allahﷻ
|
||||
6,1,The Creator—His Message,The Promise of Allahﷻ
|
||||
6,2,The Qur’an and Hadith,Objectives of the Qur’an?
|
||||
6,2,The Qur’an and Hadith,Compilation of the Qur’an
|
||||
6,2,The Qur’an and Hadith,Previous Scriptures and the Qur’an
|
||||
6,2,The Qur’an and Hadith,Compilation of Hadith
|
||||
6,3,Fundamentals of Deen,Importance of Shahadah
|
||||
6,3,Fundamentals of Deen,Khushu in Salat
|
||||
6,3,Fundamentals of Deen,Taqwa: A Quality of Believers
|
||||
6,4,Messengers of Allah,Nuh (A)
|
||||
6,4,Messengers of Allah,"Talut, Jalut, and Dawud (A)"
|
||||
6,4,Messengers of Allah,Dawud (A) and Sulaiman (A)
|
||||
6,4,Messengers of Allah,Musa (A) and Fir‘awn
|
||||
6,4,Messengers of Allah,Musa (A) and Khidir
|
||||
6,4,Messengers of Allah,‘Isa (A) and Maryam (ra)
|
||||
6,5,Some Prominent Muslimah,Khadijah (ra)
|
||||
6,5,Some Prominent Muslimah,‘Aishah (ra)
|
||||
6,5,Some Prominent Muslimah,Fatimah (ra)
|
||||
6,5,Some Prominent Muslimah,Some Prominent Muslimahs
|
||||
6,6,Knowledge Enrichment,Al-Qiyamah: The Awakening
|
||||
6,6,Knowledge Enrichment,Ruh and Nafs: An Overview
|
||||
6,6,Knowledge Enrichment,Angels and Jinn: An Overview
|
||||
6,6,Knowledge Enrichment,Shaitan: The Invisible Enemy
|
||||
6,7,The Current Society,My Friend Is Muslim Now
|
||||
6,7,The Current Society,Friendship
|
||||
6,7,The Current Society,Muslims Around the World
|
||||
6,7,The Current Society,People of Other Faith
|
||||
6,8,Developing Islamic Values,Greed and Dishonesty
|
||||
6,8,Developing Islamic Values,Avoiding Extravagance
|
||||
7,1,The Creator,Why Islam? what is Islam?
|
||||
7,1,The Creator,Belief in Allahﷻ
|
||||
7,1,The Creator,The Qur’an: Its Qualitative Names
|
||||
7,1,The Creator,Istighfar: Seeking Forgiveness of Allahﷻ
|
||||
7,1,The Creator,Allahﷻ: Angry or Kind
|
||||
7,2,Stories of the Messengers,Adam (A): Trial of the Messenger
|
||||
7,2,Stories of the Messengers,Life of Ibrahim (A)
|
||||
7,2,Stories of the Messengers,Sacrifice of Ibrahim (A)
|
||||
7,2,Stories of the Messengers,Lut (A): Message for Modern Societies
|
||||
7,2,Stories of the Messengers,Yusuf (A)—The Will to Overcome Temptation
|
||||
7,3,Stories from the Qur’an,The Companions of the Cave
|
||||
7,3,Stories from the Qur’an,Dhul Qurnain: Journey of a King
|
||||
7,3,Stories from the Qur’an,Effective Debate and Negotiation Styles in the Qur’an
|
||||
7,4,Two Companions,Abu Sufyan
|
||||
7,4,Two Companions,Khalid Ibn Walid (R)
|
||||
7,5,Knowledge Enrichment,The character of the Messengers
|
||||
7,5,Knowledge Enrichment,Rasulullahﷺ Marriages
|
||||
7,5,Knowledge Enrichment,Lailatul Qadr
|
||||
7,5,Knowledge Enrichment,Fasting During Ramadan
|
||||
7,5,Knowledge Enrichment,My Family is Muslim Now
|
||||
7,5,Knowledge Enrichment,Science in the Qur’an
|
||||
7,5,Knowledge Enrichment,Lessons from Past Civilizations
|
||||
7,6,Teachings of the Qur’an,Amr Bil Ma‘ruf
|
||||
7,6,Teachings of the Qur’an,Guard Your Tongue
|
||||
7,6,Teachings of the Qur’an,Islamic Greetings
|
||||
7,6,Teachings of the Qur’an,How to Achieve Success
|
||||
7,6,Teachings of the Qur’an,Permitted and Prohibited
|
||||
7,6,Teachings of the Qur’an,Types of Behavior Allahﷻ Loves
|
||||
8,1,Knowing the Creator,Divine Names
|
||||
8,1,Knowing the Creator,Sunan of Allahﷻ
|
||||
8,1,Knowing the Creator,Objectives of the Qur’an
|
||||
8,1,Knowing the Creator,Surah Hujurat: Its Teachings
|
||||
8,1,Knowing the Creator,True Piety: Analysis of Verse 2:177
|
||||
8,1,Knowing the Creator,Ayatul Kursi
|
||||
8,2,Knowing the Messengerﷺ,The Person Muhammadﷺ
|
||||
8,2,Knowing the Messengerﷺ,Farewell Pilgrimage
|
||||
8,2,Knowing the Messengerﷺ,Finality of Prophethood
|
||||
8,2,Knowing the Messengerﷺ,"Hadith: Collection, Classification"
|
||||
8,3,Challenges in Madinah,Hypocrites
|
||||
8,3,Challenges in Madinah,Banu Qaynuqa
|
||||
8,3,Challenges in Madinah,Banu Nadir
|
||||
8,3,Challenges in Madinah,Banu Qurayzah
|
||||
8,3,Challenges in Madinah,Mission to Tabuk
|
||||
8,4,Islamic Ethical Framework,Friends and Friendship
|
||||
8,4,Islamic Ethical Framework,Friendship With Non-Muslims
|
||||
8,4,Islamic Ethical Framework,Dating in Islam
|
||||
8,4,Islamic Ethical Framework,Hold Firmly the Rope of Allah
|
||||
8,4,Islamic Ethical Framework,Elements of Bad Life
|
||||
8,5,"Islamic Values, Teachings",Duties Toward Parents
|
||||
8,5,"Islamic Values, Teachings","Hope, Hopefulness, Hopelessness"
|
||||
8,5,"Islamic Values, Teachings",Trials in Life
|
||||
8,5,"Islamic Values, Teachings",Permitted and Prohibited Food
|
||||
8,5,"Islamic Values, Teachings",Performance of Hajj
|
||||
8,5,"Islamic Values, Teachings",Parables in the Qur’an
|
||||
8,6,Islam After the Rasul (S),Origin and History of Shi‘ah
|
||||
8,6,Islam After the Rasul (S),Ummayad Dynasty
|
||||
8,6,Islam After the Rasul (S),Abbasid Dynasty
|
||||
9,1,A Reflection on the Divine,Signs of Allahﷻ in Nature
|
||||
9,1,A Reflection on the Divine,Pondering the Qur’an
|
||||
9,1,A Reflection on the Divine,Preservation and Compilation of the Qur’an
|
||||
9,1,A Reflection on the Divine,Ibadat—Easy Ways to Do It
|
||||
9,1,A Reflection on the Divine,Surah Baqarah—Statement of Faith and Commitment
|
||||
9,2,Islam and Muslim,Why Human Beings Are Superior
|
||||
9,2,Islam and Muslim,Life Cycle of Truth
|
||||
9,2,Islam and Muslim,Is Islam a Violent Religion?
|
||||
9,2,Islam and Muslim,"Present Life: Vanity, Deception, Play"
|
||||
9,2,Islam and Muslim,Shariah
|
||||
9,2,Islam and Muslim,Justice in Islam
|
||||
9,3,Ethical Standard in Islam,Choices We Make
|
||||
9,3,Ethical Standard in Islam,Peer Pressure
|
||||
9,3,Ethical Standard in Islam,Islamic Perspective on Dating
|
||||
9,3,Ethical Standard in Islam,Indecency
|
||||
9,3,Ethical Standard in Islam,Alcohol and Gambling
|
||||
9,3,Ethical Standard in Islam,Permitted and Prohibited Food
|
||||
9,3,Ethical Standard in Islam,Food of the People of the Book
|
||||
9,3,Ethical Standard in Islam,Let Ramadan Bring The Best in Us
|
||||
9,4,Essays on Rasulullahﷺ,Khadijah (ra)
|
||||
9,4,Essays on Rasulullahﷺ,Rasulullahﷺ Multiple Marriages
|
||||
9,4,Essays on Rasulullahﷺ,Marriage to Zainab (ra)
|
||||
9,4,Essays on Rasulullahﷺ,Rasulullahﷺ: A Great Army General
|
||||
9,4,Essays on Rasulullahﷺ,Prophecy of Muhammadﷺ in the Bible
|
||||
9,4,Essays on Rasulullahﷺ,Allegations Against Rasulullahﷺ
|
||||
9,5,Faith-Based Wealth Building,Faith Based Wealth Building
|
||||
9,5,Faith-Based Wealth Building,"Earn, Save, Spend, Invest"
|
||||
9,5,Faith-Based Wealth Building,Let Investment Work for You
|
||||
1,1,Aqaid: Our Belief,1. Allah: Our Creator
|
||||
1,1,Aqaid: Our Belief,2. Islam
|
||||
1,1,Aqaid: Our Belief,3. Our Faith
|
||||
1,1,Aqaid: Our Belief,4. Nabi Muhammad (s)
|
||||
1,1,Aqaid: Our Belief,5. The Qur’an
|
||||
1,2,Knowing Allah,6. Allah Loves Us
|
||||
1,2,Knowing Allah,7. Remembering Allah
|
||||
1,2,Knowing Allah,8. Allah Rewards Us
|
||||
1,3,Our Ibadat,9. Five Pillars of Islam
|
||||
1,3,Our Ibadat,10. Shahadah: The First Pillar
|
||||
1,3,Our Ibadat,11. Salat: The Second Pillar
|
||||
1,3,Our Ibadat,12. Zakat: The Third Pillar
|
||||
1,3,Our Ibadat,13. Fasting: The Fourth Pillar
|
||||
1,3,Our Ibadat,14. Hajj: The Fifth Pillar
|
||||
1,4,Messengers of Allah,15. Adam (A): The First Nabi
|
||||
1,4,Messengers of Allah,16. Nuh (A): Saved From the Great Flood
|
||||
1,4,Messengers of Allah,17. Ibrahim (A): Never Listen to Shaitan
|
||||
1,4,Messengers of Allah,18. Musa (A): Challenging a Bad Ruler
|
||||
1,4,Messengers of Allah,19. Isa (A): A Great Nabi of Allah
|
||||
1,5,Other Basics of Islam,20. Angels: They Always Work for Allah
|
||||
1,5,Other Basics of Islam,21. Shaitan: Our Enemy
|
||||
1,5,Other Basics of Islam,22. Makkah and Madinah
|
||||
1,5,Other Basics of Islam,23. Eid: Two Festivals
|
||||
1,6,Akhlaq and Adab in Islam,24. Good Manners
|
||||
1,6,Akhlaq and Adab in Islam,25. Kindness and Sharing
|
||||
1,6,Akhlaq and Adab in Islam,26. Respect
|
||||
1,6,Akhlaq and Adab in Islam,27. Forgiveness
|
||||
1,6,Akhlaq and Adab in Islam,28. Thanking Allah
|
||||
2,1,The Creator and His Message,1. Allah: Our Creator
|
||||
2,1,The Creator and His Message,2. How Does Allah Create?
|
||||
2,1,The Creator and His Message,3. What Does Allah Do?
|
||||
2,1,The Creator and His Message,4, Allah: What Does He Not Do
|
||||
2,1,The Creator and His Message,5. The Qur’an
|
||||
2,1,The Creator and His Message,6. Hadith and Sunnah
|
||||
2,2,Our Ibadat,7. Shahadah: The First Pillar
|
||||
2,2,Our Ibadat,8. Salat: The Second Pillar
|
||||
2,2,Our Ibadat,9. Zakah: The Third Pillar
|
||||
2,2,Our Ibadat,10. Sawm: The Fourth Pillar
|
||||
2,2,Our Ibadat,11. Hajj: The Fifth Pillar
|
||||
2,2,Our Ibadat,12. Wudu: Cleaning Before Salat
|
||||
2,3,The Messengers of Allah,13. Ibrahim (A): A Friend of Allah
|
||||
2,3,The Messengers of Allah,14. Yaqub (A) and Yusuf (A)
|
||||
2,3,The Messengers of Allah,15. Musa (A) and Harun (A)
|
||||
2,3,The Messengers of Allah,16. Yunus (A)
|
||||
2,3,The Messengers of Allah,17. Nabi Muhammad ﷺ
|
||||
2,4,Learning About Islam,18. Obey Allah, Obey Rasul ﷺ
|
||||
2,4,Learning About Islam,19. Day of Judgment
|
||||
2,4,Learning About Islam,20. Our Masjid
|
||||
2,4,Learning About Islam,21. Islamic Phrases
|
||||
2,4,Learning About Islam,22. Food that We May Eat
|
||||
2,5,Akhlaq and Adab in Islam,23. Truthfulness
|
||||
2,5,Akhlaq and Adab in Islam,24. Kindness
|
||||
2,5,Akhlaq and Adab in Islam,25. Respect
|
||||
2,5,Akhlaq and Adab in Islam,26. Responsibility
|
||||
2,5,Akhlaq and Adab in Islam,27. Obedience
|
||||
2,5,Akhlaq and Adab in Islam,28. Cleanliness
|
||||
2,5,Akhlaq and Adab in Islam,29. Honesty
|
||||
3,1,Knowing About Allāh ﷺ,1. Who Is Allāh ﷺ?
|
||||
3,1,Knowing About Allāh ﷺ,2. What Allāh ﷺ Is and Is Not
|
||||
3,1,Knowing About Allāh ﷺ,3. Allāh ﷺ: The Most-Merciful, Most-Rewarding
|
||||
3,1,Knowing About Allāh ﷺ,4. Allāh ﷺ: The Best Judge
|
||||
3,1,Knowing About Allāh ﷺ,5. What Does Allāh ﷺ Want Us to Do?
|
||||
3,2,Teachings of Islam,6. We Are Muslims: We Have ‘Īmān
|
||||
3,2,Teachings of Islam,7. Belief in the Qur’ān
|
||||
3,2,Teachings of Islam,8. Belief in the Messengers
|
||||
3,2,Teachings of Islam,9. Hadīth and Sunnah
|
||||
3,2,Teachings of Islam,10. Jinn
|
||||
3,2,Teachings of Islam,11. Muslims in North America
|
||||
3,2,Teachings of Islam,12. The Straight Path: The Right Path
|
||||
3,3,Nabi Muhammad ﷺ,13. Kindness of Rasūlullāh ﷺ
|
||||
3,3,Nabi Muhammad ﷺ,14. How Rasūlullāh ﷺ Treated Others
|
||||
3,3,Nabi Muhammad ﷺ,15. Our Relationship With Rasūlullāh ﷺ
|
||||
3,4,Messengers of Allāh ﷺ,16. Ismā‘īl (A) and Ishāq (A): Nabi of Allāh ﷺ
|
||||
3,4,Messengers of Allāh ﷺ,17. Shu‘aib (A): A Nabi of Allāh ﷺ
|
||||
3,4,Messengers of Allāh ﷺ,18. Dāwūd (A): A Nabi of Allāh ﷺ
|
||||
3,4,Messengers of Allāh ﷺ,19. ‘Īsā (A): A Nabi of Allāh ﷺ
|
||||
3,5,Learning About Islam,20. The Ka‘bah
|
||||
3,5,Learning About Islam,21. Masjid an-Nabawī: The Nabi’s Masjid
|
||||
3,5,Learning About Islam,22. Bilāl ibn Rabāh
|
||||
3,5,Learning About Islam,23. Zaid ibn Hārithah
|
||||
3,6,Akhlaq and Adab in Islam,24. Ways To Be a Good Person
|
||||
3,6,Akhlaq and Adab in Islam,25. Kindness: A Virtue of the Believers
|
||||
3,6,Akhlaq and Adab in Islam,26. Forgiveness: A Quality of the Believers
|
||||
3,6,Akhlaq and Adab in Islam,27. Good Deeds: A Duty of the Believers
|
||||
3,6,Akhlaq and Adab in Islam,28. Perseverance: Never Give Up
|
||||
3,6,Akhlaq and Adab in Islam,29. Punctuality: Doing Things on Time
|
||||
4,1,Knowing the Creator,1. Rewards of Allah: Everybody Receives Them
|
||||
4,1,Knowing the Creator,1. Discipline of Allah: Because He Loves Us
|
||||
4,1,Knowing the Creator,3. Names of Allah
|
||||
4,1,Knowing the Creator,4. Books of Allah
|
||||
4,2,How Islam Changed Arabia,5. Pre-Islamic Arabia: Age of Ignorance
|
||||
4,2,How Islam Changed Arabia,6. The Year of the Elephant
|
||||
4,2,How Islam Changed Arabia,7. Early Life of Muhammad ﷺ
|
||||
4,2,How Islam Changed Arabia,8. Life Before Becoming a Nabi
|
||||
4,2,How Islam Changed Arabia,9. First Revelation
|
||||
4,2,How Islam Changed Arabia,10. Makkah Period: The Early Years of the Muslims
|
||||
4,2,How Islam Changed Arabia,11. Hijrat to Madinah: The Migration that Shaped History
|
||||
4,2,How Islam Changed Arabia,12. Madinah Period: Islam Prospers
|
||||
4,3,The Rightly Guided Khalifah,13. Abū Bakr (R): The First Khalifah
|
||||
4,3,The Rightly Guided Khalifah,14. ‘Umar al-Khaṭṭāb (R): The Second Khalifah
|
||||
4,3,The Rightly Guided Khalifah,15. ‘Uthman Ibn ‘Affān (R): The Third Khalifah
|
||||
4,3,The Rightly Guided Khalifah,16. ‘Ali Ibn Abu Ṭālib (R): The Fourth Khalifah
|
||||
4,4,Messengers of Allah,17. Hūd (A): Struggle to Guide Mankind
|
||||
4,4,Messengers of Allah,18. Ṣāliḥ (A): Struggle to Guide the Misguided
|
||||
4,4,Messengers of Allah,19. Mūsā (A): His Life and Achievements
|
||||
4,4,Messengers of Allah,20. Sulaimān (A): A King and a Servant of Allah ﷺ
|
||||
4,5,Fiqh of Salat,21. Preparation for Salat
|
||||
4,5,Fiqh of Salat,22. The Requirements of Salat
|
||||
4,5,Fiqh of Salat,23. Mubṭilāt-us-Salāt: Things that Invalidate Salāt
|
||||
4,5,Fiqh of Salat,24. How to Pray Behind an Imām
|
||||
4,6,General Islamic Topics,25. Compilers of Hadīth
|
||||
4,6,General Islamic Topics,26. Shaitan’s Mode of Operation
|
||||
4,6,General Islamic Topics,27. Day of Judgment: The Day of Ultimate Justice
|
||||
4,6,General Islamic Topics,28. ‘Eid: Significance of the Festivities
|
||||
4,6,General Islamic Topics,29. Truthfulness: An Important Quality for Muslims
|
||||
4,6,General Islamic Topics,30. Perseverance: Keep on Trying
|
||||
5,1,The Creator,1. His Message, and His Messengers,Tawhid, Kafir, Kufr, Shirk, Nifaq
|
||||
5,1,The Creator,2. His Message, and His Messengers,Why Should We Worship Allah?
|
||||
5,1,The Creator,3. His Message, and His Messengers,The Revelation of the Qur’an
|
||||
5,1,The Creator,4. His Message, and His Messengers,Characteristics of the Messengers
|
||||
5,2,The Battles and Other Developments,5. Pledges of ‘Aqabah: Invitation to Migrate
|
||||
5,2,The Battles and Other Developments,6. The Battle of Badr: Allah Supports the Righteous
|
||||
5,2,The Battles and Other Developments,7. The Battle of Uhud: Obey Allah and Obey the Rasul ﷺ
|
||||
5,2,The Battles and Other Developments,8. The Battle of the Trench: A Bloodless Battle
|
||||
5,2,The Battles and Other Developments,9. The Treaty of Hudaibiyah: A Clear Victory
|
||||
5,2,The Battles and Other Developments,10. Liberation of Makkah: A Bloodless Victory
|
||||
5,3,Stories of the Messengers of Allah,11. Adam (A): The Creation of Human Beings
|
||||
5,3,Stories of the Messengers of Allah,12. Ibrahim (A): His Debate with the Polytheists
|
||||
5,3,Stories of the Messengers of Allah,13. Ibrahim (A): His Plan Against the Idols
|
||||
5,3,Stories of the Messengers of Allah,14. Luqmān (A): A Wise Man’s Lifelong Advice
|
||||
5,3,Stories of the Messengers of Allah,15. Yūsuf (A): His Childhood and Life in Aziz’s Home
|
||||
5,3,Stories of the Messengers of Allah,16. Yūsuf (A): Standing Up for Righteousness
|
||||
5,3,Stories of the Messengers of Allah,17. Yūsuf (A): A Childhood Dream Comes True
|
||||
5,4,Islam in The World,20. Major Masājid in the World
|
||||
5,5,Islamic Values and Teachings,21. Upholding Truth: A Duty of All Believers
|
||||
5,5,Islamic Values and Teachings,22. Responsibility and Punctuality
|
||||
5,5,Islamic Values and Teachings,23. My Mind, My Body: The Body is a Mirror of the Mind
|
||||
5,5,Islamic Values and Teachings,24. Kindness and Forgiveness
|
||||
5,5,Islamic Values and Teachings,25. The Middle Path: Ways to Avoid the Two Extremes
|
||||
5,5,Islamic Values and Teachings,26. Salat: Its Significance
|
||||
5,5,Islamic Values and Teachings,27. Sawm: Its Significance
|
||||
5,5,Islamic Values and Teachings,28. Zakat and Sadaqah: Similarities and Differences
|
||||
6,1,The Creator,1. His Message, and His Messengers,Tawhid, Kafir, Kufr, Shirk, Nifaq
|
||||
6,1,The Creator,2. His Message, and His Messengers,Why Should We Worship Allah?
|
||||
6,1,The Creator,3. His Message, and His Messengers,The Revelation of the Qur’an
|
||||
6,1,The Creator,4. His Message, and His Messengers,Characteristics of the Messengers
|
||||
6,2,The Battles and Other Developments,5. Pledges of ‘Aqabah: Invitation to Migrate
|
||||
6,2,The Battles and Other Developments,6. The Battle of Badr: Allah Supports the Righteous
|
||||
6,2,The Battles and Other Developments,7. The Battle of Uhud: Obey Allah and Obey the Rasul ﷺ
|
||||
6,2,The Battles and Other Developments,8. The Battle of the Trench: A Bloodless Battle
|
||||
6,2,The Battles and Other Developments,9. The Treaty of Hudaibiyah: A Clear Victory
|
||||
6,2,The Battles and Other Developments,10. Liberation of Makkah: A Bloodless Victory
|
||||
6,3,Stories of the Messengers of Allah,11. Adam (A): The Creation of Human Beings
|
||||
6,3,Stories of the Messengers of Allah,12. Ibrahim (A): His Debate with the Polytheists
|
||||
6,3,Stories of the Messengers of Allah,13. Ibrahim (A): His Plan Against the Idols
|
||||
6,3,Stories of the Messengers of Allah,14. Luqmān (A): A Wise Man’s Lifelong Advice
|
||||
6,3,Stories of the Messengers of Allah,15. Yūsuf (A): His Childhood and Life in Aziz’s Home
|
||||
6,3,Stories of the Messengers of Allah,16. Yūsuf (A): Standing Up for Righteousness
|
||||
6,3,Stories of the Messengers of Allah,17. Yūsuf (A): A Childhood Dream Comes True
|
||||
6,4,Islam in The World,20. Major Masājid in the World
|
||||
6,5,Islamic Values and Teachings,21. Upholding Truth: A Duty of All Believers
|
||||
6,5,Islamic Values and Teachings,22. Responsibility and Punctuality
|
||||
6,5,Islamic Values and Teachings,23. My Mind, My Body: The Body is a Mirror of the Mind
|
||||
6,5,Islamic Values and Teachings,24. Kindness and Forgiveness
|
||||
6,5,Islamic Values and Teachings,25. The Middle Path: Ways to Avoid the Two Extremes
|
||||
6,5,Islamic Values and Teachings,26. Salat: Its Significance
|
||||
6,5,Islamic Values and Teachings,27. Sawm: Its Significance
|
||||
6,5,Islamic Values and Teachings,28. Zakat and Sadaqah: Similarities and Differences
|
||||
7,1,The Creator,1. Why Islam? What is Islam?
|
||||
7,1,The Creator,2. Belief in Allah
|
||||
7,1,The Creator,3. The Qur’an: Its Qualitative Names
|
||||
7,1,The Creator,4. Istighfār: Seeking Forgiveness and Protection
|
||||
7,1,The Creator,5. Allah: Angry or Kind?
|
||||
7,2,Stories of the Messengers,6. Ādam (A): The Trial of the First Messenger
|
||||
7,2,Stories of the Messengers,7. The Life of Ibrāhīm (A): Beginning a Nation
|
||||
7,2,Stories of the Messengers,8. The Sacrifice of Ibrāhīm (A)
|
||||
7,2,Stories of the Messengers,9. Lūt (A): A Message for Modern Societies
|
||||
7,2,Stories of the Messengers,10. Yūsuf (A): The Will to Overcome Temptation
|
||||
7,3,Stories from the Qur’an,11. The Companions of the Cave
|
||||
7,3,Stories from the Qur’an,12. Dhu al-Qarnain: The Journey of a King
|
||||
7,3,Stories from the Qur’an,13. Effective Debate and Negotiation Styles in the Qur’an
|
||||
7,4,Two Companions Who Shaped Islam,14. Abū Sufyān: His Life and Achievements
|
||||
7,4,Two Companions Who Shaped Islam,15. Khālid Ibn al-Walīd: The “Sword of Allah”
|
||||
7,5,Knowledge Enrichment,16. Character of the Messengers
|
||||
7,5,Knowledge Enrichment,17. Rasūlullāh’s Marriages
|
||||
7,5,Knowledge Enrichment,18. Lailatul Qadr: The Night of Majesty
|
||||
7,5,Knowledge Enrichment,19. Fasting During Ramadan: The Month of Benefits
|
||||
7,5,Knowledge Enrichment,20. My Family is Muslim Now
|
||||
7,5,Knowledge Enrichment,21. Science in the Qur’an
|
||||
7,5,Knowledge Enrichment,22. Lessons From Past Civilizations
|
||||
7,6,Akhlaq and Adab in Islam,23. Amr Bil Ma’rūf: Enjoin Good Deeds
|
||||
7,6,Akhlaq and Adab in Islam,24. Guard Your Tongue: Think Before You Speak
|
||||
7,6,Akhlaq and Adab in Islam,25. Islamic Greeting: Wishing Peace
|
||||
7,6,Akhlaq and Adab in Islam,26. How to Achieve Success
|
||||
7,6,Akhlaq and Adab in Islam,27. Permitted and Prohibited
|
||||
7,6,Akhlaq and Adab in Islam,28. Types of Behavior Allah Loves
|
||||
8,1,Knowing the Creator,1. Divine Names
|
||||
8,1,Knowing the Creator,2. Sunan of Allah
|
||||
8,1,Knowing the Creator,3. Objectives of the Qur’an
|
||||
8,1,Knowing the Creator,4. Lessons from Sūrah al-Hujurāt
|
||||
8,1,Knowing the Creator,5. True Piety: A Synthesis of Belief, Practice, and Conduct
|
||||
8,1,Knowing the Creator,6. Āyatul Kursi: The Throne Verse
|
||||
8,2,Knowing the Messenger ﷺ,7. The Person Muhammad ﷺ
|
||||
8,2,Knowing the Messenger ﷺ,8. Farewell Pilgrimage
|
||||
8,2,Knowing the Messenger ﷺ,9. Finality of Prophethood
|
||||
8,2,Knowing the Messenger ﷺ,10. Hadith: Collection and Classification
|
||||
8,3,Challenges in Madinah,11. Hypocrites
|
||||
8,3,Challenges in Madinah,12. Banu Qaynuqa: Threat Within Madinah
|
||||
8,3,Challenges in Madinah,13. Banu Nadir: Treachery Within Madinah
|
||||
8,3,Challenges in Madinah,14. Banu Qurayzah
|
||||
8,3,Challenges in Madinah,15. Mission to Tabūk: A Test of Steadfastness
|
||||
8,4,Islamic Ethical Framework,16. Friends and Friendship: Who is a Good Friend?
|
||||
8,4,Islamic Ethical Framework,17. Friendship With Non-Muslims
|
||||
8,4,Islamic Ethical Framework,18. Dating: How Islam Views the Practice
|
||||
8,4,Islamic Ethical Framework,19. Hold Firmly the Rope of Allah
|
||||
8,4,Islamic Ethical Framework,20. Elements of a Bad Life
|
||||
8,5,Islamic Values and Teachings,21. Duties Towards Parents
|
||||
8,5,Islamic Values and Teachings,22. Hope, Hopefulness, Hopelessness
|
||||
8,5,Islamic Values and Teachings,23. Trials in Life: Everyone Will Experience Them
|
||||
8,5,Islamic Values and Teachings,24. Permitted and Prohibited Food
|
||||
8,5,Islamic Values and Teachings,25. Performance of Hajj
|
||||
8,5,Islamic Values and Teachings,26. Parables in the Qur’an
|
||||
8,6,Islam After the Messenger ﷺ,27. Early History of Shi‘ah Muslims
|
||||
8,6,Islam After the Messenger ﷺ,28. Umayyad Dynasty
|
||||
8,6,Islam After the Messenger ﷺ,29. Abbasid Dynasty
|
||||
9,1,A Reflection on the Divine,1. Signs of Allahﷻ in Nature
|
||||
9,1,A Reflection on the Divine,2. Pondering the Qur’an
|
||||
9,1,A Reflection on the Divine,3. Preservation and Compilation of the Qur’an
|
||||
9,1,A Reflection on the Divine,4. Ibadat—Easy Ways to Do It
|
||||
9,1,A Reflection on the Divine,5. Surah Baqarah—Statement of Faith and Commitment
|
||||
9,2,Islam and Muslim,6. Why Human Beings Are Superior
|
||||
9,2,Islam and Muslim,7. Life Cycle of Truth
|
||||
9,2,Islam and Muslim,8. Is Islam a Violent Religion?
|
||||
9,2,Islam and Muslim,9. Present Life: Vanity, Deception, Play
|
||||
9,2,Islam and Muslim,10. Shariah
|
||||
9,2,Islam and Muslim,11. Justice in Islam
|
||||
9,3,Ethical Standard in Islam,12. Choices We Make
|
||||
9,3,Ethical Standard in Islam,13. Peer Pressure
|
||||
9,3,Ethical Standard in Islam,14. Islamic Perspective on Dating
|
||||
9,3,Ethical Standard in Islam,15. Indecency
|
||||
9,3,Ethical Standard in Islam,16. Alcohol and Gambling
|
||||
9,3,Ethical Standard in Islam,17. Permitted and Prohibited Food
|
||||
9,3,Ethical Standard in Islam,18. Food of the People of the Book
|
||||
9,3,Ethical Standard in Islam,19. Let Ramadan Bring The Best in Us
|
||||
9,4,Essays on Rasulullahﷺ,20. Khadijah (ra)
|
||||
9,4,Essays on Rasulullahﷺ,21. Rasulullahﷺ Multiple Marriages
|
||||
9,4,Essays on Rasulullahﷺ,22. Marriage to Zainab (ra)
|
||||
9,4,Essays on Rasulullahﷺ,23. Rasulullahﷺ: A Great Army General
|
||||
9,4,Essays on Rasulullahﷺ,24. Prophecy of Muhammadﷺ in the Bible
|
||||
9,4,Essays on Rasulullahﷺ,25. Allegations Against Rasulullahﷺ
|
||||
9,5,Faith-Based Wealth Building,26. Faith Based Wealth Building
|
||||
9,5,Faith-Based Wealth Building,27. Earn, Save, Spend, Invest
|
||||
9,5,Faith-Based Wealth Building,28. Let Investment Work for You
|
||||
|
||||
|
@@ -1,45 +0,0 @@
|
||||
Grade,Surah
|
||||
1,Al-Fatihah
|
||||
1,An-Nas
|
||||
1,Al-Falaq
|
||||
1,Al-Ikhlas
|
||||
2,Al-Masad
|
||||
2,An-Nasr
|
||||
2,Al-Kafirun
|
||||
2,Al-Kawthar
|
||||
2,Al-Ma'un
|
||||
3,Quraysh
|
||||
3,Al-Fil
|
||||
3,Al-Humazah
|
||||
3,Al-'Asr
|
||||
3,At-Takathur
|
||||
4,Al-Qari'ah
|
||||
4,Al-'Adiyat
|
||||
4,Az-Zalzalah
|
||||
4,Al-Bayyinah
|
||||
4,Al-Qadr
|
||||
5,Al-'Alaq
|
||||
5,At-Tin
|
||||
5,Ash-Sharh
|
||||
5,Ad-Duhaa
|
||||
5,Al-Layl
|
||||
6,Ash-Shams
|
||||
6,Al-Balad
|
||||
6,Al-Fajr
|
||||
6,Al-Ghashiyah
|
||||
6,Al-A'la
|
||||
7,At-Tariq
|
||||
7,Al-Buruj
|
||||
7,Al-Inshiqaq
|
||||
7,Al-Mutaffifin
|
||||
7,Al-Infitar
|
||||
8,At-Takwir
|
||||
8,Abasa
|
||||
8,Al-Mursalat
|
||||
8,An-Naba
|
||||
9,Al-Mulk
|
||||
9,Al-Qalam
|
||||
9,Al-Haqqah
|
||||
9,Al-Ma'arij
|
||||
9,Nuh
|
||||
9,Al-Jinn
|
||||
|
Binary file not shown.
Reference in New Issue
Block a user