Files
novacpx/panel/lib/CloudflareManager.php
T
myron 135bbcb0b3 Features #14-17: WordPress Manager, Backup, Cloudflare, TOTP 2FA
- WordPressManager.php: wp-cli wrapper for install/update/clone/delete
- BackupManager.php: tar+mysqldump, schedules, retention, rclone
- CloudflareManager.php: zone/record management, sync, cache purge
- TOTP.php: RFC 6238 pure-PHP with backup codes
- Auth.php: TOTP_REQUIRED two-step login flow
- 4 new API endpoints: wordpress, backup, cloudflare, totp
- DB migration 002: TOTP cols, CF cols, wordpress_installs, backups tables
- admin.js: full UI for all 4 features + TOTP login step
- admin/index.php: sidebar nav for WordPress, 2FA Manager, Cloudflare

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 21:13:59 +00:00

151 lines
7.7 KiB
PHP

<?php
class CloudflareManager {
private const API = 'https://api.cloudflare.com/client/v4/';
private PDO $db;
public function __construct() {
$this->db = Database::getInstance()->getPDO();
}
// ── Credential management ─────────────────────────────────────────────────
public function saveCredentials(int $accountId, string $apiKey, string $email): bool {
$stmt = $this->db->prepare("UPDATE accounts SET cf_api_key=?, cf_api_email=? WHERE id=?");
return $stmt->execute([$apiKey, $email, $accountId]);
}
public function testCredentials(string $apiKey, string $email): bool {
$r = $this->req('GET', 'user/tokens/verify', [], $apiKey, $email);
return $r['success'] ?? false;
}
public function getCredentials(int $accountId): ?array {
$stmt = $this->db->prepare("SELECT cf_api_key, cf_api_email, cf_zone_id FROM accounts WHERE id=?");
$stmt->execute([$accountId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return ($row && $row['cf_api_key']) ? $row : null;
}
// ── Zones ─────────────────────────────────────────────────────────────────
public function listZones(string $apiKey, string $email): array {
$r = $this->req('GET', 'zones?per_page=200&status=active', [], $apiKey, $email);
return $r['result'] ?? [];
}
public function getZoneId(string $domain, string $apiKey, string $email): ?string {
$r = $this->req('GET', 'zones?name=' . urlencode($domain), [], $apiKey, $email);
return $r['result'][0]['id'] ?? null;
}
// ── DNS records ───────────────────────────────────────────────────────────
public function listRecords(string $zoneId, string $apiKey, string $email): array {
$r = $this->req('GET', "zones/{$zoneId}/dns_records?per_page=200", [], $apiKey, $email);
return $r['result'] ?? [];
}
public function createRecord(string $zoneId, array $record, string $apiKey, string $email): array {
return $this->req('POST', "zones/{$zoneId}/dns_records", $record, $apiKey, $email);
}
public function updateRecord(string $zoneId, string $recordId, array $record, string $apiKey, string $email): array {
return $this->req('PUT', "zones/{$zoneId}/dns_records/{$recordId}", $record, $apiKey, $email);
}
public function deleteRecord(string $zoneId, string $recordId, string $apiKey, string $email): bool {
$r = $this->req('DELETE', "zones/{$zoneId}/dns_records/{$recordId}", [], $apiKey, $email);
return $r['success'] ?? false;
}
public function toggleProxy(string $zoneId, string $recordId, bool $proxied, string $apiKey, string $email): array {
// Fetch existing record first
$r = $this->req('GET', "zones/{$zoneId}/dns_records/{$recordId}", [], $apiKey, $email);
$rec = $r['result'] ?? [];
$rec['proxied'] = $proxied;
unset($rec['id'], $rec['zone_id'], $rec['zone_name'], $rec['created_on'], $rec['modified_on'], $rec['meta']);
return $this->req('PUT', "zones/{$zoneId}/dns_records/{$recordId}", $rec, $apiKey, $email);
}
// ── Sync: push local DNS records to Cloudflare ────────────────────────────
public function syncToCloudflare(string $domain, string $zoneId, string $apiKey, string $email): array {
$localRecords = $this->getLocalRecords($domain);
$cfRecords = $this->listRecords($zoneId, $apiKey, $email);
$cfIndex = [];
foreach ($cfRecords as $r) $cfIndex[$r['type'] . '_' . $r['name']] = $r;
$created = 0; $updated = 0;
foreach ($localRecords as $local) {
$key = $local['type'] . '_' . $local['name'] . '.' . $domain . '.';
if (isset($cfIndex[$key])) {
$this->updateRecord($zoneId, $cfIndex[$key]['id'], [
'type' => $local['type'],
'name' => $local['name'],
'content' => $local['content'],
'ttl' => (int)($local['ttl'] ?? 1),
'proxied' => in_array($local['type'], ['A','AAAA','CNAME']),
], $apiKey, $email);
$updated++;
} else {
$this->createRecord($zoneId, [
'type' => $local['type'],
'name' => $local['name'],
'content' => $local['content'],
'ttl' => (int)($local['ttl'] ?? 1),
'proxied' => in_array($local['type'], ['A','AAAA','CNAME']),
], $apiKey, $email);
$created++;
}
}
return ['created' => $created, 'updated' => $updated];
}
// ── Sync: pull Cloudflare records into local DB ───────────────────────────
public function syncFromCloudflare(string $domain, string $zoneId, string $apiKey, string $email): int {
$cfRecords = $this->listRecords($zoneId, $apiKey, $email);
$count = 0;
foreach ($cfRecords as $rec) {
$name = rtrim(str_replace('.' . $domain, '', $rec['name']), '.');
$this->db->prepare("INSERT INTO dns_records (domain, name, type, content, ttl, priority)
VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE content=VALUES(content), ttl=VALUES(ttl)")
->execute([$domain, $name ?: '@', $rec['type'], $rec['content'], $rec['ttl'] ?? 300, $rec['priority'] ?? 0]);
$count++;
}
// Store zone ID on domain
$this->db->prepare("UPDATE dns_zones SET cf_zone_id=? WHERE domain=?")->execute([$zoneId, $domain]);
return $count;
}
// ── Purge cache ───────────────────────────────────────────────────────────
public function purgeCache(string $zoneId, string $apiKey, string $email): bool {
$r = $this->req('POST', "zones/{$zoneId}/purge_cache", ['purge_everything' => true], $apiKey, $email);
return $r['success'] ?? false;
}
// ── HTTP helper ───────────────────────────────────────────────────────────
private function req(string $method, string $path, array $body, string $apiKey, string $email): array {
$ch = curl_init(self::API . ltrim($path, '/'));
$headers = [
"X-Auth-Email: {$email}",
"X-Auth-Key: {$apiKey}",
"Content-Type: application/json",
];
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true,
]);
if ($body && in_array($method, ['POST','PUT','PATCH'])) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$result = curl_exec($ch);
curl_close($ch);
return json_decode($result, true) ?? ['success' => false, 'errors' => ['curl failed']];
}
private function getLocalRecords(string $domain): array {
$stmt = $this->db->prepare("SELECT * FROM dns_records WHERE domain=?");
$stmt->execute([$domain]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}