moving helper functions to scripts folder
This commit is contained in:
1 parent
e7fad500f9
commit
dc39b6988c
9 files changed
+516
-51
No files matched your search
+53
-13
@@ -3,6 +3,15 @@ from database import *
|
|||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'photos')
|
||||||
|
app.config['URL_PREFIX'] = '/uploads' # public URL prefix
|
||||||
|
|
||||||
|
# make sure the folder exists
|
||||||
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
||||||
|
|
||||||
|
# helper to build a public URL
|
||||||
|
def public_url(filename: str) -> str:
|
||||||
|
return f"{app.config['URL_PREFIX']}/{filename}"
|
||||||
|
|
||||||
# Initialise database on startup
|
# Initialise database on startup
|
||||||
initialize_db()
|
initialize_db()
|
||||||
@@ -179,29 +188,60 @@ def setting_detail(name: str):
|
|||||||
|
|
||||||
# ----------------------------- Photos -----------------------------
|
# ----------------------------- Photos -----------------------------
|
||||||
|
|
||||||
@app.route('/api/providers/<provider_id>/photos', methods=['POST'])
|
# photo upload endpoint
|
||||||
|
@app.route('/api/providers/<int:provider_id>/photos', methods=['POST'])
|
||||||
def upload_photos(provider_id):
|
def upload_photos(provider_id):
|
||||||
if 'photos' not in request.files:
|
if 'photos' not in request.files:
|
||||||
return jsonify({'error': 'No files uploaded'}), 400
|
return jsonify({'error': 'No files uploaded'}), 400
|
||||||
|
|
||||||
# Secure filename + save to persistent storage
|
files = request.files.getlist('photos')
|
||||||
upload_folder = f'{os.path.dirname(__file__)}/photos'
|
if not files:
|
||||||
os.makedirs(upload_folder, exist_ok=True)
|
return jsonify({'error': 'No files in list'}), 400
|
||||||
|
|
||||||
photo_paths = []
|
allowed_ext = {'.jpg', '.jpeg', '.png', '.gif'}
|
||||||
for file in request.files.getlist('photos'):
|
saved_filenames = []
|
||||||
if file.filename == '':
|
|
||||||
|
for f in files:
|
||||||
|
if f.filename == '':
|
||||||
continue
|
continue
|
||||||
|
filename = secure_filename(f.filename)
|
||||||
|
ext = os.path.splitext(filename)[1].lower()
|
||||||
|
if ext not in allowed_ext:
|
||||||
|
return jsonify({'error': f'Unsupported file type: {ext}'}), 400
|
||||||
|
# rename to avoid collisions
|
||||||
|
new_name = f"{uuid.uuid4().hex}{ext}"
|
||||||
|
f.save(os.path.join(app.config['UPLOAD_FOLDER'], new_name))
|
||||||
|
saved_filenames.append(public_url(new_name))
|
||||||
|
|
||||||
filename = secure_filename(file.filename)
|
# Merge with any pre‑existing photos
|
||||||
file.save(os.path.join(upload_folder, filename))
|
existing = db.fetch(f"SELECT photos FROM providers WHERE id=?", (provider_id,))[0][0]
|
||||||
photo_paths.append(f"/uploads/{filename}")
|
try:
|
||||||
|
photos = json.loads(existing)
|
||||||
|
except Exception:
|
||||||
|
photos = []
|
||||||
|
photos.extend(saved_filenames)
|
||||||
|
|
||||||
# Store paths in DB (e.g., PostgreSQL JSON field)
|
|
||||||
db.execute("UPDATE providers SET photos = %s WHERE id = %s",
|
db.execute("UPDATE providers SET photos = %s WHERE id = %s",
|
||||||
(json.dumps(photo_paths), provider_id))
|
(json.dumps(photos), provider_id))
|
||||||
|
|
||||||
return jsonify({'success': True, 'paths': photo_paths}), 200
|
return jsonify({'success': True, 'photos': photos}), 200
|
||||||
|
|
||||||
|
# Endpoint to change the main photo
|
||||||
|
@app.route('/api/providers/<int:provider_id>/main_photo', methods=['PUT'])
|
||||||
|
def set_main_photo(provider_id):
|
||||||
|
data = request.get_json()
|
||||||
|
url = data.get('url')
|
||||||
|
if not url:
|
||||||
|
return jsonify({'error': 'Missing url'}), 400
|
||||||
|
|
||||||
|
# Validate that the URL belongs to this provider
|
||||||
|
photos = db.fetch(f"SELECT photos FROM providers WHERE id=?", (provider_id,))[0][0]
|
||||||
|
if json.loads(photos).count(url) == 0:
|
||||||
|
return jsonify({'error': 'URL not part of this provider'}), 400
|
||||||
|
|
||||||
|
db.execute("UPDATE providers SET main_photo = %s WHERE id = %s",
|
||||||
|
(url, provider_id))
|
||||||
|
return jsonify({'success': True, 'main_photo': url})
|
||||||
|
|
||||||
# Run the app if this file is executed directly
|
# Run the app if this file is executed directly
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+3
-1
@@ -52,7 +52,9 @@ def initialize_db() -> None:
|
|||||||
age INTEGER,
|
age INTEGER,
|
||||||
photo TEXT,
|
photo TEXT,
|
||||||
rates TEXT,
|
rates TEXT,
|
||||||
private INTEGER NOT NULL DEFAULT 0
|
private INTEGER NOT NULL DEFAULT 0,
|
||||||
|
photos TEXT DEFAULT '[]',
|
||||||
|
main_photo TEXT
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|||||||
+304
-27
@@ -1,37 +1,314 @@
|
|||||||
<html>
|
<?php
|
||||||
<head>
|
/**
|
||||||
</head>
|
* photos.php - Service Provider Photo Gallery Implementation
|
||||||
|
* Requirements:
|
||||||
|
* 1. Upload multiple images via PHP Frontend
|
||||||
|
* 2. Display gallery with thumbnail selection
|
||||||
|
* 3. Use Flask API for storage and metadata
|
||||||
|
* 4. Store photos in file system (not database BLOBs)
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/config.php';
|
||||||
|
|
||||||
<body>
|
// Validate provider ID
|
||||||
|
if (!isset($_GET['id']) || empty($_GET['id'])) {
|
||||||
|
die('Provider ID missing. Use: photos.php?id=123');
|
||||||
|
}
|
||||||
|
$provider_id = $_GET['id'];
|
||||||
|
|
||||||
<div class="photo-grid">
|
// Fetch existing photos from API
|
||||||
<div class="photo-item">
|
$api_url = BASE_API_URL . '/providers/' . urlencode($provider_id) . "/photos";
|
||||||
<input type="radio" name="main_photo" value="1" checked>
|
$existing_photos = [];
|
||||||
<img src="photo1.jpg" />
|
try {
|
||||||
</div>
|
$response = file_get_contents($api_url);
|
||||||
<div class="photo-item">
|
$existing_photos = json_decode($response, true);
|
||||||
<input type="radio" name="main_photo" value="2">
|
if (isset($existing_photos['error'])) {
|
||||||
<img src="photo2.jpg" />
|
trigger_error($existing_photos['error'], E_USER_WARNING);
|
||||||
</div>
|
$existing_photos = [];
|
||||||
</div>
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
trigger_error('API error: ' . $e->getMessage(), E_USER_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
<input type="file" id="photo-upload" name="photos[]" multiple accept="image/*" />
|
// Check upload permissions
|
||||||
|
$has_upload_perm = true; // Add authorization logic here
|
||||||
|
|
||||||
|
// Generate unique token for upload (security)
|
||||||
|
$upload_token = bin2hex(random_bytes(16));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Photos for Service Provider #<?php echo htmlspecialchars($provider_id); ?></title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #333;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
.upload-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.upload-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #3a86ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upload-btn:hover {
|
||||||
|
background: #1d66cc;
|
||||||
|
}
|
||||||
|
#preview-container {
|
||||||
|
display: none;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.photo-item {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
.photo-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.main-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: #3a86ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
#gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.no-photos {
|
||||||
|
text-align: center;
|
||||||
|
padding: 50px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: #e74c3c;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Photos for Service Provider #<?php echo htmlspecialchars($provider_id); ?></h1>
|
||||||
|
|
||||||
|
<?php if (isset($existing_photos['error'])): ?>
|
||||||
|
<div class="error">
|
||||||
|
<?php echo htmlspecialchars($existing_photos['error']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($has_upload_perm): ?>
|
||||||
|
<div class="upload-section">
|
||||||
|
<input type="file" id="photo-upload" name="photos[]" multiple accept="image/*">
|
||||||
|
<button id="upload-btn" class="upload-btn">Upload Photos</button>
|
||||||
|
</div>
|
||||||
<div id="preview-container"></div>
|
<div id="preview-container"></div>
|
||||||
|
<?php else: ?>
|
||||||
|
<p>Upload permissions disabled. Contact administrator.</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="loading" id="loading">
|
||||||
|
Loading photos...
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<div id="gallery">
|
||||||
document.getElementById('photo-upload').addEventListener('change', function(e) {
|
<?php if (empty($existing_photos)): ?>
|
||||||
const files = Array.from(e.target.files);
|
<div class="no-photos">
|
||||||
const preview = document.getElementById('preview-container');
|
<h2>No photos uploaded yet</h2>
|
||||||
preview.innerHTML = '';
|
<p>Upload images to display them here</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($existing_photos as $photo): ?>
|
||||||
|
<div class="photo-item">
|
||||||
|
<input type="radio" name="main_photo" value="<?php echo htmlspecialchars($photo['path']); ?>" <?php echo $photo['is_main'] ? 'checked' : ''; ?> class="main-radio">
|
||||||
|
<img src="<?php echo htmlspecialchars($photo['path']); ?>" alt="Service provider photo">
|
||||||
|
<?php if ($photo['is_main']): ?>
|
||||||
|
<div class="main-indicator"></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
files.forEach(file => {
|
<script>
|
||||||
const img = document.createElement('img');
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
img.src = URL.createObjectURL(file);
|
const photoInput = document.getElementById('photo-upload');
|
||||||
img.classList.add('preview-thumb');
|
const previewContainer = document.getElementById('preview-container');
|
||||||
preview.appendChild(img);
|
const gallery = document.getElementById('gallery');
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
|
|
||||||
|
// Show preview on file selection
|
||||||
|
photoInput.addEventListener('change', function(e) {
|
||||||
|
if (this.files.length > 0) {
|
||||||
|
previewContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
previewContainer.innerHTML = '';
|
||||||
|
for (let i = 0; i < this.files.length; i++) {
|
||||||
|
const file = this.files[i];
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = function(e) {
|
||||||
|
const preview = document.createElement('div');
|
||||||
|
preview.className = 'photo-item';
|
||||||
|
|
||||||
|
preview.innerHTML = `
|
||||||
|
<input type="radio" name="main_photo" value="${file.name}"
|
||||||
|
class="main-radio"
|
||||||
|
style="position: absolute; top: 2px; left: 2px;">
|
||||||
|
<img src="${e.target.result}"
|
||||||
|
alt="Photo preview">
|
||||||
|
<div style="position: absolute; top: 2px; right: 2px;
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
background: #3a86ff;
|
||||||
|
border-radius: 50%;"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
previewContainer.appendChild(preview);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle upload button
|
||||||
|
document.getElementById('upload-btn').addEventListener('click', async () => {
|
||||||
|
const fileInput = document.getElementById('photo-upload');
|
||||||
|
if (!fileInput.files.length) {
|
||||||
|
alert('Please select at least one photo to upload');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
for (let i = 0; i < fileInput.files.length; i++) {
|
||||||
|
formData.append('photos[]', fileInput.files[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
BASE_API_URL . '/providers/<?php echo urlencode($provider_id); ?>/photos',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-Upload-Token': '<?php echo $upload_token; ?>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`API error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Add new photos to gallery
|
||||||
|
data.paths.forEach(path => {
|
||||||
|
const newPhoto = document.createElement('div');
|
||||||
|
newPhoto.className = 'photo-item';
|
||||||
|
|
||||||
|
newPhoto.innerHTML = `
|
||||||
|
<input type="radio" name="main_photo" value="${path}"
|
||||||
|
class="main-radio"
|
||||||
|
style="position: absolute; top: 2px; left: 2px;">
|
||||||
|
<img src="${path}"
|
||||||
|
alt="New photo"
|
||||||
|
style="object-fit: cover;">
|
||||||
|
<div style="position: absolute; top: 2px; right: 2px;
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
background: #3a86ff;
|
||||||
|
border-radius: 50%;"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
gallery.appendChild(newPhoto);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
fileInput.value = '';
|
||||||
|
previewContainer.style.display = 'none';
|
||||||
|
|
||||||
|
// Update main photo selection (if any)
|
||||||
|
const mainRadios = gallery.querySelectorAll('.main-radio');
|
||||||
|
if (mainRadios.length > 0) {
|
||||||
|
mainRadios[0].checked = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upload failed:', error);
|
||||||
|
alert('Upload failed: ' + error.message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
</body>
|
// Handle main photo selection
|
||||||
|
gallery.addEventListener('change', async (e) => {
|
||||||
|
if (e.target.className === 'main-radio') {
|
||||||
|
const mainPhoto = e.target.value;
|
||||||
|
const providerId = '<?php echo $provider_id; ?>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
BASE_API_URL . '/providers/${providerId}/main_photo',
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
photo_path: mainPhoto
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`API error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update visuals
|
||||||
|
gallery.querySelectorAll('.main-indicator').forEach(ind => {
|
||||||
|
ind.style.display = 'none';
|
||||||
|
});
|
||||||
|
e.target.parentElement.querySelector('.main-indicator').style.display = 'block';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Setting main photo failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide loading message
|
||||||
|
loading.style.display = 'none';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
File renamed without changes.
File renamed without changes.
@@ -33,7 +33,39 @@ function callApi(string $method, string $path, $body = null)
|
|||||||
}
|
}
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
// 1️⃣ Get provider details
|
// ------------------------------------------------------------------
|
||||||
|
// Helper: upload photos (multipart)
|
||||||
|
// Returns the JSON response from the API (or null on error)
|
||||||
|
function uploadPhotosApi(int $provider_id, array $files): ?array
|
||||||
|
{
|
||||||
|
$url = BASE_API_URL . "/api/providers/{$provider_id}/photos";
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|
||||||
|
// Build multipart array
|
||||||
|
$multipart = [];
|
||||||
|
foreach ($files['tmp_name'] as $i => $tmp) {
|
||||||
|
if ($files['name'][$i] === '') continue;
|
||||||
|
$multipart[] = [
|
||||||
|
'name' => 'photos',
|
||||||
|
'contents' => fopen($tmp, 'r'),
|
||||||
|
'filename' => $files['name'][$i]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $multipart);
|
||||||
|
// Let PHP set the correct Content‑Type boundary
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return ($httpCode < 400) ? json_decode($response, true) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get provider details
|
||||||
$provider = callApi('GET', '/providers?id=' . $provider_id);
|
$provider = callApi('GET', '/providers?id=' . $provider_id);
|
||||||
if (!$provider) {
|
if (!$provider) {
|
||||||
echo '<h1>Error</h1><p>Could not load provider details.</p>';
|
echo '<h1>Error</h1><p>Could not load provider details.</p>';
|
||||||
@@ -43,14 +75,14 @@ if (is_array($provider) && count($provider) > 0 && isset($provider[0])) {
|
|||||||
$provider = $provider[0];
|
$provider = $provider[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2️⃣ Get rate‑option list (comma‑separated string → array)
|
// Get rate‑option list (comma‑separated string → array)
|
||||||
$rateSetting = callApi('GET', '/settings/rate_options');
|
$rateSetting = callApi('GET', '/settings/rate_options');
|
||||||
$rateOptions = [];
|
$rateOptions = [];
|
||||||
if ($rateSetting && isset($rateSetting['value'])) {
|
if ($rateSetting && isset($rateSetting['value'])) {
|
||||||
$rateOptions = array_map('trim', explode(',', $rateSetting['value']));
|
$rateOptions = array_map('trim', explode(',', $rateSetting['value']));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3️⃣ Decode the API’s rate string (JSON → array of arrays)
|
// Decode the API’s rate string (JSON → array of arrays)
|
||||||
$ratesData = $provider['rates'] ?? '[]';
|
$ratesData = $provider['rates'] ?? '[]';
|
||||||
$decoded = [];
|
$decoded = [];
|
||||||
if (is_string($ratesData)) {
|
if (is_string($ratesData)) {
|
||||||
@@ -62,7 +94,7 @@ if (is_string($ratesData)) {
|
|||||||
$decoded = $ratesData; // already an array – legacy path
|
$decoded = $ratesData; // already an array – legacy path
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4️⃣ Build two helper arrays
|
// Build two helper arrays
|
||||||
$selectedRateKeys = []; // lowercase keys of selected rates
|
$selectedRateKeys = []; // lowercase keys of selected rates
|
||||||
$ratesAssoc = []; // key → value
|
$ratesAssoc = []; // key → value
|
||||||
|
|
||||||
@@ -75,7 +107,7 @@ foreach ($decoded as $entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5️⃣ Find orphan rates (present in $decoded but not in $rateOptions)
|
// Find orphan rates (present in $decoded but not in $rateOptions)
|
||||||
$orphanRates = [];
|
$orphanRates = [];
|
||||||
$orphanKeys = [];
|
$orphanKeys = [];
|
||||||
foreach ($decoded as $entry) {
|
foreach ($decoded as $entry) {
|
||||||
@@ -90,8 +122,8 @@ foreach ($decoded as $entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// 6️⃣ Handle form POST
|
// Handle form POST
|
||||||
// --- Handle form POST -------------------------------------------------
|
/*
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
// Base payload
|
// Base payload
|
||||||
$payload = [
|
$payload = [
|
||||||
@@ -107,14 +139,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
// Rebuild the rates array from the form.
|
// Rebuild the rates array from the form.
|
||||||
$finalRates = [];
|
$finalRates = [];
|
||||||
|
|
||||||
// 1️⃣ Add the selected valid rate options (value may be changed by the user)
|
// Add the selected valid rate options (value may be changed by the user)
|
||||||
$selectedRateKeysRaw = $_POST['rate_selected'] ?? [];
|
$selectedRateKeysRaw = $_POST['rate_selected'] ?? [];
|
||||||
foreach ($selectedRateKeysRaw as $opt) { // $opt is the original rate name
|
foreach ($selectedRateKeysRaw as $opt) { // $opt is the original rate name
|
||||||
$newVal = $_POST['rate_value'][$opt] ?? '0'; // the updated value
|
$newVal = $_POST['rate_value'][$opt] ?? '0'; // the updated value
|
||||||
$finalRates[] = [$opt => $newVal];
|
$finalRates[] = [$opt => $newVal];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2️⃣ Add orphan rates that the user chose **not** to delete
|
// Add orphan rates that the user chose **not** to delete
|
||||||
$orphanDeleteSet = array_flip(array_map('strtolower', $_POST['orphan_delete'] ?? []));
|
$orphanDeleteSet = array_flip(array_map('strtolower', $_POST['orphan_delete'] ?? []));
|
||||||
foreach ($orphanRates as $key => $value) { // $key is already lower‑cased
|
foreach ($orphanRates as $key => $value) { // $key is already lower‑cased
|
||||||
if (!isset($orphanDeleteSet[strtolower($key)])) {
|
if (!isset($orphanDeleteSet[strtolower($key)])) {
|
||||||
@@ -122,7 +154,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3️⃣ Encode the new array as JSON
|
// Encode the new array as JSON
|
||||||
//$payload['rates'] = !empty($finalRates) ? json_encode($finalRates) : null;
|
//$payload['rates'] = !empty($finalRates) ? json_encode($finalRates) : null;
|
||||||
$payload['rates'] = !empty($finalRates) ? $finalRates : null;
|
$payload['rates'] = !empty($finalRates) ? $finalRates : null;
|
||||||
|
|
||||||
@@ -135,6 +167,68 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
echo '<h1>Error</h1><p>Failed to update provider.</p>';
|
echo '<h1>Error</h1><p>Failed to update provider.</p>';
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
/* -------- 1. Build the base payload -------- */
|
||||||
|
$payload = [
|
||||||
|
'name' => $_POST['name'] ?? '',
|
||||||
|
'age' => (int)($_POST['age'] ?? 0),
|
||||||
|
'photo' => $_POST['photo'] ?? '',
|
||||||
|
'phone' => $_POST['phone'] ?? '',
|
||||||
|
'private' => isset($_POST['private']),
|
||||||
|
];
|
||||||
|
|
||||||
|
/* -------- 2. Re‑build the rates array -------- */
|
||||||
|
$finalRates = [];
|
||||||
|
$selectedRateKeysRaw = $_POST['rate_selected'] ?? [];
|
||||||
|
foreach ($selectedRateKeysRaw as $opt) {
|
||||||
|
$newVal = $_POST['rate_value'][$opt] ?? '0';
|
||||||
|
$finalRates[] = [$opt => $newVal];
|
||||||
|
}
|
||||||
|
// orphan rates that the user chose *not* to delete
|
||||||
|
$orphanDeleteSet = array_flip(array_map('strtolower', $_POST['orphan_delete'] ?? []));
|
||||||
|
foreach ($orphanRates as $key => $value) {
|
||||||
|
if (!isset($orphanDeleteSet[$key])) {
|
||||||
|
$finalRates[] = [$key => $value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$payload['rates'] = !empty($finalRates) ? $finalRates : null;
|
||||||
|
|
||||||
|
/* -------- 3. Call the API to update provider -------- */
|
||||||
|
$result = callApi('PUT', "/providers/{$provider_id}", $payload);
|
||||||
|
if (!$result) {
|
||||||
|
echo '<h1>Error</h1><p>Failed to update provider.</p>';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- 4. Upload photos if any were chosen -------- */
|
||||||
|
if (!empty($_FILES['photos']['name'][0])) {
|
||||||
|
$uploadRes = uploadPhotosApi($provider_id, $_FILES['photos']);
|
||||||
|
if ($uploadRes && isset($uploadRes['photos'])) {
|
||||||
|
// Update main photo if the user selected one
|
||||||
|
$selectedMain = $_POST['main_photo'] ?? null;
|
||||||
|
if ($selectedMain) {
|
||||||
|
callApi('PUT', "/api/providers/{$provider_id}/main_photo",
|
||||||
|
['url' => $selectedMain]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Photo upload failed – you may want to show a message
|
||||||
|
echo '<p style="color:red;">Photo upload failed.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- 5. Set the chosen main photo from the new uploads -------- */
|
||||||
|
// If a main photo was chosen from the new uploads, send it
|
||||||
|
if (!empty($_POST['main_photo'])) {
|
||||||
|
callApi('PUT', "/api/providers/{$provider_id}/main_photo",
|
||||||
|
['url' => $_POST['main_photo']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------- 6. Redirect back to the list -------- */
|
||||||
|
header('Location: list_providers.php');
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -158,6 +252,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<h1>Edit Provider</h1>
|
<h1>Edit Provider</h1>
|
||||||
<form method="post" id="editForm">
|
<form method="post" id="editForm">
|
||||||
<!-- Basic fields -->
|
<!-- Basic fields -->
|
||||||
|
|
||||||
|
<?php if (!empty($provider['photos'])): ?>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Existing Photos</legend>
|
||||||
|
<div style="display:flex; flex-wrap:wrap;">
|
||||||
|
<?php foreach ($provider['photos'] as $idx => $url): ?>
|
||||||
|
<div style="margin:4px; text-align:center;">
|
||||||
|
<img src="<?= htmlspecialchars($url) ?>" style="max-width:100px; height:auto;">
|
||||||
|
<br>
|
||||||
|
<input type="radio" name="main_photo" value="<?= htmlspecialchars($url) ?>"
|
||||||
|
<?= ($provider['main_photo'] ?? '') === $url ? 'checked' : '' ?>>
|
||||||
|
<label>Thumbnail</label>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<?php endif; ?>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Upload New Photos</legend>
|
||||||
|
<label>
|
||||||
|
<input type="file" name="photos[]" multiple accept="image/*">
|
||||||
|
</label>
|
||||||
|
<p>You can select several images at once.</p>
|
||||||
|
</fieldset>
|
||||||
<label>Name: <input type="text" name="name" required value="<?php echo htmlspecialchars($provider['name'] ?? ''); ?>"></label>
|
<label>Name: <input type="text" name="name" required value="<?php echo htmlspecialchars($provider['name'] ?? ''); ?>"></label>
|
||||||
<label>Age: <input type="number" name="age" required value="<?php echo htmlspecialchars($provider['age'] ?? ''); ?>"></label>
|
<label>Age: <input type="number" name="age" required value="<?php echo htmlspecialchars($provider['age'] ?? ''); ?>"></label>
|
||||||
<label>Photo URL: <input type="text" name="photo" value="<?php echo htmlspecialchars($provider['photo'] ?? ''); ?>"></label>
|
<label>Photo URL: <input type="text" name="photo" value="<?php echo htmlspecialchars($provider['photo'] ?? ''); ?>"></label>
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__.'/config.php';
|
||||||
|
|
||||||
|
function uploadPhotos($provider_id, $files) {
|
||||||
|
$url = BASE_API_URL . "/api/providers/$provider_id/photos";
|
||||||
|
|
||||||
|
$c = curl_init($url);
|
||||||
|
curl_setopt($c, CURLOPT_POST, true);
|
||||||
|
curl_setopt($c, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']);
|
||||||
|
// Build multipart array
|
||||||
|
$multipart = [];
|
||||||
|
foreach ($files['tmp_name'] as $i => $tmp) {
|
||||||
|
$multipart[] = [
|
||||||
|
'name' => 'photos',
|
||||||
|
'contents' => fopen($tmp, 'r'),
|
||||||
|
'filename' => $files['name'][$i],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
curl_setopt($c, CURLOPT_POSTFIELDS, $multipart);
|
||||||
|
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|
||||||
|
$res = curl_exec($c);
|
||||||
|
$http = curl_getinfo($c, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($c);
|
||||||
|
|
||||||
|
return $http < 400 ? json_decode($res, true) : null;
|
||||||
|
}
|
||||||
|
?>
|
||||||
Reference in new issue
Block a user