getenv('DABIR_CONNECTOR_SECRET') ?: '', 'site_name' => 'My Blog', 'db' => [ 'dsn' => 'mysql:host=localhost;dbname=YOUR_DB;charset=utf8mb4', 'user' => 'YOUR_DB_USER', 'pass' => 'YOUR_DB_PASSWORD', ], // Your posts table. Set a column to null if you don't have it. 'posts' => [ 'table' => 'posts', 'id' => 'id', 'title' => 'title', 'slug' => 'slug', 'content' => 'content', 'excerpt' => 'excerpt', // or null 'meta_description' => 'meta_description', // or null 'image' => 'image', // featured image column, or null 'image_alt' => null, // or column name 'category_id' => 'category_id', // or null 'faq' => null, // JSON column for FAQ, or null 'status' => 'status', // or null (then every post is "published") 'published_at' => 'published_at', // or null 'created_at' => 'created_at', // or null 'updated_at' => 'updated_at', // or null ], // What your status column stores for draft / published posts. 'status_values' => ['draft' => 'draft', 'publish' => 'published'], // Categories table, or null if you don't have categories. 'categories' => ['table' => 'categories', 'id' => 'id', 'name' => 'name'], // Public URL of a post. {slug} and {id} are replaced. 'post_url' => 'https://example.com/blog/{slug}', // Where downloaded images go (must be writable) and their public URL. 'images_dir' => __DIR__ . '/uploads/dabir', 'images_url' => 'https://example.com/uploads/dabir', // Store the featured image in the DB as the full 'url' or as a 'path' relative to images_dir. 'image_store_as' => 'url', // Images are only downloaded from these hosts. 'image_hosts' => ['cloud.ali-programmer.ir'], // false = your theme shows the FAQ from the 'faq' column itself (no FAQ block inside the body). 'faq_in_content' => true, // true = prepend Dabir's \n" . $body; } return $body; } function dabir_faq_json(array $in): ?string { $faq = []; foreach ((array)($in['faq'] ?? []) as $i) { $q = trim((string)($i['question'] ?? '')); $a = trim((string)($i['answer'] ?? '')); if ($q !== '' && $a !== '') { $faq[] = ['question' => $q, 'answer' => $a]; } } return $faq ? json_encode($faq, JSON_UNESCAPED_UNICODE) : null; } function dabir_category_id(PDO $pdo, array $c, string $name): ?string { $cat = $c['categories'] ?? null; $name = trim($name); if (!$cat || $name === '') { return null; } $st = $pdo->prepare('SELECT ' . dabir_q($cat['id']) . ' FROM ' . dabir_q($cat['table']) . ' WHERE ' . dabir_q($cat['name']) . ' = ? LIMIT 1'); $st->execute([$name]); $id = $st->fetchColumn(); return $id === false ? null : (string)$id; } function dabir_unique_slug(PDO $pdo, array $c, string $text): string { $p = $c['posts']; $base = mb_strtolower(trim($text)); $base = preg_replace('/[^\p{L}\p{N}\s-]+/u', '', $base); $base = trim((string)preg_replace('/[\s-]+/u', '-', $base), '-'); $base = mb_substr($base ?: 'post', 0, 180); $st = $pdo->prepare('SELECT COUNT(*) FROM ' . dabir_q($p['table']) . ' WHERE ' . dabir_q($p['slug']) . ' = ?'); $slug = $base; for ($i = 2; ; $i++) { $st->execute([$slug]); if ((int)$st->fetchColumn() === 0) { return $slug; } $slug = $base . '-' . $i; } } function dabir_post_url(array $c, array $row): string { return strtr($c['post_url'], [ '{slug}' => rawurlencode((string)($row['slug'] ?? '')), '{id}' => rawurlencode((string)($row['id'] ?? '')), ]); } function dabir_status_out(array $c, $value): string { if ($value === null) { return 'publish'; } return (string)$value === (string)$c['status_values']['publish'] ? 'publish' : 'draft'; } /* ---------------- images (HTTPS + allowed host + real image only) ---------------- */ /** @return array{path: string, url: string}|null */ function dabir_image(array $c, $url): ?array { static $copied = []; if (!is_string($url) || $url === '') { return null; } if (array_key_exists($url, $copied)) { return $copied[$url]; } $parts = parse_url($url); $host = strtolower($parts['host'] ?? ''); if (($parts['scheme'] ?? '') !== 'https' || !in_array($host, array_map('strtolower', $c['image_hosts']), true)) { return $copied[$url] = null; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => false, CURLOPT_TIMEOUT => 30, CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_MAXFILESIZE => 10 * 1024 * 1024, ]); $data = curl_exec($ch); $code = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch); if (!is_string($data) || $code !== 200 || strlen($data) > 10 * 1024 * 1024) { return $copied[$url] = null; } $info = @getimagesizefromstring($data); $ext = [IMAGETYPE_WEBP => 'webp', IMAGETYPE_PNG => 'png', IMAGETYPE_JPEG => 'jpg'][$info[2] ?? 0] ?? null; if (!$ext) { return $copied[$url] = null; } $sub = date('Y/m'); $dir = rtrim($c['images_dir'], '/') . '/' . $sub; if (!is_dir($dir) && !@mkdir($dir, 0755, true)) { return $copied[$url] = null; } $name = bin2hex(random_bytes(12)) . '.' . $ext; if (file_put_contents($dir . '/' . $name, $data) === false) { return $copied[$url] = null; } return $copied[$url] = [ 'path' => $sub . '/' . $name, 'url' => rtrim($c['images_url'], '/') . '/' . $sub . '/' . $name, ]; } function dabir_localize_images(array $c, string $html): string { return (string)preg_replace_callback('/(]*\ssrc=")([^"]+)(")/i', function ($m) use ($c) { $local = dabir_image($c, html_entity_decode($m[2])); return $m[1] . ($local ? htmlspecialchars($local['url'], ENT_QUOTES) : $m[2]) . $m[3]; }, $html); }