Joywork/admin/migrate-articles.php

206 lines
9.1 KiB
PHP
Raw Normal View History

2026-05-22 20:21:54 +02:00
<?php
require_once($_SERVER['DOCUMENT_ROOT'] . "/config.php");
if (!$_SESSION['superadmin']) { die('Доступ запрещён'); }
// Карта id → slug (из существующих PHP-заглушек)
$slugMap = [
1 => 'poisk-obektov-nedvizhimosti-scanner',
2 => 'multilistingovaya-sistema-partnerskikh-sdelok',
3 => 'voronki-prodazh-v-crm',
4 => 'avtopublikaciya-obyavlenij-na-200-sajtov',
5 => 'ip-telefoniya-i-kolltraking-v-crm',
6 => 'dokumentooborot-v-crm-dlja-rieltorov',
7 => 'baza-kommercheskoj-nedvizhimosti',
8 => 'kabinet-klienta-v-crm',
9 => 'odobrjaem-ipoteku-onlajn-broker',
10 => 'skvoznaja-analitika-v-nedvizhimosti',
11 => 'reklama-kvartiry-v-socsetyah',
12 => 'crm-dlja-agenta-reklama-200-sajtov',
13 => 'poisk-obektov-na-tematicheskih-sajtah',
14 => 'smart-voronka-prodazh-v-crm',
15 => 'integraciya-crm-s-ip-telefoniej',
16 => 'zakaz-svedenij-egrn-iz-crm',
17 => 'nakoplenie-klientskoj-bazy-targetirovannaya-reklama',
18 => 'dokumentooborot-rieltora-v-crm',
19 => 'avtomaticheskij-poisk-v-crm',
20 => 'upravlenie-tipami-razmeshchenij-obektov',
21 => 'internet-marketing-v-nedvizhimosti',
22 => 'kak-reklamirovat-nedvizhimost-na-ploshchadkah',
23 => 'strategii-prodvizheniya-agentstva-2023',
24 => 'analiz-rynka-rijeltorskih-uslug',
25 => 'servisy-po-poisku-nedvizhimosti',
26 => 'marketing-dlja-zastrojshhikov',
27 => 'analiz-obektov-nedvizhimosti',
28 => 'prezentacija-obektov-nedvizhimosti',
29 => 'parser-nedvizhimosti',
30 => 'poisk-obektov-nedvizhimosti-v-rezhime-online',
31 => 'kak-uvelichit-prodazhi-nedvizhimosti',
32 => 'na-kakih-sajtah-prodavat-nedvizhimost',
33 => 'avtomatizacija-prodazh-nedvizhimosti',
34 => 'instrumenty-rijeltora-nedvizhimosti',
35 => 'lidy-dlja-agentstva-nedvizhimosti',
36 => 'cel-vnedrenija-crm',
37 => 'rijeltor-nedvizhimosti-s-nulja',
38 => 'houmstejdzhing-kvartiry',
39 => 'prognoz-rynka-nedvizhimosti-2024',
40 => 'otchet-rijeltora-o-rabote',
41 => 'baza-novostroek-ot-zastrojshhika',
42 => 'klienty-na-nedvizhimost',
43 => 'opredelit-rynochnuju-stoimost-obekta',
44 => 'avtovygruzka-nedvizhimosti-na-avito',
45 => 'kak-otkryt-agentstvo-nedvizhimosti',
46 => 'flipping-v-nedvizhimosti',
47 => 'cifrovye-tehnologii-v-nedvizhimosti',
48 => 'cenoobrazovanie-na-rynke-nedvizhimosti',
49 => 'crm-sdelki-s-nedvizhimostju',
50 => 'upravlenie-reklamnymi-kampanijami',
51 => 'marketing-dlja-zastrojshhikov-vazhnost',
52 => 'srm-dlja-stroitelnoj-kompanii',
53 => 'kak-rijeltoru-najti-klientov',
54 => 'sajt-dlja-agentstva-nedvizhimosti',
55 => 'brend-agentstva-nedvizhimosti',
56 => 'reklama-dlja-rijeltora',
57 => 'franshiza-agentstva-nedvizhimosti',
58 => 'skript-holodnogo-zvonka',
59 => 'biznes-plan-agentstva-nedvizhimosti',
60 => 'struktura-agentstva-nedvizhimosti',
61 => 'oshibki-pri-vnedrenii-crm',
62 => 'cifrovizacija-nedvizhimosti',
63 => 'segmentacija-klientov-srm',
64 => 'utp-dlja-nedvizhimosti',
65 => 'avtomatizacija-raboty-rieltora',
66 => 'avtomatizaciya-otdela-prodazh-nedvizhimosti',
67 => 'otsenka-kvartiry-brokerom',
68 => 'servis-dlya-analiza-nedvizhimosti',
];
$src = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/article.php');
// Извлекаем скалярные поля метаданных через regex (без eval)
function extractArtField($src, $id, $field) {
if (preg_match('/\$art' . $id . '\[\'' . $field . '\'\]\s*=\s*"([^"]*?)"/u', $src, $m)) return $m[1];
if (preg_match("/\\\$art{$id}\\['{$field}'\\]\\s*=\\s*'([^']*?)'/u", $src, $m)) return $m[1];
return '';
}
function extractArtAuthorField($src, $id, $field) {
if (preg_match('/\$art' . $id . '\[\'author\'\]\s*=\s*\[.*?\'' . $field . '\'\s*=>\s*\'([^\']*?)\'/su', $src, $m)) return $m[1];
if (preg_match('/\$art' . $id . '\[\'author\'\]\s*=\s*\[.*?\'' . $field . '\'\s*=>\s*"([^"]*?)"/su', $src, $m)) return $m[1];
return '';
}
// Извлекаем HTML-контент из блоков if ($id == X) { ... }
function extractArtContent($src, $id) {
if (preg_match('/\$id == ' . $id . '\) \{ \?>(.*?)(?=<\?php \})/su', $src, $m)) {
return trim($m[1]);
}
return '';
}
function convertDate($d) {
$parts = explode('.', $d);
if (count($parts) === 3) return $parts[2] . '-' . $parts[1] . '-' . $parts[0];
return date('Y-m-d');
}
// Генерируем description из первого абзаца статьи (для SEO и карточек блога)
function generateDescription($content) {
if (!$content) return '';
// Берём первый <p> в статье
if (preg_match('/<p[^>]*>(.*?)<\/p>/s', $content, $m)) {
$text = strip_tags($m[1]);
$text = preg_replace('/\s+/u', ' ', $text);
$text = trim(html_entity_decode($text, ENT_QUOTES, 'UTF-8'));
// Обрезаем до ~160 символов (стандарт meta description), не разрезая слова
if (mb_strlen($text, 'UTF-8') > 160) {
$text = mb_substr($text, 0, 160, 'UTF-8');
$lastSpace = mb_strrpos($text, ' ', 0, 'UTF-8');
if ($lastSpace !== false) $text = mb_substr($text, 0, $lastSpace, 'UTF-8');
$text .= '…';
}
return $text;
}
return '';
}
// Считаем количество <script> блоков в контенте (для отчёта в dry-run)
function countScripts($content) {
return preg_match_all('/<script[\s\S]*?<\/script>/i', $content);
}
$dryRun = isset($_GET['dry']) && $_GET['dry'] == '1';
$results = [];
$errors = [];
for ($i = 1; $i <= 68; $i++) {
$title = extractArtField($src, $i, 'title');
if (!$title) {
$errors[] = "Нет title для статьи #$i";
continue;
}
$slug = isset($slugMap[$i]) ? $slugMap[$i] : Article::makeSlug($title);
$content = extractArtContent($src, $i);
$dateRaw = extractArtField($src, $i, 'date');
$updateRaw = extractArtField($src, $i, 'update');
$data = [
'slug' => $slug,
'title' => $title,
'page_title' => extractArtField($src, $i, 'pageTitle') ?: $title,
'description' => generateDescription($content),
'content' => $content,
'date_created' => $dateRaw ? convertDate($dateRaw) : date('Y-m-d'),
'date_updated' => $updateRaw ? convertDate($updateRaw) : date('Y-m-d'),
'reading_time' => max(1, (int)extractArtField($src, $i, 'time')),
'image' => extractArtField($src, $i, 'image'),
'author_name' => extractArtAuthorField($src, $i, 'name') ?: 'Станислав Дмитриев',
'author_img' => extractArtAuthorField($src, $i, 'img') ?: '/images/articles/rukofodJW.png',
'author_job' => extractArtAuthorField($src, $i, 'job') ?: 'руководитель JoyWork',
'published' => 1,
'sort_order' => $i,
];
$scriptsCount = countScripts($content);
if (!$dryRun) {
if (!Article::slugExists($slug)) {
Article::create($data);
$scriptsNote = $scriptsCount > 0 ? " | scripts: {$scriptsCount}" : '';
$results[] = "✓ #{$i}: {$title}{$scriptsNote}";
} else {
$results[] = "— #{$i}: пропущена (slug уже есть): {$slug}";
}
} else {
$contentInfo = strlen($content) ? strlen($content) . " симв." : '❌ пусто';
$scriptsInfo = $scriptsCount > 0 ? " | scripts: {$scriptsCount}" : '';
$descrInfo = $data['description'] ? ' | descr: ✓' : ' | descr: ❌';
$results[] = "[DRY] #{$i}: {$title}{$slug} | контент: {$contentInfo}{$scriptsInfo}{$descrInfo}";
}
}
?><!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Миграция статей</title>
<style>body{font-family:Arial;padding:20px;max-width:900px;} .ok{color:green;} .err{color:red;} pre{background:#f5f5f5;padding:15px;border-radius:4px;font-size:13px;line-height:1.6;white-space:pre-wrap;}</style>
</head><body>
<h2>Миграция статей из PHP в БД</h2>
<?php if ($dryRun): ?>
<p style="background:#fff3cd;padding:10px;border-radius:4px;">⚠️ Режим dry-run данные <strong>не записаны</strong>. Проверьте результаты и <a href="?">запустите реально</a>.</p>
<?php else: ?>
<p><a href="?dry=1">Dry-run (без записи)</a> | <a href="/admin/articles.php"> К списку статей</a></p>
<?php endif; ?>
<h3>Результат (<?= count($results) ?> / 68):</h3>
<pre><?php foreach ($results as $r) echo htmlspecialchars($r) . "\n"; ?></pre>
<?php if ($errors): ?>
<h3 class="err">Ошибки (<?= count($errors) ?>):</h3>
<pre class="err"><?php foreach ($errors as $e) echo htmlspecialchars($e) . "\n"; ?></pre>
<?php endif; ?>
<?php if (!$dryRun && empty($errors) && count($results) > 0): ?>
<p class="ok" style="font-size:16px;"><strong> Готово! <a href="/admin/articles.php">Перейти к статьям </a></strong></p>
<?php endif; ?>
</body></html>