Migrarea conținutului dintr-un sistem legacy cu 19 ani de istorie (2008–2026) este un exercițiu de arheologie digitală. Când am preluat proiectul pentru Liceul pentru Deficienți de Vedere, m-am lovit de un CMS custom, fără API, unde fiecare pagină era o aventură de markup non-semantic. Iată cum am reușit să portăm peste 380 de articole într-un mod robust și idempotent.
Arhitectura Scraper-ului: CLI PHP și Caching
Am construit un scraper în PHP CLI care folosește cURL pentru request-uri și DOMDocument cu DOMXPath pentru parsare. Pentru a evita ban-urile și pentru a fi idempotenți, am implementat un strat de caching bazat pe hash-ul MD5 al URL-ului.
foreach (range(2008, 2026) as $year) {
$url = "noutati.php?do=year&year=$year";
$cacheFile = "cache/" . md5($url) . ".html";
if (!file_exists($cacheFile)) {
$html = curl_get($url);
file_put_contents($cacheFile, $html);
}
$dom = new DOMDocument();
@$dom->loadHTML(file_get_contents($cacheFile));
$xpath = new DOMXPath($dom);
// ... procesare link-uri
}
Cosmarul CP1252: Repararea diacriticelor la nivel de byte
Cea mai mare provocare a fost dubla encodare CP1252. Diacriticele apăreau corupte deoarece serverul legacy trimitea datele ca UTF-8, dar baza de date era stocată într-un format mixt. Am creat un map de fixup la nivel de byte.
function fix_encoding($str) {
$map = [
"\xC3\x88\xE2\x84\xA2" => "\xC8\x99", // ș
"\xC3\x83\xC2\x83" => "\xC4\x83", // ă
// ... 20+ mapări
];
return strtr($str, $map);
}
Extragerea conținutului: Strategia “Sibling Walk”
Deoarece totul era într-un div generic, am folosit o funcție de parsare care scanează frații nodului de dată până întâlnește o barieră (tag-uri ca aside, footer sau clase de sidebar).
function extract_content_after_date($node) {
$content = '';
$sibling = $node->nextSibling;
while ($sibling) {
if (in_array($sibling->nodeName, ['aside', 'nav', 'footer'])) break;
if (strpos($sibling->getAttribute('class'), 'sidebar') !== false) break;
$content .= $sibling->ownerDocument->saveHTML($sibling);
$sibling = $sibling->nextSibling;
}
return $content;
}
Inferența categoriilor și XML pentru WP All Import
Am dedus 7 categorii (ex: “Concursuri”, “Proiecte europene”) printr-o funcție de matching pe cuvinte cheie. XML-ul generat pentru WP All Import folosește CDATA pentru a proteja conținutul și câmpuri meta pentru a asigura deduplicarea prin ldv_id.
$xml = new SimpleXMLElement('<root/>');
$item = $xml->addChild('article');
$item->addChild('title', htmlspecialchars($title));
$item->addChild('content', '<![CDATA[' . $content . ']]>');
$item->addChild('ldv_id', $original_id); // Meta pentru deduplicare
Importul de documente și idempotența
Ultima piesă a puzzle-ului a fost importul fișierelor PDF. Am creat un script care scanează folderul uploads/documente-import/, mapând numele folderului la taxonomii WordPress. Verificarea _doc_file_url în baza de date meta previne crearea de fișiere duplicate la fiecare rulare a scriptului.
Această abordare ne-a permis să migrăm 19 ani de istorie fără a pierde integritatea datelor și, cel mai important, fără a crea duplicate, oferind o bază curată pentru viitorul digital al liceului.

