Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
Clubphp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Clubphp
Commits
8479c580
Commit
8479c580
authored
Apr 07, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 3 files via Son of Anton
parent
c6199662
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
91 additions
and
129 deletions
+91
-129
CSRFMiddleware.php
app/Middleware/CSRFMiddleware.php
+2
-7
MemberApiController.php
app/Modules/Members/Controllers/MemberApiController.php
+69
-34
MemberSearchService.php
app/Modules/Members/Services/MemberSearchService.php
+20
-88
No files found.
app/Middleware/CSRFMiddleware.php
View file @
8479c580
...
...
@@ -12,25 +12,21 @@ class CSRFMiddleware implements MiddlewareInterface
{
public
function
handle
(
Request
$request
,
callable
$next
)
:
Response
{
// Only validate on state-changing methods
$method
=
strtoupper
(
$request
->
method
());
if
(
in_array
(
$method
,
[
'GET'
,
'HEAD'
,
'OPTIONS'
]))
{
return
$next
(
$request
);
}
// Skip CSRF for API routes that use token auth
$path
=
$request
->
path
();
if
(
str_starts_with
(
$path
,
'/api/'
)
&&
$request
->
bearerToken
())
{
return
$next
(
$request
);
}
// Get token from POST data or header
$token
=
$request
->
post
(
'_csrf_token'
,
''
)
?:
$request
->
header
(
'X-CSRF-TOKEN'
)
?:
''
;
if
(
!
CSRF
::
validate
(
$token
))
{
// If AJAX request, return JSON error
if
(
$request
->
isAjax
()
||
$request
->
isJson
())
{
$response
=
new
Response
();
return
$response
->
json
([
...
...
@@ -39,7 +35,6 @@ class CSRFMiddleware implements MiddlewareInterface
],
419
);
}
// For regular form submissions, show error page
http_response_code
(
419
);
echo
'<!DOCTYPE html><html lang="ar" dir="rtl"><head><meta charset="UTF-8"><title>419</title>'
.
'<style>body{font-family:Cairo,Arial,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#F3F4F6;}'
...
...
@@ -54,8 +49,8 @@ class CSRFMiddleware implements MiddlewareInterface
exit
;
}
//
Token valid — regenerate for next request
CSRF
::
regenerate
();
//
Do NOT regenerate token — keep it valid for the entire session
// This allows multiple AJAX calls and form submissions without breaking
return
$next
(
$request
);
}
...
...
app/Modules/Members/Controllers/MemberApiController.php
View file @
8479c580
...
...
@@ -6,6 +6,7 @@ namespace App\Modules\Members\Controllers;
use
App\Core\Controller
;
use
App\Core\Request
;
use
App\Core\Response
;
use
App\Core\App
;
use
App\Modules\Members\Services\NationalIdParser
;
use
App\Modules\Members\Services\MemberSearchService
;
...
...
@@ -16,57 +17,91 @@ class MemberApiController extends Controller
$nid
=
trim
((
string
)
$request
->
post
(
'national_id'
,
''
));
if
(
$nid
===
''
)
{
return
$this
->
json
([
'error'
=>
'الرقم القومي مطلوب'
],
422
);
return
$this
->
json
([
'error'
=>
'الرقم القومي مطلوب'
,
'parsed'
=>
[
'is_valid'
=>
false
,
'errors'
=>
[
'الرقم القومي مطلوب'
]]]
);
}
$
result
=
NationalIdParser
::
parse
(
$nid
);
$
parsed
=
NationalIdParser
::
parse
(
$nid
);
//
Also check duplicate
//
Check for duplicates across all tables
$duplicate
=
null
;
if
(
$result
[
'is_valid'
])
{
$dup
=
NationalIdParser
::
checkDuplicate
(
$nid
);
if
(
$parsed
[
'is_valid'
])
{
$db
=
App
::
getInstance
()
->
db
();
// Check members
try
{
$dup
=
$db
->
selectOne
(
"SELECT id, full_name_ar, membership_number FROM members WHERE national_id = ? AND is_archived = 0"
,
[
$nid
]
);
if
(
$dup
)
{
$duplicate
=
[
'type'
=>
'member'
,
'id'
=>
(
int
)
$dup
[
'id'
],
'full_name_ar'
=>
$dup
[
'full_name_ar'
],
'membership_number'
=>
$dup
[
'membership_number'
]
??
''
,
];
}
}
catch
(
\Throwable
$e
)
{}
// Check spouses
if
(
!
$duplicate
)
{
try
{
$dup
=
$db
->
selectOne
(
"SELECT s.id, s.full_name_ar, s.member_id, m.membership_number
FROM spouses s JOIN members m ON m.id = s.member_id
WHERE s.national_id = ? AND s.is_archived = 0"
,
[
$nid
]
);
if
(
$dup
)
{
$duplicate
=
[
'type'
=>
'spouse'
,
'id'
=>
(
int
)
$dup
[
'id'
],
'full_name_ar'
=>
$dup
[
'full_name_ar'
],
'membership_number'
=>
$dup
[
'membership_number'
]
??
''
,
'member_id'
=>
(
int
)
$dup
[
'member_id'
],
];
}
}
catch
(
\Throwable
$e
)
{}
}
// Check children
if
(
!
$duplicate
)
{
try
{
$dup
=
$db
->
selectOne
(
"SELECT c.id, c.full_name_ar, c.member_id, m.membership_number
FROM children c JOIN members m ON m.id = c.member_id
WHERE c.national_id = ? AND c.is_archived = 0"
,
[
$nid
]
);
if
(
$dup
)
{
$duplicate
=
[
'type'
=>
'child'
,
'id'
=>
(
int
)
$dup
[
'id'
],
'full_name_ar'
=>
$dup
[
'full_name_ar'
],
'membership_number'
=>
$dup
[
'membership_number'
],
'status'
=>
$dup
[
'status'
],
'is_archived'
=>
(
bool
)
$dup
[
'is_archived'
],
'membership_number'
=>
$dup
[
'membership_number'
]
??
''
,
'member_id'
=>
(
int
)
$dup
[
'member_id'
],
];
}
}
catch
(
\Throwable
$e
)
{}
}
}
return
$this
->
json
([
'parsed'
=>
$
result
,
'parsed'
=>
$
parsed
,
'duplicate'
=>
$duplicate
,
]);
}
public
function
quickS
earch
(
Request
$request
)
:
Response
public
function
s
earch
(
Request
$request
)
:
Response
{
$query
=
trim
((
string
)
$request
->
post
(
'q'
,
''
));
if
(
mb_strlen
(
$query
)
<
2
)
{
return
$this
->
json
([
'results'
=>
[]]);
}
$results
=
MemberSearchService
::
quickSearch
(
$query
,
10
);
return
$this
->
json
([
'results'
=>
$results
]);
}
public
function
checkDuplicate
(
Request
$request
)
:
Response
{
$nid
=
trim
((
string
)
$request
->
post
(
'national_id'
,
''
));
$excludeId
=
$request
->
post
(
'exclude_id'
,
null
);
if
(
$nid
===
''
)
{
return
$this
->
json
([
'exists'
=>
false
]);
if
(
$query
===
''
||
mb_strlen
(
$query
)
<
2
)
{
return
$this
->
json
([
'results'
=>
[]]);
}
$
dup
=
NationalIdParser
::
checkDuplicate
(
$nid
,
$excludeId
?
(
int
)
$excludeId
:
null
);
$
results
=
MemberSearchService
::
search
(
$query
,
20
);
return
$this
->
json
([
'exists'
=>
$dup
!==
null
,
'member'
=>
$dup
,
]);
return
$this
->
json
([
'results'
=>
$results
]);
}
}
\ No newline at end of file
app/Modules/Members/Services/MemberSearchService.php
View file @
8479c580
...
...
@@ -4,106 +4,38 @@ declare(strict_types=1);
namespace
App\Modules\Members\Services
;
use
App\Core\App
;
use
App\Core\Pagination
;
final
class
MemberSearchService
{
/**
* Search members with multiple criteria.
*/
public
static
function
search
(
array
$filters
,
int
$perPage
=
25
,
int
$page
=
1
)
:
array
public
static
function
search
(
string
$query
,
int
$limit
=
25
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$where
=
"m.is_archived = 0"
;
$params
=
[];
$query
=
trim
(
$query
);
if
(
!
empty
(
$filters
[
'q'
]))
{
$q
=
trim
(
$filters
[
'q'
]);
$where
.=
" AND (m.full_name_ar LIKE ? OR m.full_name_en LIKE ? OR m.national_id LIKE ? OR m.membership_number LIKE ? OR m.phone_mobile LIKE ? OR m.form_number LIKE ? OR m.passport_number LIKE ?)"
;
$s
=
"%
{
$q
}
%"
;
$params
=
array_merge
(
$params
,
[
$s
,
$s
,
$s
,
$s
,
$s
,
$s
,
$s
]);
if
(
$query
===
''
)
{
return
[];
}
if
(
!
empty
(
$filters
[
'status'
]))
{
$where
.=
" AND m.status = ?"
;
$params
[]
=
$filters
[
'status'
];
}
if
(
!
empty
(
$filters
[
'branch_id'
]))
{
$where
.=
" AND m.branch_id = ?"
;
$params
[]
=
(
int
)
$filters
[
'branch_id'
];
}
if
(
!
empty
(
$filters
[
'membership_type'
]))
{
$where
.=
" AND m.membership_type = ?"
;
$params
[]
=
$filters
[
'membership_type'
];
}
if
(
!
empty
(
$filters
[
'gender'
]))
{
$where
.=
" AND m.gender = ?"
;
$params
[]
=
$filters
[
'gender'
];
}
if
(
!
empty
(
$filters
[
'date_from'
]))
{
$where
.=
" AND m.created_at >= ?"
;
$params
[]
=
$filters
[
'date_from'
]
.
' 00:00:00'
;
}
if
(
!
empty
(
$filters
[
'date_to'
]))
{
$where
.=
" AND m.created_at <= ?"
;
$params
[]
=
$filters
[
'date_to'
]
.
' 23:59:59'
;
}
if
(
!
empty
(
$filters
[
'national_id'
]))
{
$where
.=
" AND m.national_id = ?"
;
$params
[]
=
$filters
[
'national_id'
];
}
$like
=
'%'
.
$query
.
'%'
;
if
(
!
empty
(
$filters
[
'membership_number'
]))
{
$where
.=
" AND m.membership_number = ?"
;
$params
[]
=
$filters
[
'membership_number'
];
}
$countRow
=
$db
->
selectOne
(
"SELECT COUNT(*) as cnt FROM members m WHERE
{
$where
}
"
,
$params
);
$total
=
(
int
)
(
$countRow
[
'cnt'
]
??
0
);
$offset
=
(
$page
-
1
)
*
$perPage
;
$rows
=
$db
->
select
(
"SELECT m.*, b.name_ar as branch_name, q.name_ar as qualification_name
return
$db
->
select
(
"SELECT m.id, m.full_name_ar, m.full_name_en, m.national_id,
m.membership_number, m.phone_mobile, m.status, m.form_number,
b.name_ar as branch_name
FROM members m
LEFT JOIN branches b ON b.id = m.branch_id
LEFT JOIN qualifications q ON q.id = m.qualification_id
WHERE
{
$where
}
ORDER BY m.created_at DESC
LIMIT
{
$perPage
}
OFFSET
{
$offset
}
"
,
$params
);
$pagination
=
Pagination
::
paginate
(
$total
,
$perPage
,
$page
);
return
[
'data'
=>
$rows
,
'pagination'
=>
$pagination
];
}
/**
* Quick search for AJAX autocomplete (header search bar).
*/
public
static
function
quickSearch
(
string
$query
,
int
$limit
=
10
)
:
array
{
$db
=
App
::
getInstance
()
->
db
();
$s
=
"%
{
$query
}
%"
;
return
$db
->
select
(
"SELECT id, membership_number, full_name_ar, national_id, phone_mobile, status
FROM members
WHERE is_archived = 0
AND (full_name_ar LIKE ? OR membership_number LIKE ? OR national_id LIKE ? OR phone_mobile LIKE ?)
ORDER BY
CASE WHEN membership_number = ? THEN 0
WHEN national_id = ? THEN 1
ELSE 2 END,
full_name_ar ASC
WHERE m.is_archived = 0
AND (
m.full_name_ar LIKE ?
OR m.national_id LIKE ?
OR m.membership_number LIKE ?
OR m.phone_mobile LIKE ?
OR m.form_number LIKE ?
OR m.full_name_en LIKE ?
)
ORDER BY m.full_name_ar
LIMIT ?"
,
[
$
s
,
$s
,
$s
,
$s
,
$query
,
$query
,
$limit
]
[
$
like
,
$like
,
$like
,
$like
,
$like
,
$like
,
$limit
]
);
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment