Web API Controller Documentation
File: /controllers/api_web.php
Purpose: RESTful API endpoints for web client registration, authentication, and project data access
Last Updated: December 20, 2024
Total Functions: 8+
Lines of Code: ~410
---
๐ Overview
The Web API Controller provides RESTful endpoints for external web clients to interact with the ERP system. It handles:
- โข User registration and authentication for web clients
- โข Project data retrieval with financial calculations
- โข Client profile management
- โข Project reporting with comprehensive financial data
- โข Cross-origin resource sharing (CORS) support
- โข JSON-based request/response handling
- โข Mobile app support (iOS/Android)
Primary Functions
- โ Web client registration with validation
- โ User authentication and session management
- โ Project listing and detailed project reports
- โ Financial calculations (expenses, income, profit/loss)
- โ Project stage management and file handling
- โ Profile management and updates
- โ CORS support for cross-domain requests
- โ Multi-platform support (iOS/Android compatibility)
Related Controllers
- โข projectController.php - Internal project management
- โข clientController.php - Customer management
- โข expensesController.php - Expense tracking
- โข incomeController.php - Income management
---
๐๏ธ Database Tables
Primary Tables (Direct Operations)
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **webclients** | Web client users | id, name, mobile, user_name, password, device_id, is_active | |
| **project** | Project master data | id, clientid, costcenterid, image, filepdf, expenses, income | |
| **webclientprojects** | Client-project access control | webclientid, projectid |
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **projectstages** | Project stage definitions | id, name | |
| **projectstagechosse** | Selected project stages | projectid, projectstageid, projectstageimages, projectstagefiles | |
| **projecttimetableitems** | Project timeline items | projectid, clause, amount, duration, startdate, enddate |
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **expenseexchange** | Project expenses | projectid, expensetype, thevalue, finalsupervision | |
| **projectexchmaterial** | Material exchanges | id, projectid | |
| **projectexchmaterialdetail** | Material exchange details | projectexchid, expensestypeid, totalbuyprice, finalsupervision | |
| **clientdebtchange** | Client payment history | clientid, clientdebtchangeamount, clientdebtchangedate, paySerialNo | |
| **income** | Project income records | costcenterid, incomeName, incomeValue | |
| **expensestype** | Expense type definitions | expensestypeid, expensestypename |
๐ Key Functions
1. register - Web Client Registration
Location: Line 68
Purpose: Register new web client with validation and duplicate checking
Function Signature:
// POST data expected
{
"name": "Client Name",
"mobile": "1234567890",
"user_name": "username",
"password": "password",
"device_id": "device_identifier"
}
Process Flow:
1. Input Validation:
if (!$name || !$mobile || !$user_name || !$password) {
return ['status' => 2, 'reason' => '๏บ๏ป๏บพ ุง๏ป๏บค๏ป๏ปฎ๏ป ุง๏ป๏ปค๏ป๏ป ๏ปฎ๏บ๏บ ๏ป๏บ๏บญ๏ป๏บ'];
}
```
2. **Duplicate Checks**:
```php
$check_username = R::count('webclients','user_name = ? AND is_active = 1', [$user_name]);
$check_mobile = R::count('webclients','mobile = ? AND is_active = 1', [$mobile]);
```
3. **Account Creation**:
```php
$row = R::dispense('webclients');
$row->name = $name;
$row->is_active = 1;
$row->addtoday = date('Y-m-d H:i:s');
$id = R::store($row);
```
**Response Format**:
json
{
"status": 1,
"reason": "ุชู ุฅูุดุงุก ุงูุญุณุงุจ ุจูุฌุงุญ",
"webclient_id": 123,
"name": "Client Name",
"mobile": "1234567890",
"user_name": "username"
}
---
### 2. **login** - User Authentication
**Location**: Line 129
**Purpose**: Authenticate web client using username/mobile and password
**Function Signature**:
php
// POST data expected
{
"username": "username_or_mobile",
"password": "password"
}
**Authentication Logic**:
php
$user = R::findOne('webclients', " (user_name = '" . $username . "' or mobile = '" . $mobile . "') and password = '" . $password . "' ");
if ($user->id > 0 && $user->is_active == 0) {
// Account exists but not activated
return ['status' => 1, 'reason' => '๏ป๏ป๏ปฎุง ๏ป๏ปข ๏ปณ๏บ๏ปข ๏บ๏ป๏ป๏ปด๏ป ๏บฃ๏บด๏บ๏บ๏ป ๏บ๏ป๏บช'];
} elseif ($user->id > 0) {
// Successful login
return ['status' => 1, 'reason' => '๏บ๏ปข ุง๏ป๏บช๏บง๏ปฎ๏ป ๏บ๏ปจ๏บ ๏บ๏บก'];
}
**Security Note**: โ ๏ธ This implementation has security vulnerabilities:
- Passwords stored in plain text
- SQL injection potential in concatenated query
- No rate limiting or brute force protection
---
### 3. **projectReport** - Comprehensive Project Report
**Location**: Line 177
**Purpose**: Generate detailed project report with financial calculations and file attachments
**Function Signature**:
php
// GET parameter: id (project ID)
**Report Components**:
**A. Project Basic Data**:
php
$project = R::getRow("select *, CONCAT('$src', project.image) as image,
CONCAT('$src', project.filepdf) as filepdf
from project where id = ? ", [$projectid]);
**B. Project Stages** (if enabled):
php
if ($project['projectstagesdata'] == 1) {
$projectstages = R::getAll("select * from projectstagechosse
where projectid = ? ", [$projectid]);
// Process stage images and files
foreach ($projectstages as $key => $stage) {
$projectstageimages = explode(',', $stage['projectstageimages']);
$images = [];
foreach ($projectstageimages as $image) {
$images[] = $src . $image;
}
$projectstages[$key]['images'] = $images;
}
}
**C. Financial Calculations**:
**Expense Calculation Logic**:
php
// Get material expenses
$projectexchmaterialdetails = R::getAll("
select *, sum(totalbuyprice) as totals, sum(finalsupervision) as finalsupervisions
from projectexchmaterialdetail JOIN projectexchmaterial
ON projectexchmaterialdetail.projectexchid = projectexchmaterial.id
WHERE projectexchmaterial.projectid = ?
group by projectexchmaterialdetail.expensestypeid", [$projectid]);
// Get direct expenses
$expenseexchange = R::getROW("
select sum(thevalue) as totals, sum(finalsupervision) as finalsupervisions
from expenseexchange
where del = 0 and projectid = ? and expensetype = ?", [$projectid, $expensetype]);
// Calculate supervision based on type
if ($project['supervision_type'] == 1) {
$finalsupervisions = $project['supervision_amount']; // Fixed amount
} else {
$finalsupervisions = $calculated_supervisions; // Calculated percentage
}
**Income Calculation**:
php
if ($project['income'] == 1) {
// Client payments
$project['allclientdebtchange'] = R::getCell("
SELECT sum(clientdebtchangeamount)
FROM clientdebtchange
WHERE clientid = ? and paySerialNo > 0 and del = 0", [$project['clientid']]);
// Other income
$project['allincomeValue'] = R::getCell("
SELECT sum(incomeValue)
FROM income
WHERE costcenterid = ? and conditions = 0", [$project['costcenterid']]);
$project['alltotalsincome'] = $project['allclientdebtchange'] + $project['allincomeValue'];
}
**Final Calculation**:
php
$project['endtotals'] = ($project['alltotals'] + $project['alltotalvalues']) - $project['alltotalsincome'];
---
### 4. **projects** - Project List for Client
**Location**: Line 162
**Purpose**: Retrieve projects accessible to a specific web client
**Function Signature**:
php
// POST data: {"webclientid": 123}
**Access Control Logic**:
php
$webclient = R::findOne('webclients', " id = ? ", [$webclientid]);
if ($webclient->clientids != 0) {
// Restricted access - only assigned projects
$projectclients = R::getAll("
select *,project.id as id, CONCAT('$src', project.image) as image
from project LEFT JOIN webclientprojects ON project.id = webclientprojects.projectid
where webclientprojects.webclientid = ? ", [$webclientid]);
} else {
// Full access - all projects
$projectclients = R::getAll("
select *, project.id as id, CONCAT('$src', project.image) as image
from project");
}
---
### 5. **updateProfile** - Client Profile Update
**Location**: Line 329
**Purpose**: Update web client profile information with validation
**Function Signature**:
php
// POST data
{
"id": 123,
"name": "Updated Name",
"mobile": "New Mobile",
"user_name": "new_username",
"password": "new_password",
"device_id": "device_id"
}
**Update Process**:
1. Validate required fields
2. Check username/mobile uniqueness (excluding current user)
3. Update record with new data
4. Set update timestamp and user
---
## ๐ Workflows
### Workflow 1: Client Registration Process
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ START: New Client Registration โ
โ POST: api_web.php?do=register โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Parse Request Data โ
โ - Check request method and headers โ
โ - Parse JSON from php://input or $_POST โ
โ - Extract: name, mobile, user_name, password, device_idโ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. Input Validation โ
โ IF any required field empty: โ
โ - Return error: "๏บ๏ป๏บพ ุง๏ป๏บค๏ป๏ปฎ๏ป ุง๏ป๏ปค๏ป๏ป ๏ปฎ๏บ๏บ ๏ป๏บ๏บญ๏ป๏บ" โ
โ - Status: 2 โ
โ - Exit process โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 3. Duplicate Username Check โ
โ Query: COUNT(webclients) WHERE user_name = ? AND is_active = 1 โ
โ IF count > 0: โ
โ - Return error: "ุง๏บณ๏ปข ุง๏ป๏ปค๏บด๏บ๏บจ๏บช๏ปก ๏ปฃ๏ป๏บฎ๏บญ" โ
โ - Status: 2 โ
โ - Exit process โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 4. Duplicate Mobile Check โ
โ Query: COUNT(webclients) WHERE mobile = ? AND is_active = 1 โ
โ IF count > 0: โ
โ - Return error: "๏บญ๏ป๏ปข ุง๏ป๏ปค๏ปฎ๏บ๏บ๏ปณ๏ป ๏ปฃ๏ป๏บฎ๏บญ" โ
โ - Status: 2 โ
โ - Exit process โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 5. Create New Account โ
โ - Create new webclients record โ
โ - Set is_active = 1 โ
โ - Set addtoday = current timestamp โ
โ - Set adduserid = 0 (system created) โ
โ - Store record and get new ID โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 6. Return Success Response โ
โ JSON Response: โ
โ { โ
โ "status": 1, โ
โ "reason": "ุชู ุฅูุดุงุก ุงูุญุณุงุจ ุจูุฌุงุญ", โ
โ "webclient_id": {new_id}, โ
โ "name": "{name}", โ
โ "mobile": "{mobile}", โ
โ "user_name": "{user_name}" โ
โ } โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
---
### Workflow 2: Project Financial Report Generation
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ START: Generate Project Report โ
โ GET: api_web.php?do=projectReport&id={projectid} โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Load Project Basic Data โ
โ - Get project master record โ
โ - Build image and PDF URLs with host โ
โ - Initialize financial totals โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. Process Project Stages (if enabled) โ
โ IF project.projectstagesdata = 1: โ
โ FOR EACH stage in projectstagechosse: โ
โ โโโ Load stage definition from projectstages โ
โ โโโ Process stage images: โ
โ โ - Split comma-separated image names โ
โ โ - Build full URLs with host prefix โ
โ โโโ Process stage files: โ
โ - Split comma-separated file names โ
โ - Combine with original names โ
โ - Build download URLs โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 3. Process Timeline Items (if enabled) โ
โ IF project.projecttimetableitems = 1: โ
โ - Query projecttimetableitems for project โ
โ - Include: clause, amount, duration, dates โ
โ ELSE: โ
โ - Set empty array โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 4. Calculate Project Expenses (if enabled) โ
โ IF project.expenses = 1: โ
โ A. Material Exchange Expenses: โ
โ - Group by expense type โ
โ - Sum totalbuyprice and finalsupervision โ
โ - Handle material returns (subtract) โ
โ โ
โ B. Direct Expenses: โ
โ - Query expenseexchange by project โ
โ - Group by expense type โ
โ - Sum thevalue and finalsupervision โ
โ โ
โ C. Calculate Supervision: โ
โ IF supervision_type = 1: โ
โ - Use fixed supervision_amount โ
โ ELSE: โ
โ - Calculate based on percentages โ
โ โ
โ D. Build Expense Type Summary: โ
โ FOR EACH expense type: โ
โ - Load expense type name โ
โ - Sum all related expenses โ
โ - Calculate supervision amounts โ
โ - Add to summary array โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 5. Calculate Project Income (if enabled) โ
โ IF project.income = 1: โ
โ A. Client Payments: โ
โ - Query clientdebtchange for project client โ
โ - Filter by paySerialNo > 0 (actual payments) โ
โ - Sum clientdebtchangeamount โ
โ โ
โ B. Other Income: โ
โ - Query income by project cost center โ
โ - Sum incomeValue where conditions = 0 โ
โ โ
โ C. Total Income: โ
โ - alltotalsincome = client payments + other income โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 6. Calculate Final Project Totals โ
โ - Total Costs = alltotals + alltotalvalues โ
โ - Net Result = Total Costs - Total Income โ
โ - endtotals = (expenses + supervision) - income โ
โ โ
โ Positive endtotals = Project Loss โ
โ Negative endtotals = Project Profit โ
โโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 7. Build & Return Complete Report โ
โ JSON Response: โ
โ { โ
โ "project": {project_data_with_totals}, โ
โ "projectstages": [stage_data_with_files], โ
โ "projecttimetableitems": [timeline_items], โ
โ "expenses": [expense_type_summaries], โ
โ "clientdebtchange": [payment_history], โ
โ "incomes": [income_records] โ
โ } โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
---
## ๐ API Endpoints
| Endpoint | Method | Purpose | Request Body |
|----------|---------|---------|--------------|
| `?do=register` | POST | Register new web client | `{name, mobile, user_name, password, device_id}` |
| `?do=login` | POST | Authenticate user | `{username, password}` |
| `?do=profile` | POST | Get user profile | `{id}` |
| `?do=projects` | POST | Get user's projects | `{webclientid}` |
| `?do=projectReport` | GET | Get detailed project report | `?id={projectid}` |
| `?do=updateProfile` | POST | Update user profile | `{id, name, mobile, user_name, password, device_id}` |
| `?do=getProjects` | POST | Alternative project list | `{webclient_id}` |
| `?do=getProject` | POST | Alternative project detail | `{projectid}` |
---
## ๐ฑ CORS & Cross-Platform Support
### CORS Headers Configuration
php
if (isset($_SERVER['HTTP_ORIGIN'])) {
header("Access-Control-Allow-Origin: *");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
}
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
exit(0);
}
### Multi-Platform Request Handling
php
$post_data = file_get_contents("php://input");
if (empty($post_data)) {
// iOS compatibility
$get_request = json_decode(json_encode($_POST));
} else {
// Android compatibility
$get_request = json_decode($post_data);
}
---
## ๐ Security Issues & Recommendations
### Critical Security Vulnerabilities
**1. Plain Text Passwords**
php
// CURRENT (INSECURE)
$row->password = $password;
// RECOMMENDED
$row->password = password_hash($password, PASSWORD_DEFAULT);
// For authentication
if (password_verify($password, $stored_hash)) {
// Valid login
}
**2. SQL Injection Risk**
php
// CURRENT (VULNERABLE)
$user = R::findOne('webclients', " (user_name = '" . $username . "' or mobile = '" . $mobile . "') and password = '" . $password . "' ");
// RECOMMENDED
$user = R::findOne('webclients', " (user_name = ? or mobile = ?) and password = ?", [$username, $username, $password_hash]);
**3. No Rate Limiting**
- Add login attempt limiting
- Implement IP-based throttling
- Add CAPTCHA for repeated failures
**4. Missing Input Sanitization**
- Validate email format
- Sanitize mobile number format
- Escape special characters in user input
---
## ๐ Performance Considerations
### Database Optimization
1. **Critical Indexes**:
- `webclients(user_name, is_active)`
- `webclients(mobile, is_active)`
- `webclientprojects(webclientid)`
- `project(clientid)`
- `expenseexchange(projectid, expensetype)`
2. **Query Optimization**:
- Use prepared statements for security and performance
- Limit result sets for large projects
- Cache frequently accessed project data
### File Handling Performance
- Image and PDF concatenation should be cached
- Consider CDN for file delivery
- Implement file compression for mobile apps
---
## ๐ Common Issues & Troubleshooting
### 1. **CORS Preflight Issues**
**Issue**: Browser blocks API requests from web applications
**Cause**: Missing or incorrect CORS headers
**Fix**:
php
// Ensure all required headers are sent
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
### 2. **Empty Response Data**
**Issue**: API returns empty or null data
**Cause**: Database connection issues or missing foreign key relationships
**Debug**:
php
// Add debugging output
error_log("Project ID: " . $projectid);
error_log("Query result: " . print_r($project, true));
### 3. **Authentication Failures**
**Issue**: Valid credentials rejected
**Cause**: Case sensitivity or character encoding issues
**Debug**:
php
// Check exact values being compared
error_log("Input username: " . $username);
error_log("Stored username: " . $user->user_name);
error_log("Password match: " . ($password === $user->password ? 'YES' : 'NO'));
---
## ๐งช Testing Scenarios
### Test Case 1: User Registration Flow
1. POST valid registration data
2. Verify success response with user ID
3. Attempt duplicate username registration
4. Verify error response for duplicate
5. Test missing required fields
6. Verify appropriate error messages
### Test Case 2: Project Report Generation
1. Request report for valid project ID
2. Verify all financial calculations
3. Test with project having no expenses
4. Test with project having no income
5. Verify file URL generation
6. Test with invalid project ID
### Test Case 3: CORS Functionality
1. Send OPTIONS preflight request
2. Verify CORS headers in response
3. Send actual POST request from browser
4. Verify request not blocked by CORS
5. Test with different origins
```
---
๐ Related Documentation
- โข CLAUDE.md - PHP 8.2 migration guide
- โข projectController.php - Internal project management
- โข API Security Best Practices - Security implementation guide
- โข Database Schema Documentation - Complete table relationships
---
Documented By: AI Assistant
Review Status: โ ๏ธ Security review needed
Next Review: Implement security fixes before production use