애플리케이션(PHP, 화면) 개선
This commit is contained in:
@@ -166,6 +166,7 @@ ORDER BY A.AppID;
|
|||||||
```
|
```
|
||||||
|
|
||||||
- 기존 코드는 기록이 없는 앱도 `AppHighestRecord = 0`으로 응답하므로 `LEFT JOIN` + `COALESCE(…, 0)`으로 **응답 형식을 그대로** 유지합니다.
|
- 기존 코드는 기록이 없는 앱도 `AppHighestRecord = 0`으로 응답하므로 `LEFT JOIN` + `COALESCE(…, 0)`으로 **응답 형식을 그대로** 유지합니다.
|
||||||
|
- ⚠️ (2026-09-15 검토) `HighestRecord`는 `FLOAT`라 `COALESCE`를 쓰면 결과가 `DOUBLE`로 바뀌어 JSON 소수점이 길어질 수 있습니다. 실제 구현은 `MAX(...) GROUP BY`로 조회하고 0은 PHP에서 채웠습니다.
|
||||||
- `get_typing_practice_app_list()`와 조건(`AppType = ? AND Status = 1`)이 같으므로 두 조회를 합쳐 앱 이름까지 한 번에 가져올 수도 있습니다.
|
- `get_typing_practice_app_list()`와 조건(`AppType = ? AND Status = 1`)이 같으므로 두 조회를 합쳐 앱 이름까지 한 번에 가져올 수도 있습니다.
|
||||||
- 안1의 UNIQUE 키 `(MaestroID, PlayerID, AppID)`가 있으면 `LEFT JOIN` 한 건당 인덱스 1회 탐색입니다.
|
- 안1의 UNIQUE 키 `(MaestroID, PlayerID, AppID)`가 있으면 `LEFT JOIN` 한 건당 인덱스 1회 탐색입니다.
|
||||||
|
|
||||||
@@ -274,8 +275,8 @@ function build_period_condition($column, $period, $date, $time) {
|
|||||||
|
|
||||||
| 파일 | 내용 | 수정 |
|
| 파일 | 내용 | 수정 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| [typing_exam_collection.php](../../../src/web/php/db/typing_exam_collection.php) `getHighestRecordArrayForAllWriting()` | `bind_param("iii", …)`에 값 2개 | 호출처가 있으면 `"ii"`, 없으면 함수 삭제 |
|
| [typing_exam_collection.php](../../../src/web/php/db/typing_exam_collection.php) `getHighestRecordArrayForAllWriting()` | `bind_param("iii", …)`에 값 2개 | 호출처가 있으면 `"ii"`, 없으면 함수 삭제 → (2026-09-15) 호출처 없음, `"ii"`로 수정하고 함수는 유지 |
|
||||||
| `server/record/*.php` 여러 파일 | `if($replyJSON.length === 0)` (PHP에서는 항상 거짓) | `count(...) === 0`으로 수정하거나 불필요하면 삭제 |
|
| `server/record/*.php` 여러 파일 | `if($replyJSON.length === 0)` (PHP에서는 항상 거짓) | **if 블록 삭제**. `count(...) === 0`으로 고치면 분기 안의 `send_error_message()`(정의되지 않은 함수)가 실행되어 Fatal error 발생 |
|
||||||
| [request_license_timer_player_record_list.php](../../../src/web/server/record/request_license_timer_player_record_list.php) | 종료일 당일 기록 누락, 존재하지 않는 `LS.AppID` 참조 | 3-1 패턴으로 재작성 |
|
| [request_license_timer_player_record_list.php](../../../src/web/server/record/request_license_timer_player_record_list.php) | 종료일 당일 기록 누락, 존재하지 않는 `LS.AppID` 참조 | 3-1 패턴으로 재작성 |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -342,11 +343,15 @@ function build_period_condition($column, $period, $date, $time) {
|
|||||||
|
|
||||||
## 8. 체크리스트
|
## 8. 체크리스트
|
||||||
|
|
||||||
- [ ] 기록 목록 API 3종: 바인딩 전환, COUNT 제거, 상한, 종료일 통일
|
- [x] **기록 목록 API 3종: 바인딩 전환, COUNT 유지, LIMIT 상한 없이, 종료일 통일** (2026-09-15 완료)
|
||||||
- [ ] 기록 목록 화면: 상한 안내 / 페이지
|
- request_app_player_record_list.php ✅
|
||||||
|
- request_writing_player_record_list.php ✅
|
||||||
|
- request_license_timer_player_record_list.php ✅
|
||||||
|
- [ ] 기록 목록 화면: 상한 안내 / 페이지 (생략)
|
||||||
- [ ] 변경 전후 검색 결과 건수 비교, SQL Injection 입력 테스트
|
- [ ] 변경 전후 검색 결과 건수 비교, SQL Injection 입력 테스트
|
||||||
- [ ] 랭킹 기간 조건 공용 함수 (안1 작업과 함께)
|
- [ ] ~~랭킹 기간 조건 공용 함수 (안1 작업과 함께)~~ — **보류** (2026-09-15): 날짜 조건은 커밋 629c8ff에서 12곳 모두 인덱스 적용 형태로 이미 수정됨. 회귀 위험 대비 이득이 작음
|
||||||
- [ ] 메뉴 N+1 제거 (연습·테스트, `menu_collection.php`, `writing_collection.php`)
|
- [x] **메뉴 N+1 제거 — 새 메뉴 경로** (2026-09-15 코드 완료): `menu_collection.php`, `writing_collection.php`, `menu_list.php`
|
||||||
- [ ] 작은 버그 3종
|
- [ ] 메뉴 N+1 제거 — 옛 메뉴 `server/app/menu_active_typing_*_app_list.php` (범위 제외, 테스트 계정 경로에서만 사용)
|
||||||
- [ ] (측정 후 필요 시) 랭킹 캐시 + 저장 시 무효화
|
- [x] **작은 버그** (2026-09-15 코드 완료): `.length` if 블록 삭제 13개 파일(추가 발견 4개 포함), `history_record.php` 미정의 변수 push 삭제, `getHighestRecordArrayForAllWriting()` `"ii"` 수정(함수 유지). 라이선스 타이머 `AppID` 참조는 유지
|
||||||
- [ ] `ranking` 테이블 처리 결정 (캐시 재활용 또는 삭제)
|
- [ ] (측정 후 필요 시) 랭킹 캐시 + 저장 시 무효화 — **이번 작업에서 제외** (2026-09-15, 적용 후 측정해서 재검토)
|
||||||
|
- [ ] `ranking` 테이블 처리 결정 (캐시 재활용 또는 삭제) — 랭킹 캐시와 함께 보류
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
# 안5. 애플리케이션(PHP/화면) 개선 실행 계획
|
||||||
|
|
||||||
|
> 참고: [`doc/plan/db/06-option5-application-layer.md`](../06-option5-application-layer.md) | 단계: **병행** | 난이도: 하~중 | 위험도: 낮음 | 예상 작업량: 2~4일
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 요약
|
||||||
|
|
||||||
|
DB 구조는 그대로 두고 PHP 코드의 비효율과 보안 이슈를 고친다:
|
||||||
|
|
||||||
|
| 항목 | 개선 내용 | 파일 수 | 영향 범위 |
|
||||||
|
|------|---------|--------|---------|
|
||||||
|
| **3-1. 기록 목록 API** | SQL Injection 제거 + COUNT 제거 + 상한 추가 | 3개 + 화면 1개 | 관리자 화면 |
|
||||||
|
| **3-2. 메뉴 N+1 쿼리** | 앱마다 조회 → 종류별 1회 조회 | 4개 | 학생 메뉴 |
|
||||||
|
| **3-4. 랭킹 코드 정리** | 중복 함수 → 공용 함수 1개 (**보류**) | 5개 파일 (12개 함수) | 랭킹 화면 |
|
||||||
|
| **3-3. 랭킹 캐시** | 선택 (측정 후 필요시) | 2개 + DB | 동시 접속 시 |
|
||||||
|
| **3-5. 작은 버그** | 각 파일 수정 중 처리 | 3개 파일 | 부분 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 실행 순서
|
||||||
|
|
||||||
|
### 1단계: 기록 목록 API 보안 + 성능 (우선순위 높음)
|
||||||
|
**소요: ~3시간 | 위험도: 낮음 | 롤백: git revert만 가능**
|
||||||
|
|
||||||
|
#### 대상 파일
|
||||||
|
```
|
||||||
|
src/web/server/record/
|
||||||
|
├── request_app_player_record_list.php
|
||||||
|
├── request_writing_player_record_list.php
|
||||||
|
└── request_license_timer_player_record_list.php
|
||||||
|
|
||||||
|
src/web/module/
|
||||||
|
└── maestro_section_record.html (화면 안내)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 변경 내용
|
||||||
|
- ✅ SQL 문자열 연결 → PreparedStatement + bind_param()
|
||||||
|
- ✅ `get_player_record_total_count()` 호출 제거 (화면에서 미사용)
|
||||||
|
- ✅ "전체 보기" 상한 도입 (예: MAX 1,000건)
|
||||||
|
- ✅ 종료일 처리 통일 (`< DATE(?) + INTERVAL 1 DAY`)
|
||||||
|
- ✅ 자격증 목록 `AppID` 참조 수정
|
||||||
|
|
||||||
|
#### 테스트 항목
|
||||||
|
```
|
||||||
|
□ 과목 전체/묶음(1000~1003)/개별 앱 검색
|
||||||
|
□ 이름 검색 있음/없음, 기간 1일/1개월
|
||||||
|
□ "전체 보기" → 상한 안내 표시
|
||||||
|
□ 종료일 당일 기록 포함 여부
|
||||||
|
□ 보안: 이름 칸에 ' OR '1'='1 입력 → 결과 비거나 해당 검색만
|
||||||
|
□ 자격증 목록 정상 조회
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 체크리스트
|
||||||
|
- [x] 3개 파일 bind_param 변환 완료
|
||||||
|
- [x] COUNT 유지 (요청사항)
|
||||||
|
- [x] LIMIT 상한 없이 유지 (요청사항)
|
||||||
|
- [x] 종료일 < DATE(?) + INTERVAL 1 DAY 통일
|
||||||
|
- [x] 결과 0건 분기(if 블록) 삭제 — 기존 동작(빈 목록 + success) 유지
|
||||||
|
- [ ] maestro_section_record.html 상한 안내 추가 (생략)
|
||||||
|
- [ ] 스테이징 테스트 완료
|
||||||
|
- [ ] SQL Injection 테스트 통과
|
||||||
|
- [ ] 전후 검색 결과 건수 비교 통과
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2단계: 랭킹 코드 정리 — ⏸️ 보류 (2026-09-15)
|
||||||
|
**소요: ~2시간 | 위험도: 낮음 | 이유: 안1 쿼리 조건 변경 시 공용 함수 1곳만 수정**
|
||||||
|
|
||||||
|
> **보류 사유 (검토 결과)**
|
||||||
|
> - 날짜 조건은 커밋 `629c8ff`에서 12곳 모두 인덱스 적용 형태로 이미 수정됨 → 원래 목적 달성
|
||||||
|
> - 실제 대상은 5개 파일·12개 함수이고, 기간 기준이 서버 시각(`app_ranking.php`)과 클라이언트 시각(나머지) 두 가지라 공용 함수가 단순하지 않음
|
||||||
|
> - `server/record`(함수 + `global $db_conn`)와 `php/db`(클래스 + `$this->mysqli`)의 구조·include 경로가 달라 통합 비용이 큼
|
||||||
|
> - 랭킹은 수업 중 가장 많이 쓰는 화면이라 회귀 위험 대비 이득이 작음
|
||||||
|
> - 참고: `app_ranking.php`는 호출처 없음(`requestAppRanking()` 미사용), `getHighestRecordArrayForAllWriting()`도 호출처 없음
|
||||||
|
|
||||||
|
#### 대상 파일
|
||||||
|
```
|
||||||
|
src/web/server/lib/ (신규)
|
||||||
|
├── ranking_period.php ← 새로 생성 (기간 조건 공용 함수)
|
||||||
|
|
||||||
|
src/web/server/record/
|
||||||
|
├── app_ranking.php (get_ranking_hour/day/month)
|
||||||
|
├── ranking_record_hour.php
|
||||||
|
├── ranking_record_day.php
|
||||||
|
└── ranking_record_month.php
|
||||||
|
|
||||||
|
src/web/php/db/
|
||||||
|
└── typing_exam_collection.php (getRankingRecord*, getRankingMinusRecord*)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 변경 내용
|
||||||
|
- ✅ 공용 함수 `build_period_condition($column, $period, $date, $time)` 생성
|
||||||
|
- 반환: (SQL 조각, 타입 문자열, 바인딩 값 배열)
|
||||||
|
- 사용자 입력 금지: `$column`은 코드 고정값만
|
||||||
|
- ✅ 기존 엔드포인트 응답 형식 그대로 유지 (클라이언트 변경 없음)
|
||||||
|
- ✅ 10개 파일의 중복 쿼리 → 공용 함수 호출로 통합
|
||||||
|
|
||||||
|
#### 체크리스트
|
||||||
|
- [ ] `src/web/server/lib/ranking_period.php` 생성
|
||||||
|
- [ ] app_ranking.php 내부 구현만 변경
|
||||||
|
- [ ] ranking_record_*.php 내부 구현만 변경
|
||||||
|
- [ ] typing_exam_collection.php 내부 구현만 변경
|
||||||
|
- [ ] 스테이징 랭킹 조회 테스트
|
||||||
|
- [ ] 게임 클라이언트 변경 불필요 확인
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3단계: 메뉴 N+1 쿼리 제거 — ✅ 코드 완료 (2026-09-15, 새 메뉴 경로만)
|
||||||
|
**소요: ~2시간 | 위험도: 낮음 | 영향: 메뉴 진입 속도 (4 + 앱 수 → 약 6회 쿼리)**
|
||||||
|
|
||||||
|
> **검토 결과 및 결정**
|
||||||
|
> - 사용 중인 메뉴는 `client/main_menu.html` → `php/menu/menu_list.php` → `menu_collection.php` / `writing_collection.php` 경로 (탭 클릭마다 호출)
|
||||||
|
> - 옛 메뉴 `server/app/menu_active_typing_*_app_list.php`는 `start.js` 링크가 주석 처리되어 마에스트로 테스트 계정 경로에서만 접근 → **이번 범위 제외**
|
||||||
|
> - 탭당 쿼리: `1 + 1 + 앱(글) 수` → `3` (예: 한글 테스트 12 → 3, 한글 긴글 7 → 3)
|
||||||
|
> - 아래 쿼리 예시의 `COALESCE(MAX(...), 0)`은 **사용하지 않음**: `FLOAT`가 `DOUBLE`로 바뀌어 JSON 소수점이 길어질 수 있음 → `MAX(...) GROUP BY` + PHP에서 0 채움
|
||||||
|
> - UNIQUE 키가 운영에는 아직 없으므로 `MAX + GROUP BY`로 앱당 1행 보장, 목록이 비면 쿼리 생략(`IN ()` 오류 방지)
|
||||||
|
>
|
||||||
|
> **변경 내용**
|
||||||
|
> - `menu_collection.php`: `getTypingHighestRecordList()`를 `AppID IN (...)` 1회 조회로 교체 (`getTypingHighestRecord()` → `getTypingHighestRecordMap()`)
|
||||||
|
> - `menu_collection.php`: `getWritingHighestRecordList()`를 1회 조회로 교체
|
||||||
|
> - `writing_collection.php`: `getWritingHighestRecord()` → `getWritingHighestRecordMap()`
|
||||||
|
> - `menu_collection.php`: `getHighestRecordList()`에 `$appGroup` 인자 추가 (미정의 변수 버그 수정), `menu_list.php` 호출부 수정
|
||||||
|
> - `menu_collection.php`: 지역 변수 `$writingCollection` → `$this->writingCollection`
|
||||||
|
> - 응답 형식 유지: 앱(글)마다 1항목, ID 정수, 기록 없으면 정수 `0`, 순서 = 앱 목록 순서
|
||||||
|
|
||||||
|
#### 대상 파일
|
||||||
|
```
|
||||||
|
src/web/server/app/
|
||||||
|
├── menu_active_typing_practice_app_list.php
|
||||||
|
└── menu_active_typing_test_app_list.php
|
||||||
|
|
||||||
|
src/web/php/db/
|
||||||
|
├── menu_collection.php (getTypingHighestRecordList)
|
||||||
|
└── writing_collection.php (getWritingHighestRecord)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 변경 내용
|
||||||
|
- ✅ 앱마다 조회 (N+1) → 종류별 1회 + LEFT JOIN 통합
|
||||||
|
- ✅ 기존 응답 형식 그대로 (기록 없는 앱도 AppHighestRecord=0)
|
||||||
|
- ✅ COALESCE(..., 0) 사용해 NULL → 0 변환
|
||||||
|
|
||||||
|
#### 쿼리 변경 예시
|
||||||
|
```php
|
||||||
|
// Before: 앱 수만큼 반복
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
SELECT MAX(AHR.HighestRecord)
|
||||||
|
FROM app A
|
||||||
|
INNER JOIN app_highest_record AHR ...
|
||||||
|
WHERE A.AppID = ?
|
||||||
|
}
|
||||||
|
|
||||||
|
// After: 1회 조회
|
||||||
|
SELECT A.AppID, COALESCE(MAX(AHR.HighestRecord), 0) AS AppHighestRecord
|
||||||
|
FROM app A
|
||||||
|
LEFT JOIN app_highest_record AHR
|
||||||
|
ON AHR.AppID = A.AppID AND AHR.MaestroID = ? AND AHR.PlayerID = ?
|
||||||
|
WHERE A.AppType = ? AND A.Status = 1
|
||||||
|
GROUP BY A.AppID
|
||||||
|
ORDER BY A.AppID;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 테스트 항목
|
||||||
|
```
|
||||||
|
□ 새 메뉴(main_menu.html) 탭별: 한글/영어 × 연습/테스트/긴글/타자게임, 마우스
|
||||||
|
□ 기록 있는 앱/없는 앱의 최고기록(동물 아이콘) 표시가 변경 전과 일치
|
||||||
|
□ menu_list.php 응답 JSON의 highestRecordList를 변경 전후 비교 (소수점 자릿수 포함)
|
||||||
|
□ 기록이 전혀 없는 학생 계정으로 메뉴 진입 (오류 없이 모두 0)
|
||||||
|
□ PHP 에러 로그에 Warning/Notice 없음
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 체크리스트
|
||||||
|
- [ ] menu_active_typing_practice_app_list.php 수정 (범위 제외: 옛 메뉴)
|
||||||
|
- [ ] menu_active_typing_test_app_list.php 수정 (범위 제외: 옛 메뉴)
|
||||||
|
- [x] menu_collection.php 수정 (AppID 배열 처리, `$appGroup` 버그, `$this->writingCollection`)
|
||||||
|
- [x] writing_collection.php 수정 (`getWritingHighestRecordMap()`)
|
||||||
|
- [x] menu_list.php 호출부 수정
|
||||||
|
- [ ] 스테이징 메뉴 진입 테스트
|
||||||
|
- [ ] 기록 있는/없는 앱 표시 일치 확인
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4단계: 작은 버그 수정 — ✅ 코드 완료 (2026-09-15)
|
||||||
|
**소요: ~30분 | 위험도: 매우 낮음**
|
||||||
|
|
||||||
|
> **검토 결과 및 결정**
|
||||||
|
> - PHP 7.3에서 `$x.length === 0`은 `$x . "length"` → `"Arraylength" === 0` → **항상 거짓**. 분기는 한 번도 실행된 적 없고 요청마다 에러 로그에 Warning만 남음. PHP 8에서는 Fatal error
|
||||||
|
> - 계획의 6개 파일 외에 4개 추가 발견 → **10개 파일 모두 if 블록 삭제** (동작 변화 없음)
|
||||||
|
> - `record/ranking_record_hour/day/month.php`, `record/history_record.php`, `record/request_app_highest_record.php`, `app/how_to_play.php`
|
||||||
|
> - (추가) `app/menu_active_app_list.php` — 비교 대상 `$replyJSON` 자체가 미정의라 `count()`로 고치면 항상 실패 응답이 되어 메뉴가 깨짐
|
||||||
|
> - (추가) `app/mouse_app_list.php`, `app/typing_app_list.php`, `player/search_player_list.php` — `search_player_list`는 검색 결과 0명일 때 지금처럼 빈 목록 표시 유지
|
||||||
|
> - `history_record.php`: 루프 안 미정의 `$return_array`에 `array_push()` 하던 줄 삭제 (응답 동일, 행마다 Warning 제거)
|
||||||
|
> - `typing_exam_collection.php` `getHighestRecordArrayForAllWriting()`: `"iii"` → `"ii"` 수정, 함수는 유지 (호출처 없음)
|
||||||
|
> - 참고(범위 밖): `src/web/php/`에는 `.htaccess`가 없고 스테이징 Dockerfile에 `php.ini` 설정이 없어 `display_errors` 기본값(On)이 적용될 수 있음 → Warning이 JSON 응답에 섞이는지 `phpinfo()`로 확인 권장
|
||||||
|
|
||||||
|
| 파일 | 문제 | 수정 |
|
||||||
|
|------|------|------|
|
||||||
|
| typing_exam_collection.php | bind_param("iii", …)에 값 2개만 | "ii"로 수정 또는 함수 삭제 |
|
||||||
|
| server/record/*.php 여러 파일 | `if($replyJSON.length === 0)` (PHP에서 항상 거짓) | **if 블록 삭제** (`count()`로 고치면 정의되지 않은 `send_error_message()`가 실행되어 Fatal error) |
|
||||||
|
| request_license_timer_player_record_list.php | 종료일 당일 기록 누락, 존재하지 않는 LS.AppID | 종료일은 1단계에서 수정 완료. AppID 참조는 화면이 3000만 보내므로 유지 (추후 정리) |
|
||||||
|
|
||||||
|
#### 체크리스트
|
||||||
|
- [x] typing_exam_collection.php bind_param `"iii"` → `"ii"` (함수 유지)
|
||||||
|
- [x] request_*_player_record_list.php 3개: if 블록 삭제 (1단계에서 처리)
|
||||||
|
- [x] 남은 파일 if 블록 삭제: ranking_record_hour/day/month.php, history_record.php, request_app_highest_record.php, app/how_to_play.php
|
||||||
|
- [x] 추가 발견 파일 if 블록 삭제: app/menu_active_app_list.php, app/mouse_app_list.php, app/typing_app_list.php, player/search_player_list.php
|
||||||
|
- [x] history_record.php 미정의 `$return_array` push 줄 삭제
|
||||||
|
- [ ] 스테이징 테스트: 결과·랭킹 화면, 시작 화면(히스토리·최고기록·게임 방법), 메뉴, 마에스트로 앱 활성화·기록·학생 검색(0명 포함)
|
||||||
|
- [ ] 스테이징 PHP 에러 로그에서 `Use of undefined constant length` Warning 사라졌는지 확인
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5단계: 랭킹 캐시 — ⏸️ 이번 작업에서 제외 (2026-09-15)
|
||||||
|
|
||||||
|
> 1·3·4단계 스테이징/운영 적용 후 수업 시간대 랭킹 쿼리 부하를 측정해서 필요할 때 다시 검토합니다. 진행 시 2단계 보류로 랭킹 코드가 5개 파일·12개 함수에 흩어져 있다는 점을 고려해 캐시 적용 위치부터 재검토해야 합니다.
|
||||||
|
**소요: ~2시간 | 위험도: 낮음 | 조건: 1·2 완료 후 측정에서 필요 판단**
|
||||||
|
|
||||||
|
#### 배경
|
||||||
|
- 교실 수업: 학생 30명 동시 → 동일한 랭킹 쿼리 90회 실행
|
||||||
|
- 랭킹 화면 1시간 열어 둠 → 기록 변화 없어도 720회 쿼리 (5초마다)
|
||||||
|
|
||||||
|
#### 방법 (DB 캐시 테이블 권장)
|
||||||
|
```sql
|
||||||
|
-- ranking 테이블 백업 후 삭제
|
||||||
|
CREATE TABLE ranking_cache (
|
||||||
|
MaestroID INT UNSIGNED NOT NULL,
|
||||||
|
AppID INT UNSIGNED NOT NULL,
|
||||||
|
RankingType CHAR(10) NOT NULL,
|
||||||
|
CachedDateTime DATETIME NOT NULL,
|
||||||
|
Payload MEDIUMTEXT NOT NULL,
|
||||||
|
PRIMARY KEY (MaestroID, AppID, RankingType)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 체크리스트
|
||||||
|
- [ ] 3-1·3-2 완료 후 성능 측정
|
||||||
|
- [ ] 랭킹 쿼리 슬로우 로그 확인 (필요시만 진행)
|
||||||
|
- [ ] ranking_cache 테이블 생성
|
||||||
|
- [ ] 캐시 조회/저장 로직 구현
|
||||||
|
- [ ] update_result_record.php에 캐시 무효화 추가
|
||||||
|
- [ ] ranking 테이블 백업 후 삭제 또는 유지 결정
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 예상 효과
|
||||||
|
|
||||||
|
| 화면 / API | 변경 전 | 변경 후 | 개선율 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 기록 목록 조회 | COUNT 1회 + 목록 1회 | 목록 1회 | **50% ↓** |
|
||||||
|
| 메뉴 진입 (앱 16개) | ~20회 쿼리 | ~6회 | **67% ↓** |
|
||||||
|
| 결과 화면 랭킹 (학생 30명) | 90회 | 3회 (캐시 미스) | **97% ↓** |
|
||||||
|
| 보안 | SQL Injection 취약 | 안전 (바인딩) | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 주의사항
|
||||||
|
|
||||||
|
| 항목 | 위험 | 대응 |
|
||||||
|
|------|------|------|
|
||||||
|
| **검색 결과 범위 변경** | 과목 묶음 범위, 종료일 처리로 결과 건수 달라질 수 있음 | 변경 전후 같은 조건으로 건수 비교 |
|
||||||
|
| **bind_param 타입 오류** | `$types`와 `$params` 개수 불일치 | 항상 같은 줄 묶음에서 함께 추가 |
|
||||||
|
| **캐시 무효화 누락** | 새 기록 후 오래된 랭킹 표시 | 기록 저장 시 해당 캐시 DELETE |
|
||||||
|
| **응답 형식 변경** | 클라이언트가 다른 형식 기대 | 기존 응답 형식 유지 (LEFT JOIN + COALESCE) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 스테이징 배포 절차
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 각 단계별 개발
|
||||||
|
# 2. 로컬 테스트 통과
|
||||||
|
git add src/web/server/record/ src/web/module/ ...
|
||||||
|
git commit -m "3-1: 기록 목록 API SQL Injection 제거 및 성능 개선"
|
||||||
|
|
||||||
|
# 3. 스테이징 배포
|
||||||
|
git push origin 08-app-improvements
|
||||||
|
|
||||||
|
# 4. 스테이징 테스트 완료
|
||||||
|
# 5. 운영 서버 배포 (새벽 2시)
|
||||||
|
git push origin release
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 최종 체크리스트
|
||||||
|
|
||||||
|
### Phase 1: 기록 목록 API
|
||||||
|
- [x] 3개 파일 bind_param 변환 (2026-09-15 완료)
|
||||||
|
- request_app_player_record_list.php ✅
|
||||||
|
- request_writing_player_record_list.php ✅
|
||||||
|
- request_license_timer_player_record_list.php ✅
|
||||||
|
- [x] COUNT 유지, LIMIT 상한 없이 유지
|
||||||
|
- [x] 종료일 < DATE(?) + INTERVAL 1 DAY 통일
|
||||||
|
- [x] 결과 0건 분기(if 블록) 삭제 — `send_error_message()` 미정의로 인한 Fatal error 방지
|
||||||
|
- [x] get_subject_app_range() 함수 추가
|
||||||
|
- [ ] maestro_section_record.html 상한 안내 (생략)
|
||||||
|
- [ ] SQL Injection 테스트
|
||||||
|
- [ ] 스테이징 배포
|
||||||
|
|
||||||
|
### Phase 2: 랭킹 코드 정리 — ⏸️ 보류 (2026-09-15, 사유는 2단계 본문 참고)
|
||||||
|
|
||||||
|
### Phase 3: 메뉴 N+1
|
||||||
|
- [x] 새 메뉴 경로 3개 파일 변경 (2026-09-15): menu_collection.php, writing_collection.php, menu_list.php
|
||||||
|
- [x] `IN (...)` + `MAX ... GROUP BY` 1회 조회, 0 채움은 PHP (COALESCE 미사용)
|
||||||
|
- [ ] 옛 메뉴 server/app 2개 파일 (범위 제외)
|
||||||
|
- [ ] 메뉴 진입 테스트
|
||||||
|
- [ ] 스테이징 배포
|
||||||
|
|
||||||
|
### Phase 4: 작은 버그
|
||||||
|
- [x] typing_exam_collection.php `"iii"` → `"ii"` (2026-09-15)
|
||||||
|
- [x] `.length` if 블록 삭제: 13개 파일 (1단계 3개 + 4단계 10개)
|
||||||
|
- [x] history_record.php 미정의 변수 push 삭제
|
||||||
|
- [ ] ~~라이선스 타이머 재작성~~ (종료일은 1단계 완료, AppID 참조는 유지 결정)
|
||||||
|
- [ ] 스테이징 테스트
|
||||||
|
|
||||||
|
### Phase 5: 랭킹 캐시 — ⏸️ 이번 작업에서 제외 (2026-09-15, 측정 후 재검토)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 참고 문서
|
||||||
|
|
||||||
|
| 링크 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| [`06-option5-application-layer.md`](../06-option5-application-layer.md) | 전체 개선 계획 및 코드 예시 |
|
||||||
|
| [`00-overview.md`](00-overview.md) | DB 인덱스 개선 계획 (병행) |
|
||||||
|
| `AGENTS.md` | 프로젝트 아키텍처 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**작성일:** 2026-09-15
|
||||||
|
**상태:** 1·3·4단계 코드 완료, 2·5단계 보류 → ⏳ 스테이징 테스트 대기 (2026-09-15)
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
## 📋 문서 구조
|
## 📋 문서 구조
|
||||||
|
|
||||||
|
### 안1: DB 인덱스 + 쿼리 최적화
|
||||||
|
|
||||||
| # | 파일 | 설명 | 소요시간 |
|
| # | 파일 | 설명 | 소요시간 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 00 | `00-overview.md` | 전체 계획 및 사전 확인 | - |
|
| 00 | `00-overview.md` | 전체 계획 및 사전 확인 | - |
|
||||||
@@ -15,6 +17,12 @@
|
|||||||
| 04 | `04-verify.md` | **개선 후 성능 측정 및 비교** (EXPLAIN, 성능 비교) | 15분 |
|
| 04 | `04-verify.md` | **개선 후 성능 측정 및 비교** (EXPLAIN, 성능 비교) | 15분 |
|
||||||
| 05 | `05-checklist.md` | **최종 체크리스트** (성공 기준) | 10분 |
|
| 05 | `05-checklist.md` | **최종 체크리스트** (성공 기준) | 10분 |
|
||||||
|
|
||||||
|
### 안5: 애플리케이션 개선 (병행)
|
||||||
|
|
||||||
|
| # | 파일 | 설명 | 소요시간 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 08 | `08-application-layer-improvements.md` | PHP/화면 개선 전체 계획 (5단계) | 2~4일 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 빠른 시작
|
## 🚀 빠른 시작
|
||||||
@@ -67,19 +75,30 @@
|
|||||||
|
|
||||||
## 🔗 관련 문서
|
## 🔗 관련 문서
|
||||||
|
|
||||||
- **계획:** [`doc/plan/db/02-option1-index-and-query-rewrite.md`](../02-option1-index-and-query-rewrite.md)
|
- **계획 (안1):** [`doc/plan/db/02-option1-index-and-query-rewrite.md`](../02-option1-index-and-query-rewrite.md)
|
||||||
|
- **계획 (안5):** [`doc/plan/db/06-option5-application-layer.md`](../06-option5-application-layer.md)
|
||||||
- **운영 서버:** 260915-improve-production 디렉토리 (진행 예정)
|
- **운영 서버:** 260915-improve-production 디렉토리 (진행 예정)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📝 진행 상황
|
## 📝 진행 상황
|
||||||
|
|
||||||
|
### 안1 (DB 인덱스)
|
||||||
| 단계 | 상태 | 날짜 |
|
| 단계 | 상태 | 날짜 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 계획 수립 | ✅ 완료 | 2026-09-15 |
|
| 계획 수립 | ✅ 완료 | 2026-09-15 |
|
||||||
| 스테이징 테스트 | ⏳ 준비 중 | |
|
| 스테이징 테스트 | ⏳ 준비 중 | |
|
||||||
| 운영 서버 적용 | ⏳ 대기 중 | |
|
| 운영 서버 적용 | ⏳ 대기 중 | |
|
||||||
|
|
||||||
|
### 안5 (Application Layer)
|
||||||
|
| 단계 | 상태 | 날짜 |
|
||||||
|
|---|---|---|
|
||||||
|
| Phase 1: 기록 목록 API | ✅ 코드 완료 | 2026-09-15 |
|
||||||
|
| Phase 2: 랭킹 코드 정리 | ⏸️ 보류 (날짜 조건은 629c8ff에서 이미 수정됨) | 2026-09-15 |
|
||||||
|
| Phase 3: 메뉴 N+1 제거 | ✅ 코드 완료 (새 메뉴 경로) | 2026-09-15 |
|
||||||
|
| Phase 4: 작은 버그 | ✅ 코드 완료 (`.length` 분기 13개 파일 정리) | 2026-09-15 |
|
||||||
|
| Phase 5: 랭킹 캐시 | ⏸️ 이번 작업에서 제외 (측정 후 재검토) | 2026-09-15 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💾 저장 위치
|
## 💾 저장 위치
|
||||||
@@ -105,6 +124,12 @@ doc/plan/db/260915-improve-stage/
|
|||||||
|
|
||||||
## 다음 단계
|
## 다음 단계
|
||||||
|
|
||||||
1. **지금:** 01-premeasure.md부터 시작
|
### 안1 (DB 인덱스 - 진행 중)
|
||||||
2. **스테이징 완료 후:** 260915-improve-production 디렉토리 생성
|
1. **지금:** 01-premeasure.md부터 시작 → 05-checklist.md 완료
|
||||||
3. **운영 서버:** 동일한 절차 (새벽 2시)
|
2. **스테이징 완료 후:** 260915-improve-production 디렉토리에서 운영 적용 (새벽 2시)
|
||||||
|
|
||||||
|
### 안5 (Application Layer - 병행 권장)
|
||||||
|
1. **08-application-layer-improvements.md** 읽기 (5단계 실행 계획)
|
||||||
|
2. **각 단계별 개발** (3-1 → 3-4 → 3-2 → 3-5 순서)
|
||||||
|
3. **스테이징 배포** → 테스트
|
||||||
|
4. **운영 서버 배포** (안1 후 또는 동시 가능)
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ class MenuCollection extends DBMethodContainer
|
|||||||
return $this->getTypingAppList($maestroID, $playerID, $appGroup, $language);
|
return $this->getTypingAppList($maestroID, $playerID, $appGroup, $language);
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if($writingCollection === null)
|
if($this->writingCollection === null)
|
||||||
$writingCollection = new WritingCollection($this->mysqli);
|
$this->writingCollection = new WritingCollection($this->mysqli);
|
||||||
$systemWritingList = $writingCollection->getSystemList($language);
|
$systemWritingList = $this->writingCollection->getSystemList($language);
|
||||||
return $systemWritingList;
|
return $systemWritingList;
|
||||||
|
|
||||||
case 4: // mouse play
|
case 4: // mouse play
|
||||||
@@ -142,9 +142,9 @@ class MenuCollection extends DBMethodContainer
|
|||||||
return $this->getTypingActiveAppList($maestroID, $playerID, $appGroup, $language);
|
return $this->getTypingActiveAppList($maestroID, $playerID, $appGroup, $language);
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if($writingCollection === null)
|
if($this->writingCollection === null)
|
||||||
$writingCollection = new WritingCollection($this->mysqli);
|
$this->writingCollection = new WritingCollection($this->mysqli);
|
||||||
$activeSystemWritingList = $writingCollection->getActiveSystemList($maestroID, $language);
|
$activeSystemWritingList = $this->writingCollection->getActiveSystemList($maestroID, $language);
|
||||||
return $activeSystemWritingList;
|
return $activeSystemWritingList;
|
||||||
|
|
||||||
case 4: // mouse play
|
case 4: // mouse play
|
||||||
@@ -199,7 +199,7 @@ class MenuCollection extends DBMethodContainer
|
|||||||
|
|
||||||
|
|
||||||
// highest record list
|
// highest record list
|
||||||
public function getHighestRecordList($maestroID, $playerID, $appList)
|
public function getHighestRecordList($maestroID, $playerID, $appGroup, $appList)
|
||||||
{
|
{
|
||||||
switch($appGroup) {
|
switch($appGroup) {
|
||||||
case 0: // typing practice
|
case 0: // typing practice
|
||||||
@@ -215,15 +215,20 @@ class MenuCollection extends DBMethodContainer
|
|||||||
|
|
||||||
public function getWritingHighestRecordList($maestroID, $playerID, $appList)
|
public function getWritingHighestRecordList($maestroID, $playerID, $appList)
|
||||||
{
|
{
|
||||||
if($writingCollection === null)
|
if($this->writingCollection === null)
|
||||||
$writingCollection = new WritingCollection($this->mysqli);
|
$this->writingCollection = new WritingCollection($this->mysqli);
|
||||||
|
|
||||||
|
$writingIDs = array();
|
||||||
|
foreach($appList as $app)
|
||||||
|
$writingIDs[] = $app["writingID"];
|
||||||
|
|
||||||
|
$recordMap = $this->writingCollection->getWritingHighestRecordMap($maestroID, $playerID, $writingIDs);
|
||||||
|
|
||||||
$highestRecordArray = array();
|
$highestRecordArray = array();
|
||||||
$count = count($appList);
|
foreach($writingIDs as $writingID) {
|
||||||
for($i = 0; $i < $count; $i++) {
|
$highestRecord = array();
|
||||||
$writingID = $appList[$i]["writingID"];
|
|
||||||
$highestRecord["writingID"] = $writingID;
|
$highestRecord["writingID"] = $writingID;
|
||||||
$highestRecord["highestRecord"] = $writingCollection->getWritingHighestRecord($maestroID, $playerID, $writingID);
|
$highestRecord["highestRecord"] = isset($recordMap[$writingID]) ? $recordMap[$writingID] : 0;
|
||||||
array_push($highestRecordArray, $highestRecord);
|
array_push($highestRecordArray, $highestRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,35 +237,46 @@ class MenuCollection extends DBMethodContainer
|
|||||||
|
|
||||||
public function getTypingHighestRecordList($maestroID, $playerID, $appList)
|
public function getTypingHighestRecordList($maestroID, $playerID, $appList)
|
||||||
{
|
{
|
||||||
$highestRecordArray = array();
|
$appIDs = array();
|
||||||
$count = count($appList);
|
foreach($appList as $app)
|
||||||
for($i = 0; $i < $count; $i++) {
|
$appIDs[] = $app["appID"];
|
||||||
$appID = $appList[$i]["appID"];
|
|
||||||
$highestRecord["appID"] = $appID;
|
|
||||||
$highestRecord["highestRecord"] = $this->getTypingHighestRecord($maestroID, $playerID, $appID);
|
|
||||||
array_push($highestRecordArray, $highestRecord);
|
|
||||||
|
|
||||||
|
$recordMap = $this->getTypingHighestRecordMap($maestroID, $playerID, $appIDs);
|
||||||
|
|
||||||
|
$highestRecordArray = array();
|
||||||
|
foreach($appIDs as $appID) {
|
||||||
|
$highestRecord = array();
|
||||||
|
$highestRecord["appID"] = $appID;
|
||||||
|
$highestRecord["highestRecord"] = isset($recordMap[$appID]) ? $recordMap[$appID] : 0;
|
||||||
|
array_push($highestRecordArray, $highestRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $highestRecordArray;
|
return $highestRecordArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTypingHighestRecord($maestroID, $playerID, $appID)
|
private function getTypingHighestRecordMap($maestroID, $playerID, $appIDs)
|
||||||
{
|
{
|
||||||
|
$recordMap = array();
|
||||||
|
if(count($appIDs) === 0)
|
||||||
|
return $recordMap;
|
||||||
|
|
||||||
|
// MAX + GROUP BY: UNIQUE 키가 없는 DB에서도 앱당 1행. COALESCE를 쓰면 FLOAT가 DOUBLE로 바뀌어 소수점이 길어짐
|
||||||
|
$placeholders = implode(",", array_fill(0, count($appIDs), "?"));
|
||||||
$query = "
|
$query = "
|
||||||
SELECT HighestRecord
|
SELECT AppID, MAX(HighestRecord)
|
||||||
FROM app_highest_record
|
FROM app_highest_record
|
||||||
WHERE MaestroID=? AND PlayerID=? AND AppID=?";
|
WHERE MaestroID=? AND PlayerID=? AND AppID IN (" . $placeholders . ")
|
||||||
|
GROUP BY AppID";
|
||||||
|
$params = array_merge(array($maestroID, $playerID), $appIDs);
|
||||||
$stmt = $this->mysqli->prepare($query);
|
$stmt = $this->mysqli->prepare($query);
|
||||||
$stmt->bind_param("iii", $maestroID, $playerID, $appID);
|
$stmt->bind_param(str_repeat("i", count($params)), ...$params);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($highestRecord);
|
$stmt->bind_result($appID, $highestRecord);
|
||||||
$returnValue = $stmt->fetch();
|
while($stmt->fetch())
|
||||||
|
$recordMap[$appID] = $highestRecord;
|
||||||
$stmt->close();
|
$stmt->close();
|
||||||
|
|
||||||
if($returnValue === NULL || $returnValue === FALSE)
|
return $recordMap;
|
||||||
return 0;
|
|
||||||
|
|
||||||
return $highestRecord;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class TypingExamCollection extends DBMethodContainer
|
|||||||
FROM typing_exam_highest_record
|
FROM typing_exam_highest_record
|
||||||
WHERE MaestroID = ? AND PlayerID = ?";
|
WHERE MaestroID = ? AND PlayerID = ?";
|
||||||
$stmt = $this->mysqli->prepare($query);
|
$stmt = $this->mysqli->prepare($query);
|
||||||
$stmt->bind_param("iii", $maestroID, $playerID);
|
$stmt->bind_param("ii", $maestroID, $playerID);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($typingExamHighestRecordID, $writingID, $highestRecord);
|
$stmt->bind_result($typingExamHighestRecordID, $writingID, $highestRecord);
|
||||||
|
|
||||||
|
|||||||
@@ -66,23 +66,29 @@ class WritingCollection extends DBMethodContainer
|
|||||||
return $writingList;
|
return $writingList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWritingHighestRecord($maestroID, $playerID, $writingID)
|
public function getWritingHighestRecordMap($maestroID, $playerID, $writingIDs)
|
||||||
{
|
{
|
||||||
|
$recordMap = array();
|
||||||
|
if(count($writingIDs) === 0)
|
||||||
|
return $recordMap;
|
||||||
|
|
||||||
|
// MAX + GROUP BY: UNIQUE 키가 없는 DB에서도 글당 1행. COALESCE를 쓰면 FLOAT가 DOUBLE로 바뀌어 소수점이 길어짐
|
||||||
|
$placeholders = implode(",", array_fill(0, count($writingIDs), "?"));
|
||||||
$query = "
|
$query = "
|
||||||
SELECT WritingID, HighestRecord
|
SELECT WritingID, MAX(HighestRecord)
|
||||||
FROM typing_exam_highest_record
|
FROM typing_exam_highest_record
|
||||||
WHERE MaestroID=? AND PlayerID=? AND WritingID=?";
|
WHERE MaestroID=? AND PlayerID=? AND WritingID IN (" . $placeholders . ")
|
||||||
|
GROUP BY WritingID";
|
||||||
|
$params = array_merge(array($maestroID, $playerID), $writingIDs);
|
||||||
$stmt = $this->mysqli->prepare($query);
|
$stmt = $this->mysqli->prepare($query);
|
||||||
$stmt->bind_param("iii", $maestroID, $playerID, $writingID);
|
$stmt->bind_param(str_repeat("i", count($params)), ...$params);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($writingID, $highestRecord);
|
$stmt->bind_result($writingID, $highestRecord);
|
||||||
$returnValue = $stmt->fetch();
|
while($stmt->fetch())
|
||||||
|
$recordMap[$writingID] = $highestRecord;
|
||||||
$stmt->close();
|
$stmt->close();
|
||||||
|
|
||||||
if($returnValue === NULL || $returnValue === FALSE)
|
return $recordMap;
|
||||||
return 0;
|
|
||||||
|
|
||||||
return $highestRecord;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ $activeAppList = $menuCollection->getActiveAppList($maestroID, $playerID, $appGr
|
|||||||
if($appGroup === "2") // typing exam
|
if($appGroup === "2") // typing exam
|
||||||
$highestRecordList = $menuCollection->getWritingHighestRecordList($maestroID, $playerID, $appList);
|
$highestRecordList = $menuCollection->getWritingHighestRecordList($maestroID, $playerID, $appList);
|
||||||
else
|
else
|
||||||
$highestRecordList = $menuCollection->getHighestRecordList($maestroID, $playerID, $appList);
|
$highestRecordList = $menuCollection->getHighestRecordList($maestroID, $playerID, $appGroup, $appList);
|
||||||
// $highestRecordList = $menuCollection->getHighestRecordList($maestroID, $playerID, $appList);
|
// $highestRecordList = $menuCollection->getHighestRecordList($maestroID, $playerID, $appList);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,6 @@ include "./../setup/connect_db.php";
|
|||||||
|
|
||||||
$replyJSON = array();
|
$replyJSON = array();
|
||||||
$replyJSON["HowToPlay"] = get_how_to_play($app_id);
|
$replyJSON["HowToPlay"] = get_how_to_play($app_id);
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "히스토리 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
||||||
$db_conn->close();
|
$db_conn->close();
|
||||||
|
|||||||
@@ -13,13 +13,6 @@ $typingActiveAppList = get_active_typing_app_list($maestro_id);
|
|||||||
$typingPracticeAppCount = get_typing_practice_app_count($maestro_id);
|
$typingPracticeAppCount = get_typing_practice_app_count($maestro_id);
|
||||||
$typingTestAppCount = get_typing_test_app_count($maestro_id);
|
$typingTestAppCount = get_typing_test_app_count($maestro_id);
|
||||||
|
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
set_error_code("no_app_list");
|
|
||||||
set_error_message("앱 목록을 가져오지 못했습니다.");
|
|
||||||
send_result_fail();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_data("MouseAppList", $mouseAppList);
|
set_data("MouseAppList", $mouseAppList);
|
||||||
set_data("TypingAppList", $typingAppList);
|
set_data("TypingAppList", $typingAppList);
|
||||||
set_data("MouseActiveAppList", $mouseActiveAppList);
|
set_data("MouseActiveAppList", $mouseActiveAppList);
|
||||||
|
|||||||
@@ -8,11 +8,6 @@ $maestroID = $_POST["maestro_id"];
|
|||||||
|
|
||||||
|
|
||||||
$mouse_app_list = get_mouse_app_list();
|
$mouse_app_list = get_mouse_app_list();
|
||||||
if($mouse_app_list.length === 0) {
|
|
||||||
set_error_message("등록된 마우스 앱 없음");
|
|
||||||
send_result_fail();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$activated_mouse_app_list = get_activated_mouse_app_list($maestroID);
|
$activated_mouse_app_list = get_activated_mouse_app_list($maestroID);
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,6 @@ $maestroID = $_POST["maestro_id"];
|
|||||||
|
|
||||||
|
|
||||||
$typing_app_list = get_typing_app_list();
|
$typing_app_list = get_typing_app_list();
|
||||||
if($typing_app_list.length === 0) {
|
|
||||||
set_error_message("등록된 타자 앱 없음");
|
|
||||||
send_result_fail();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$activated_typing_app_list = get_activated_typing_app_list($maestroID);
|
$activated_typing_app_list = get_activated_typing_app_list($maestroID);
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ $endNo = $_POST["end_no"];
|
|||||||
|
|
||||||
|
|
||||||
$playerList = get_player_list_page($maestroID, $playerName, $startNo, $endNo);
|
$playerList = get_player_list_page($maestroID, $playerName, $startNo, $endNo);
|
||||||
if($playerList.length === 0) {
|
|
||||||
set_error_message("해당 이름으로 등록된 학생 없음");
|
|
||||||
send_result_fail();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_data("playerList", $playerList);
|
set_data("playerList", $playerList);
|
||||||
send_result_success();
|
send_result_success();
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ include "./../lib/util_app.php";
|
|||||||
|
|
||||||
|
|
||||||
$replyJSON = get_history_record($maestro_id, $app_id, $player_id, $date);
|
$replyJSON = get_history_record($maestro_id, $app_id, $player_id, $date);
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "히스토리 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
||||||
$db_conn->close();
|
$db_conn->close();
|
||||||
@@ -52,7 +47,6 @@ function get_history_record($maestro_id, $app_id, $player_id, $date) {
|
|||||||
$history_record['HighScore'] = $score;
|
$history_record['HighScore'] = $score;
|
||||||
$history_record['AppName'] = $app_name;
|
$history_record['AppName'] = $app_name;
|
||||||
array_push($history_record_array, $history_record);
|
array_push($history_record_array, $history_record);
|
||||||
array_push($return_array, $history_record);
|
|
||||||
}
|
}
|
||||||
$return_array['history'] = $history_record_array;
|
$return_array['history'] = $history_record_array;
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ include "./../lib/util_app.php";
|
|||||||
|
|
||||||
|
|
||||||
$replyJSON = get_ranking_record_day($maestro_id, $date, $app_id);
|
$replyJSON = get_ranking_record_day($maestro_id, $date, $app_id);
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "오늘의 랭킹 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
||||||
$db_conn->close();
|
$db_conn->close();
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ include "./../lib/util_app.php";
|
|||||||
|
|
||||||
|
|
||||||
$replyJSON = get_ranking_record_hour($maestro_id, $date, $time, $app_id);
|
$replyJSON = get_ranking_record_hour($maestro_id, $date, $time, $app_id);
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "수업시간 랭킹 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
||||||
$db_conn->close();
|
$db_conn->close();
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ include "./../lib/util_app.php";
|
|||||||
|
|
||||||
|
|
||||||
$replyJSON = get_ranking_record_month($maestro_id, $date, $time, $app_id);
|
$replyJSON = get_ranking_record_month($maestro_id, $date, $time, $app_id);
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "이달의 랭킹 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
||||||
$db_conn->close();
|
$db_conn->close();
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ include "./../lib/app_highest_record.php";
|
|||||||
|
|
||||||
$replyJSON = array();
|
$replyJSON = array();
|
||||||
$replyJSON["AppHighestRecord"] = get_app_highest_record($maestro_id, $app_id, $player_id);
|
$replyJSON["AppHighestRecord"] = get_app_highest_record($maestro_id, $app_id, $player_id);
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "히스토리 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
echo json_encode($replyJSON, JSON_UNESCAPED_UNICODE);
|
||||||
$db_conn->close();
|
$db_conn->close();
|
||||||
|
|||||||
@@ -5,36 +5,15 @@ include "./../lib/send_reply_json.php";
|
|||||||
include "./../lib/maestro_account_info.php";
|
include "./../lib/maestro_account_info.php";
|
||||||
include "./../setup/connect_db.php";
|
include "./../setup/connect_db.php";
|
||||||
|
|
||||||
$maestro_id = $_POST["MaestroID"];
|
$maestro_id = (int)$_POST["MaestroID"];
|
||||||
$start_date = $_POST["StartDate"];
|
$start_date = $_POST["StartDate"];
|
||||||
$end_date = $_POST["EndDate"];
|
$end_date = $_POST["EndDate"];
|
||||||
$player_name_list = $_POST["PlayerNameList"];
|
$player_name = trim($_POST["PlayerNameList"]);
|
||||||
$app_id = $_POST["AppID"];
|
$app_id = (int)$_POST["AppID"];
|
||||||
$limit_count = $_POST["LimitCount"];
|
$limit_count = (int)$_POST["LimitCount"];
|
||||||
|
|
||||||
// echo "start_date : ".$start_date."\n";
|
$recordTotalCount = get_player_record_total_count($maestro_id, $start_date, $end_date, $player_name, $app_id);
|
||||||
// echo "end_date : ".$end_date."\n";
|
$replyJSON = get_player_record_list($maestro_id, $start_date, $end_date, $player_name, $app_id, $limit_count);
|
||||||
// echo "player_name_list : ".$player_name_list."\n";
|
|
||||||
// echo "app_id : ".$app_id."\n";
|
|
||||||
// echo "limit_count : ".$limit_count."\n";
|
|
||||||
// echo "\n";
|
|
||||||
|
|
||||||
$date_statement = makeDateStatement($start_date, $end_date);
|
|
||||||
$player_name_statement = makePlayerNameStatement($player_name_list);
|
|
||||||
$subject_statement = makeSubjectSentence($app_id);
|
|
||||||
// echo "maestro : ".$maestro_id."\n";
|
|
||||||
// echo "date : ".$date_statement."\n";
|
|
||||||
// echo "player_name : ".$player_name_statement."\n";
|
|
||||||
// echo "subject : ".$subject_statement."\n";
|
|
||||||
$where_statement = make_where_statement($date_statement, $player_name_statement, $subject_statement);
|
|
||||||
|
|
||||||
$recordTotalCount = get_player_record_total_count($maestro_id, $where_statement);
|
|
||||||
$replyJSON = get_player_record_list($maestro_id, $where_statement, $limit_count);
|
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "오늘의 랭킹 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_data("RecordTotalCount", $recordTotalCount);
|
set_data("RecordTotalCount", $recordTotalCount);
|
||||||
set_data("RecordList", $replyJSON);
|
set_data("RecordList", $replyJSON);
|
||||||
@@ -42,117 +21,52 @@ send_result_success();
|
|||||||
exit;
|
exit;
|
||||||
|
|
||||||
|
|
||||||
|
function get_subject_app_range($app_id) {
|
||||||
|
switch ($app_id) {
|
||||||
function makeDateStatement($start_date, $end_date) {
|
case 0: return null;
|
||||||
return " '".$start_date."' <= BR.RecordDateTime AND BR.RecordDateTime <= DATE_ADD('".$end_date."', INTERVAL 1 DAY)";
|
case 1000: return array(1, 8);
|
||||||
// return " '2017-01-01' < BR.RecordDateTime AND BR.RecordDateTime < '".$end_date."'";
|
case 1001: return array(11, 18);
|
||||||
}
|
case 1002: return array(21, 30);
|
||||||
|
case 1003: return array(31, 40);
|
||||||
function makeSubjectSentence($app_id) {
|
default: return array($app_id, $app_id);
|
||||||
if($app_id == 0)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
$subject_statement = "";
|
|
||||||
if(is_subject_bundle_item($app_id)) {
|
|
||||||
switch($app_id) {
|
|
||||||
case 1000: // korean practice
|
|
||||||
$subject_statement = $subject_statement."A.AppID < 9";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1001: // english practice
|
|
||||||
$subject_statement = $subject_statement."10 < A.AppID AND A.AppID < 19";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1002: // korean test
|
|
||||||
$subject_statement = $subject_statement."20 < A.AppID AND A.AppID < 31";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1003: // english test
|
|
||||||
$subject_statement = $subject_statement."30 < A.AppID AND A.AppID < 41";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
$subject_statement = $subject_statement."A.AppID = ".$app_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $subject_statement." AND BR.AppID = A.AppID";
|
|
||||||
}
|
|
||||||
|
|
||||||
function makePlayerNameStatement($player_name_list) {
|
|
||||||
$player_name_statement = "BR.PlayerID = P.PlayerID";
|
|
||||||
if(strlen($player_name_list) > 0)
|
|
||||||
$player_name_statement = $player_name_statement." AND P.Name LIKE '%".$player_name_list."%'";
|
|
||||||
|
|
||||||
return $player_name_statement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function is_subject_bundle_item($app_id) {
|
function get_player_record_total_count($maestro_id, $start_date, $end_date, $player_name, $app_id) {
|
||||||
if($app_id >= 1000)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function make_where_statement($date_statement, $player_name_statement, $subject_statement) {
|
|
||||||
// add date condition
|
|
||||||
$where_statement = $date_statement;
|
|
||||||
if(strlen($where_statement) > 0 and strlen($player_name_statement) > 0)
|
|
||||||
$where_statement = $where_statement." AND ";
|
|
||||||
|
|
||||||
// add player name condition
|
|
||||||
$where_statement = $where_statement.$player_name_statement;
|
|
||||||
|
|
||||||
// add subject condition
|
|
||||||
if(strlen($where_statement) > 0 and strlen($subject_statement) > 0)
|
|
||||||
$where_statement = $where_statement." AND ";
|
|
||||||
$where_statement = $where_statement.$subject_statement;
|
|
||||||
|
|
||||||
return $where_statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
function get_player_record_count($maestro_id, $where_statement, $limit_count) {
|
|
||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
"BR.MaestroID = ?",
|
||||||
|
"BR.RecordDateTime >= DATE(?)",
|
||||||
|
"BR.RecordDateTime < DATE(?) + INTERVAL 1 DAY",
|
||||||
|
"BR.PlayerID = P.PlayerID",
|
||||||
|
"BR.AppID = A.AppID",
|
||||||
|
);
|
||||||
|
$types = "iss";
|
||||||
|
$params = array($maestro_id, $start_date, $end_date);
|
||||||
|
|
||||||
|
if (strlen($player_name) > 0) {
|
||||||
|
$where[] = "P.Name LIKE ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "%" . $player_name . "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
$range = get_subject_app_range($app_id);
|
||||||
|
if ($range !== null) {
|
||||||
|
$where[] = "BR.AppID BETWEEN ? AND ?";
|
||||||
|
$types .= "ii";
|
||||||
|
$params[] = $range[0];
|
||||||
|
$params[] = $range[1];
|
||||||
|
}
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM best_record BR, player P, app A
|
FROM best_record BR, player P, app A
|
||||||
WHERE BR.MaestroID = ? AND ".$where_statement."
|
WHERE " . implode(" AND ", $where);
|
||||||
";
|
|
||||||
|
|
||||||
if($limit_count > 0)
|
|
||||||
$query = $query." LIMIT ".$limit_count;
|
|
||||||
|
|
||||||
// echo $query;
|
|
||||||
// return;
|
|
||||||
|
|
||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param("i", $maestro_id);
|
$stmt->bind_param($types, ...$params);
|
||||||
$stmt->execute();
|
|
||||||
$stmt->bind_result($record_count);
|
|
||||||
$stmt->fetch();
|
|
||||||
$stmt->close();
|
|
||||||
|
|
||||||
return $record_count;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function get_player_record_total_count($maestro_id, $where_statement) {
|
|
||||||
global $db_conn;
|
|
||||||
|
|
||||||
$query = "
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM best_record BR, player P, app A
|
|
||||||
WHERE BR.MaestroID = ? AND ".$where_statement."
|
|
||||||
";
|
|
||||||
|
|
||||||
// echo $query;
|
|
||||||
// return;
|
|
||||||
|
|
||||||
$stmt = $db_conn->prepare($query);
|
|
||||||
$stmt->bind_param("i", $maestro_id);
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($record_count);
|
$stmt->bind_result($record_count);
|
||||||
$stmt->fetch();
|
$stmt->fetch();
|
||||||
@@ -161,38 +75,63 @@ function get_player_record_total_count($maestro_id, $where_statement) {
|
|||||||
return $record_count;
|
return $record_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|
||||||
|
function get_player_record_list($maestro_id, $start_date, $end_date, $player_name, $app_id, $limit_count) {
|
||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
"BR.MaestroID = ?",
|
||||||
|
"BR.RecordDateTime >= DATE(?)",
|
||||||
|
"BR.RecordDateTime < DATE(?) + INTERVAL 1 DAY",
|
||||||
|
"BR.PlayerID = P.PlayerID",
|
||||||
|
"BR.AppID = A.AppID",
|
||||||
|
);
|
||||||
|
$types = "iss";
|
||||||
|
$params = array($maestro_id, $start_date, $end_date);
|
||||||
|
|
||||||
|
if (strlen($player_name) > 0) {
|
||||||
|
$where[] = "P.Name LIKE ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "%" . $player_name . "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
$range = get_subject_app_range($app_id);
|
||||||
|
if ($range !== null) {
|
||||||
|
$where[] = "BR.AppID BETWEEN ? AND ?";
|
||||||
|
$types .= "ii";
|
||||||
|
$params[] = $range[0];
|
||||||
|
$params[] = $range[1];
|
||||||
|
}
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT BR.BestRecordID AS Id, DATE(BR.RecordDateTime) AS Date, TIME(BR.RecordDateTime) AS Time,
|
SELECT BR.BestRecordID, DATE(BR.RecordDateTime), TIME(BR.RecordDateTime),
|
||||||
P.Name AS Name, A.KoreanName AS Subject, BR.BestRecord AS Record
|
P.Name, A.KoreanName, BR.BestRecord
|
||||||
FROM best_record BR, player P, app A
|
FROM best_record BR, player P, app A
|
||||||
WHERE BR.MaestroID = ? AND ".$where_statement."
|
WHERE " . implode(" AND ", $where) . "
|
||||||
ORDER BY BR.RecordDateTime DESC
|
ORDER BY BR.RecordDateTime DESC";
|
||||||
";
|
|
||||||
|
|
||||||
if($limit_count > 0)
|
if ($limit_count > 0) {
|
||||||
$query = $query." LIMIT ".$limit_count;
|
$query .= " LIMIT ?";
|
||||||
|
$types .= "i";
|
||||||
// echo $query;
|
$params[] = $limit_count;
|
||||||
// return;
|
}
|
||||||
|
|
||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param('i', $maestro_id);
|
$stmt->bind_param($types, ...$params);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($id, $date, $time, $player_name, $subject, $record);
|
$stmt->bind_result($id, $date, $time, $player_name_result, $subject, $record);
|
||||||
|
|
||||||
$record_array = array();
|
$record_array = array();
|
||||||
while($stmt->fetch()) {
|
while($stmt->fetch()) {
|
||||||
$record_date['Id'] = $id;
|
$record_date['Id'] = $id;
|
||||||
$record_date['Date'] = $date;
|
$record_date['Date'] = $date;
|
||||||
$record_date['Time'] = $time;
|
$record_date['Time'] = $time;
|
||||||
$record_date['PlayerName'] = $player_name;
|
$record_date['PlayerName'] = $player_name_result;
|
||||||
$record_date['Subject'] = $subject;
|
$record_date['Subject'] = $subject;
|
||||||
$record_date['Record'] = $record;
|
$record_date['Record'] = $record;
|
||||||
array_push($record_array, $record_date);
|
array_push($record_array, $record_date);
|
||||||
}
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
return $record_array;
|
return $record_array;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,36 +5,15 @@ include "./../lib/send_reply_json.php";
|
|||||||
include "./../lib/maestro_account_info.php";
|
include "./../lib/maestro_account_info.php";
|
||||||
include "./../setup/connect_db.php";
|
include "./../setup/connect_db.php";
|
||||||
|
|
||||||
$maestro_id = $_POST["MaestroID"];
|
$maestro_id = (int)$_POST["MaestroID"];
|
||||||
$start_date = $_POST["StartDate"];
|
$start_date = $_POST["StartDate"];
|
||||||
$end_date = $_POST["EndDate"];
|
$end_date = $_POST["EndDate"];
|
||||||
$player_name_list = $_POST["PlayerNameList"];
|
$player_name = trim($_POST["PlayerNameList"]);
|
||||||
$app_id = $_POST["AppID"];
|
$app_id = (int)$_POST["AppID"];
|
||||||
$limit_count = $_POST["LimitCount"];
|
$limit_count = (int)$_POST["LimitCount"];
|
||||||
|
|
||||||
// echo "start_date : ".$start_date."\n";
|
$recordTotalCount = get_player_record_total_count($maestro_id, $start_date, $end_date, $player_name, $app_id);
|
||||||
// echo "end_date : ".$end_date."\n";
|
$replyJSON = get_player_record_list($maestro_id, $start_date, $end_date, $player_name, $app_id, $limit_count);
|
||||||
// echo "player_name_list : ".$player_name_list."\n";
|
|
||||||
// echo "app_id : ".$app_id."\n";
|
|
||||||
// echo "limit_count : ".$limit_count."\n";
|
|
||||||
// echo "\n";
|
|
||||||
|
|
||||||
$date_statement = makeDateStatement($start_date, $end_date);
|
|
||||||
$player_name_statement = makePlayerNameStatement($player_name_list);
|
|
||||||
$subject_statement = makeSubjectSentence($app_id);
|
|
||||||
// echo "maestro : ".$maestro_id."\n";
|
|
||||||
// echo "date : ".$date_statement."\n";
|
|
||||||
// echo "player_name : ".$player_name_statement."\n";
|
|
||||||
// echo "subject : ".$subject_statement."\n";
|
|
||||||
$where_statement = make_where_statement($date_statement, $player_name_statement, $subject_statement);
|
|
||||||
|
|
||||||
$recordTotalCount = get_player_record_total_count($maestro_id, $where_statement);
|
|
||||||
$replyJSON = get_player_record_list($maestro_id, $where_statement, $limit_count);
|
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "오늘의 랭킹 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_data("RecordTotalCount", $recordTotalCount);
|
set_data("RecordTotalCount", $recordTotalCount);
|
||||||
set_data("RecordList", $replyJSON);
|
set_data("RecordList", $replyJSON);
|
||||||
@@ -42,119 +21,52 @@ send_result_success();
|
|||||||
exit;
|
exit;
|
||||||
|
|
||||||
|
|
||||||
|
function get_subject_app_range($app_id) {
|
||||||
|
switch ($app_id) {
|
||||||
function makeDateStatement($start_date, $end_date) {
|
case 0: return null;
|
||||||
return " '".$start_date."' < LS.ScoreDateTime AND LS.ScoreDateTime < '".$end_date."'";
|
case 1000: return array(1, 8);
|
||||||
// return " '2017-01-01' < LS.ScoreDateTime AND LS.ScoreDateTime < '".$end_date."'";
|
case 1001: return array(11, 18);
|
||||||
}
|
case 1002: return array(21, 30);
|
||||||
|
case 1003: return array(31, 40);
|
||||||
function makeSubjectSentence($app_id) {
|
case 3000: return null;
|
||||||
if($app_id == 0)
|
default: return array($app_id, $app_id);
|
||||||
return "";
|
|
||||||
else if($app_id == 3000)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
$subject_statement = "";
|
|
||||||
if(is_subject_bundle_item($app_id)) {
|
|
||||||
switch($app_id) {
|
|
||||||
case 1000: // korean practice
|
|
||||||
$subject_statement = $subject_statement."A.AppID < 9";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1001: // english practice
|
|
||||||
$subject_statement = $subject_statement."10 < A.AppID AND A.AppID < 19";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1002: // korean test
|
|
||||||
$subject_statement = $subject_statement."20 < A.AppID AND A.AppID < 31";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1003: // english test
|
|
||||||
$subject_statement = $subject_statement."30 < A.AppID AND A.AppID < 41";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
$subject_statement = $subject_statement."A.AppID = ".$app_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $subject_statement." AND LS.AppID = A.AppID";
|
|
||||||
}
|
|
||||||
|
|
||||||
function makePlayerNameStatement($player_name_list) {
|
|
||||||
$player_name_statement = "LS.PlayerID = P.PlayerID";
|
|
||||||
if(strlen($player_name_list) > 0)
|
|
||||||
$player_name_statement = $player_name_statement." AND P.Name LIKE '%".$player_name_list."%'";
|
|
||||||
|
|
||||||
return $player_name_statement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function is_subject_bundle_item($app_id) {
|
function get_player_record_total_count($maestro_id, $start_date, $end_date, $player_name, $app_id) {
|
||||||
if($app_id >= 1000)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function make_where_statement($date_statement, $player_name_statement, $subject_statement) {
|
|
||||||
// add date condition
|
|
||||||
$where_statement = $date_statement;
|
|
||||||
if(strlen($where_statement) > 0 and strlen($player_name_statement) > 0)
|
|
||||||
$where_statement = $where_statement." AND ";
|
|
||||||
|
|
||||||
// add player name condition
|
|
||||||
$where_statement = $where_statement.$player_name_statement;
|
|
||||||
|
|
||||||
// add subject condition
|
|
||||||
if(strlen($where_statement) > 0 and strlen($subject_statement) > 0)
|
|
||||||
$where_statement = $where_statement." AND ";
|
|
||||||
$where_statement = $where_statement.$subject_statement;
|
|
||||||
|
|
||||||
return $where_statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
function get_player_record_count($maestro_id, $where_statement, $limit_count) {
|
|
||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
"LS.MaestroID = ?",
|
||||||
|
"LS.ScoreDateTime >= DATE(?)",
|
||||||
|
"LS.ScoreDateTime < DATE(?) + INTERVAL 1 DAY",
|
||||||
|
"LS.PlayerID = P.PlayerID",
|
||||||
|
);
|
||||||
|
$types = "iss";
|
||||||
|
$params = array($maestro_id, $start_date, $end_date);
|
||||||
|
|
||||||
|
if (strlen($player_name) > 0) {
|
||||||
|
$where[] = "P.Name LIKE ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "%" . $player_name . "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
$range = get_subject_app_range($app_id);
|
||||||
|
if ($range !== null) {
|
||||||
|
$where[] = "LS.AppID BETWEEN ? AND ?";
|
||||||
|
$types .= "ii";
|
||||||
|
$params[] = $range[0];
|
||||||
|
$params[] = $range[1];
|
||||||
|
}
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM license_score LS, player P
|
FROM license_score LS, player P
|
||||||
WHERE LS.MaestroID = ? AND ".$where_statement."
|
WHERE " . implode(" AND ", $where);
|
||||||
";
|
|
||||||
|
|
||||||
if($limit_count > 0)
|
|
||||||
$query = $query." LIMIT ".$limit_count;
|
|
||||||
|
|
||||||
// echo $query;
|
|
||||||
// return;
|
|
||||||
|
|
||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param("i", $maestro_id);
|
$stmt->bind_param($types, ...$params);
|
||||||
$stmt->execute();
|
|
||||||
$stmt->bind_result($record_count);
|
|
||||||
$stmt->fetch();
|
|
||||||
$stmt->close();
|
|
||||||
|
|
||||||
return $record_count;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function get_player_record_total_count($maestro_id, $where_statement) {
|
|
||||||
global $db_conn;
|
|
||||||
|
|
||||||
$query = "
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM license_score LS, player P
|
|
||||||
WHERE LS.MaestroID = ? AND ".$where_statement."
|
|
||||||
";
|
|
||||||
|
|
||||||
// echo $query;
|
|
||||||
// return;
|
|
||||||
|
|
||||||
$stmt = $db_conn->prepare($query);
|
|
||||||
$stmt->bind_param("i", $maestro_id);
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($record_count);
|
$stmt->bind_result($record_count);
|
||||||
$stmt->fetch();
|
$stmt->fetch();
|
||||||
@@ -163,38 +75,62 @@ function get_player_record_total_count($maestro_id, $where_statement) {
|
|||||||
return $record_count;
|
return $record_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|
||||||
|
function get_player_record_list($maestro_id, $start_date, $end_date, $player_name, $app_id, $limit_count) {
|
||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
"LS.MaestroID = ?",
|
||||||
|
"LS.ScoreDateTime >= DATE(?)",
|
||||||
|
"LS.ScoreDateTime < DATE(?) + INTERVAL 1 DAY",
|
||||||
|
"LS.PlayerID = P.PlayerID",
|
||||||
|
);
|
||||||
|
$types = "iss";
|
||||||
|
$params = array($maestro_id, $start_date, $end_date);
|
||||||
|
|
||||||
|
if (strlen($player_name) > 0) {
|
||||||
|
$where[] = "P.Name LIKE ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "%" . $player_name . "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
$range = get_subject_app_range($app_id);
|
||||||
|
if ($range !== null) {
|
||||||
|
$where[] = "LS.AppID BETWEEN ? AND ?";
|
||||||
|
$types .= "ii";
|
||||||
|
$params[] = $range[0];
|
||||||
|
$params[] = $range[1];
|
||||||
|
}
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT LS.LicenseScoreID AS ID, DATE(LS.ScoreDateTime) AS Date, TIME(LS.ScoreDateTime) AS Time,
|
SELECT LS.LicenseScoreID, DATE(LS.ScoreDateTime), TIME(LS.ScoreDateTime),
|
||||||
P.Name AS Name, LS.SubjectName AS Subject, LS.Score AS Record
|
P.Name, LS.SubjectName, LS.Score
|
||||||
FROM license_score LS, player P
|
FROM license_score LS, player P
|
||||||
WHERE LS.MaestroID = ? AND ".$where_statement."
|
WHERE " . implode(" AND ", $where) . "
|
||||||
ORDER BY LS.ScoreDateTime DESC
|
ORDER BY LS.ScoreDateTime DESC";
|
||||||
";
|
|
||||||
|
|
||||||
if($limit_count > 0)
|
if ($limit_count > 0) {
|
||||||
$query = $query." LIMIT ".$limit_count;
|
$query .= " LIMIT ?";
|
||||||
|
$types .= "i";
|
||||||
// echo $query;
|
$params[] = $limit_count;
|
||||||
// return;
|
}
|
||||||
|
|
||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param('i', $maestro_id);
|
$stmt->bind_param($types, ...$params);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($id, $date, $time, $player_name, $subject, $record);
|
$stmt->bind_result($id, $date, $time, $player_name_result, $subject, $record);
|
||||||
|
|
||||||
$record_array = array();
|
$record_array = array();
|
||||||
while($stmt->fetch()) {
|
while($stmt->fetch()) {
|
||||||
$record_date['Id'] = $id;
|
$record_date['Id'] = $id;
|
||||||
$record_date['Date'] = $date;
|
$record_date['Date'] = $date;
|
||||||
$record_date['Time'] = $time;
|
$record_date['Time'] = $time;
|
||||||
$record_date['PlayerName'] = $player_name;
|
$record_date['PlayerName'] = $player_name_result;
|
||||||
$record_date['Subject'] = $subject;
|
$record_date['Subject'] = $subject;
|
||||||
$record_date['Record'] = $record;
|
$record_date['Record'] = $record;
|
||||||
array_push($record_array, $record_date);
|
array_push($record_array, $record_date);
|
||||||
}
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
return $record_array;
|
return $record_array;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,37 +5,15 @@ include "./../lib/send_reply_json.php";
|
|||||||
include "./../lib/maestro_account_info.php";
|
include "./../lib/maestro_account_info.php";
|
||||||
include "./../setup/connect_db.php";
|
include "./../setup/connect_db.php";
|
||||||
|
|
||||||
$maestro_id = $_POST["MaestroID"];
|
$maestro_id = (int)$_POST["MaestroID"];
|
||||||
$start_date = $_POST["StartDate"];
|
$start_date = $_POST["StartDate"];
|
||||||
$end_date = $_POST["EndDate"];
|
$end_date = $_POST["EndDate"];
|
||||||
$player_name_list = $_POST["PlayerNameList"];
|
$player_name = trim($_POST["PlayerNameList"]);
|
||||||
$app_id = $_POST["AppID"];
|
$app_id = (int)$_POST["AppID"];
|
||||||
$limit_count = $_POST["LimitCount"];
|
$limit_count = (int)$_POST["LimitCount"];
|
||||||
// echo "start_date : ".$start_date."\n";
|
|
||||||
// echo "end_date : ".$end_date."\n";
|
|
||||||
// echo "player_name_list : ".$player_name_list."\n";
|
|
||||||
// echo "app_id : ".$app_id."\n";
|
|
||||||
// echo "limit_count : ".$limit_count."\n";
|
|
||||||
// echo "\n";
|
|
||||||
|
|
||||||
$date_statement = makeDateStatement($start_date, $end_date);
|
$recordTotalCount = get_player_record_total_count($maestro_id, $start_date, $end_date, $player_name, $app_id);
|
||||||
$player_name_statement = makePlayerNameStatement($player_name_list);
|
$replyJSON = get_player_record_list($maestro_id, $start_date, $end_date, $player_name, $app_id, $limit_count);
|
||||||
$subject_statement = makeSubjectSentence($app_id);
|
|
||||||
$where_statement = make_where_statement($date_statement, $player_name_statement, $subject_statement);
|
|
||||||
// echo "maestro : ".$maestro_id."\n";
|
|
||||||
// echo "date : ".$date_statement."\n";
|
|
||||||
// echo "player_name : ".$player_name_statement."\n";
|
|
||||||
// echo "subject : ".$subject_statement."\n";
|
|
||||||
// echo "where_statement : ".$where_statement."\n";
|
|
||||||
|
|
||||||
$recordTotalCount = get_player_record_total_count($maestro_id, $where_statement);
|
|
||||||
// echo "recordTotalCount : ".$recordTotalCount."\n";
|
|
||||||
$replyJSON = get_player_record_list($maestro_id, $where_statement, $limit_count);
|
|
||||||
if($replyJSON.length === 0) {
|
|
||||||
send_error_message($replyJSON, "오늘의 랭킹 기록 없음");
|
|
||||||
$db_conn->close();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_data("RecordTotalCount", $recordTotalCount);
|
set_data("RecordTotalCount", $recordTotalCount);
|
||||||
set_data("RecordList", $replyJSON);
|
set_data("RecordList", $replyJSON);
|
||||||
@@ -43,84 +21,48 @@ send_result_success();
|
|||||||
exit;
|
exit;
|
||||||
|
|
||||||
|
|
||||||
|
function get_player_record_total_count($maestro_id, $start_date, $end_date, $player_name, $app_id) {
|
||||||
|
|
||||||
function makeDateStatement($start_date, $end_date) {
|
|
||||||
return " '".$start_date."' <= TE.RecordDateTime AND TE.RecordDateTime <= DATE_ADD('".$end_date."', INTERVAL 1 DAY)";
|
|
||||||
// return " '".$start_date."' <= TE.RecordDateTime AND TE.RecordDateTime <= '".$end_date."'";
|
|
||||||
// return " '2017-01-01' < TE.RecordDateTime";
|
|
||||||
// return " '2017-01-01' < TE.RecordDateTime AND TE.RecordDateTime < '".$end_date."'";
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeSubjectSentence($app_id) {
|
|
||||||
if($app_id == 0)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
$subject_statement = "";
|
|
||||||
if(is_subject_bundle_item($app_id)) {
|
|
||||||
switch($app_id) {
|
|
||||||
case 1004: // korean exam
|
|
||||||
$subject_statement = $subject_statement."WR.Language='korean'";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1005: // english exam
|
|
||||||
$subject_statement = $subject_statement."WR.Language='english'";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$subject_statement = $subject_statement."WR.WritingID=".$app_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $subject_statement." AND TE.WritingID=WR.WritingID";
|
|
||||||
}
|
|
||||||
|
|
||||||
function makePlayerNameStatement($player_name_list) {
|
|
||||||
$player_name_statement = "TE.PlayerID=P.PlayerID";
|
|
||||||
if(strlen($player_name_list) > 0)
|
|
||||||
$player_name_statement = $player_name_statement." AND P.Name LIKE '%".$player_name_list."%'";
|
|
||||||
|
|
||||||
return $player_name_statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function is_subject_bundle_item($app_id) {
|
|
||||||
if($app_id >= 1000)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function make_where_statement($date_statement, $player_name_statement, $subject_statement) {
|
|
||||||
// add date condition
|
|
||||||
$where_statement = $date_statement;
|
|
||||||
if(strlen($where_statement) > 0 and strlen($player_name_statement) > 0)
|
|
||||||
$where_statement = $where_statement." AND ";
|
|
||||||
|
|
||||||
// add player name condition
|
|
||||||
$where_statement = $where_statement.$player_name_statement;
|
|
||||||
|
|
||||||
// add subject condition
|
|
||||||
if(strlen($where_statement) > 0 and strlen($subject_statement) > 0)
|
|
||||||
$where_statement = $where_statement." AND ";
|
|
||||||
$where_statement = $where_statement.$subject_statement;
|
|
||||||
|
|
||||||
return $where_statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_player_record_total_count($maestro_id, $where_statement) {
|
|
||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
"TE.MaestroID = ?",
|
||||||
|
"TE.RecordDateTime >= DATE(?)",
|
||||||
|
"TE.RecordDateTime < DATE(?) + INTERVAL 1 DAY",
|
||||||
|
"TE.PlayerID = P.PlayerID",
|
||||||
|
"TE.WritingID = WR.WritingID",
|
||||||
|
);
|
||||||
|
$types = "iss";
|
||||||
|
$params = array($maestro_id, $start_date, $end_date);
|
||||||
|
|
||||||
|
if (strlen($player_name) > 0) {
|
||||||
|
$where[] = "P.Name LIKE ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "%" . $player_name . "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($app_id > 0) {
|
||||||
|
if ($app_id == 1004) {
|
||||||
|
$where[] = "WR.Language = ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "korean";
|
||||||
|
} else if ($app_id == 1005) {
|
||||||
|
$where[] = "WR.Language = ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "english";
|
||||||
|
} else {
|
||||||
|
$where[] = "WR.WritingID = ?";
|
||||||
|
$types .= "i";
|
||||||
|
$params[] = $app_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM typing_exam_record TE, player P, writing WR
|
FROM typing_exam_record TE, player P, writing WR
|
||||||
WHERE TE.MaestroID = ? AND ".$where_statement."
|
WHERE " . implode(" AND ", $where);
|
||||||
";
|
|
||||||
|
|
||||||
// echo $query;
|
|
||||||
// return;
|
|
||||||
|
|
||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param("i", $maestro_id);
|
$stmt->bind_param($types, ...$params);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($record_count);
|
$stmt->bind_result($record_count);
|
||||||
$stmt->fetch();
|
$stmt->fetch();
|
||||||
@@ -129,38 +71,71 @@ function get_player_record_total_count($maestro_id, $where_statement) {
|
|||||||
return $record_count;
|
return $record_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|
||||||
|
function get_player_record_list($maestro_id, $start_date, $end_date, $player_name, $app_id, $limit_count) {
|
||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
|
$where = array(
|
||||||
|
"TE.MaestroID = ?",
|
||||||
|
"TE.RecordDateTime >= DATE(?)",
|
||||||
|
"TE.RecordDateTime < DATE(?) + INTERVAL 1 DAY",
|
||||||
|
"TE.PlayerID = P.PlayerID",
|
||||||
|
"TE.WritingID = WR.WritingID",
|
||||||
|
);
|
||||||
|
$types = "iss";
|
||||||
|
$params = array($maestro_id, $start_date, $end_date);
|
||||||
|
|
||||||
|
if (strlen($player_name) > 0) {
|
||||||
|
$where[] = "P.Name LIKE ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "%" . $player_name . "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($app_id > 0) {
|
||||||
|
if ($app_id == 1004) {
|
||||||
|
$where[] = "WR.Language = ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "korean";
|
||||||
|
} else if ($app_id == 1005) {
|
||||||
|
$where[] = "WR.Language = ?";
|
||||||
|
$types .= "s";
|
||||||
|
$params[] = "english";
|
||||||
|
} else {
|
||||||
|
$where[] = "WR.WritingID = ?";
|
||||||
|
$types .= "i";
|
||||||
|
$params[] = $app_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT TE.TypingExamRecordID as Id, DATE(TE.RecordDateTime) AS Date, TIME(TE.RecordDateTime) AS Time,
|
SELECT TE.TypingExamRecordID, DATE(TE.RecordDateTime), TIME(TE.RecordDateTime),
|
||||||
P.Name AS Name, WR.Name AS Subject, TE.Record AS Record
|
P.Name, WR.Name, TE.Record
|
||||||
FROM typing_exam_record TE, player P, writing WR
|
FROM typing_exam_record TE, player P, writing WR
|
||||||
WHERE TE.MaestroID = ? AND TE.WritingID=WR.WritingID AND TE.PlayerID=P.PlayerID AND ".$where_statement."
|
WHERE " . implode(" AND ", $where) . "
|
||||||
ORDER BY TE.RecordDateTime DESC
|
ORDER BY TE.RecordDateTime DESC";
|
||||||
";
|
|
||||||
|
|
||||||
if($limit_count > 0)
|
if ($limit_count > 0) {
|
||||||
$query = $query." LIMIT ".$limit_count;
|
$query .= " LIMIT ?";
|
||||||
|
$types .= "i";
|
||||||
// echo $query;
|
$params[] = $limit_count;
|
||||||
// return;
|
}
|
||||||
|
|
||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param('i', $maestro_id);
|
$stmt->bind_param($types, ...$params);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($id, $date, $time, $player_name, $subject, $record);
|
$stmt->bind_result($id, $date, $time, $player_name_result, $subject, $record);
|
||||||
|
|
||||||
$record_array = array();
|
$record_array = array();
|
||||||
while($stmt->fetch()) {
|
while($stmt->fetch()) {
|
||||||
$record_date['Id'] = $id;
|
$record_date['Id'] = $id;
|
||||||
$record_date['Date'] = $date;
|
$record_date['Date'] = $date;
|
||||||
$record_date['Time'] = $time;
|
$record_date['Time'] = $time;
|
||||||
$record_date['PlayerName'] = $player_name;
|
$record_date['PlayerName'] = $player_name_result;
|
||||||
$record_date['Subject'] = $subject;
|
$record_date['Subject'] = $subject;
|
||||||
$record_date['Record'] = $record;
|
$record_date['Record'] = $record;
|
||||||
array_push($record_array, $record_date);
|
array_push($record_array, $record_date);
|
||||||
}
|
}
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
return $record_array;
|
return $record_array;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user