Jak wylistować martwe embedy YouTube w postach na WordPress’ie


Uwaga! Wszystkie informacje i solucje zawarte w postach typu DYI mogą z czasem przestać być aktualne. Niektóre publikowane kody źródłowe, będąc zależnymi od używanych konfiguracji platform i sprzętu, mogą nie działać, lub działać nieprawidłowo u niektórych użytkowników. Stosując opisane rozwiązania przyjmujesz do wiadomości i zgadzasz się, że nie ponoszę odpowiedzialności za ich finalne efekty.


Martwe embedy filmów z YouTube’a zdarzają się bardzo często na stronach, które są prowadzone od bardzo długiego czasu, mogących pochwalić się setkami postów. Naprawdę praktycznie się nie zdarza, żeby komuś chciało się przedzierać ręcznie np. przez dwa tysiące publikacji, żeby sprawdzić, czy przypadkiem osadzenie jakiegoś filmu nie stało się aktywne. Być może kiedyś tak się robiło, dzisiaj jednak przychodzi z pomocą używanie skryptów.

W poniższym przykładzie ważne są dwie kwestie: po pierwsze przetwarzanie setek postów na raz z dużym prawdopodobieństwem wygeneruje błąd serwera – 504  Gateway Time-out. To typowy error, który występuje np. w sytuacji kiedy serwer – pośrednik (np. Apache, czy Nginx) czeka zbyt długo na zakończenie działania skryptu. Aby zapobiec takiej sytuacji, skrypt będzie pobierał dane w porcjach, listował je przyrostowo na stronie, zapamiętując za pomocą sesji gdzie kończy się ostatnio przetworzona porcja danych.

Druga ważna kwestia to metodologia sprawdzania, który embed jest aktywny, a który nie. Przy „normalnym” odpytywaniu dochodzi do przekłamań, np. sytuacji, w których film oznaczony jako prywatny jest traktowany jak istniejący (a przecież na stronie nie da się go wyświetlić). Z pomocą przychodzi YouTube oEmbed API – interfejs, który pozwala zweryfikować, czy dany film istnieje i jest dostępny. YouTube oEmbed API zwraca dane JSON tylko dla dostępnych filmów. Jeśli wideo nie istnieje lub jest prywatne, serwer YouTube zwraca błąd 404. Zaimplementuję to w w kodzie.

Update: okazało się, że wcześniejsza wersja kodu nie przetwarzała w prawidłowy sposób wszystkich postów. W związku z tym postanowiłem rozbić ją na dwa wątki – skrypt przetwarzający posty oraz „odbiornik” html/js wyświetlający je. Po testach uznaję, że to rozwiązanie działa prawidłowo.

<?php
session_start();
require_once(’wp-config.php’);

header(’Content-Type: application/json’);

// Połączenie z bazą danych
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
if ($mysqli->connect_error) {
die(json_encode([’error’ => „Błąd połączenia: ” . $mysqli->connect_error]));
}

// Rozmiar porcji
$batchSize = 20;

// Offset – pamiętany w sesji
if (!isset($_SESSION[’offset’])) {
$_SESSION[’offset’] = 0;
}

// Pobierz porcję postów
$query = $mysqli->prepare(„SELECT ID, post_title, post_content
FROM {$table_prefix}posts
WHERE post_status = 'publish’
AND post_type = 'post’
LIMIT ?, ?”);
$query->bind_param(„ii”, $_SESSION[’offset’], $batchSize);
$query->execute();
$result = $query->get_result();

// Wyniki przetwarzania
$processed = [];
while ($row = $result->fetch_assoc()) {
$postId = $row[’ID’];
$title = $row[’post_title’];
$content = $row[’post_content’];

// Znajdź wszystkie linki YouTube w treści
$links = [];
preg_match_all(’/(https?:\/\/(?:www\.)?youtube\.com\/watch\?v=[\w-]+|https?:\/\/youtu\.be\/[\w-]+)/’, $content, $matches);
$links = array_merge($links, $matches[0]);

// Znajdź embedy w formacie [youtube_sc url=…]
preg_match_all(’/\[youtube_sc url=([\w-]+)\]/’, $content, $embedMatches);
foreach ($embedMatches[1] as $videoId) {
$links[] = „https://youtube.com/watch?v=” . $videoId;
}

// Sprawdź poprawność linków w YouTube oEmbed
$invalidLinks = [];
foreach ($links as $link) {
$oembedUrl = „https://www.youtube.com/oembed?url=” . urlencode($link) . „&format=json”;
$response = @file_get_contents($oembedUrl);
if ($response === false) {
$invalidLinks[] = $link;
}
}

$processed[] = [
'title’ => $title,
'url’ => get_permalink($postId),
'links’ => $links,
'invalidLinks’ => $invalidLinks,
];
}

// Zwiększ offset
$_SESSION[’offset’] += $batchSize;

// Sprawdź, czy to koniec przetwarzania
$moreToProcess = $result->num_rows === $batchSize;

// Zwróć dane w formacie JSON
echo json_encode([
'processed’ => $processed,
'moreToProcess’ => $moreToProcess,
]);


<!DOCTYPE html>
<html lang=”pl”>
<head>
<meta charset=”UTF-8″>
<title>Przetwarzanie postów WordPress</title>
<style>
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
}
th {
background-color: #f2f2f2;
text-align: left;
}
td {
vertical-align: top;
}
</style>
</head>
<body>
<h1>Przetwarzanie postów WordPress</h1>
<table>
<thead>
<tr>
<th>L.P.</th>
<th>Tytuł</th>
<th>Linki YouTube</th>
<th>Niepoprawne linki</th>
</tr>
</thead>
<tbody id=”results”></tbody>
</table>
<script>
let counter = 1; // Licznik dla numeracji L.P.

async function processPosts() {
try {
const response = await fetch(’ytu.php’); //zamiast ytu.php podaj nazwę pliku ze skryptem php
const data = await response.json();

if (data.error) {
console.error(data.error);
return;
}

const tbody = document.getElementById(’results’);
data.processed.forEach(post => {
// Sprawdź, czy post ma niepoprawne linki
if (post.invalidLinks.length > 0) {
const row = document.createElement(’tr’);

// Numeracja L.P.
const lpCell = document.createElement(’td’);
lpCell.textContent = counter++;
row.appendChild(lpCell);

// Tytuł z linkiem
const titleCell = document.createElement(’td’);
const titleLink = document.createElement(’a’);
titleLink.href = post.url;
titleLink.textContent = post.title;
titleLink.target = '_blank’;
titleCell.appendChild(titleLink);
row.appendChild(titleCell);

// Linki YouTube
const linksCell = document.createElement(’td’);
post.links.forEach(youtubeLink => {
const link = document.createElement(’a’);
link.href = youtubeLink;
link.textContent = youtubeLink;
link.target = '_blank’;
linksCell.appendChild(link);
linksCell.appendChild(document.createElement(’br’));
});
row.appendChild(linksCell);

// Niepoprawne linki
const invalidLinksCell = document.createElement(’td’);
post.invalidLinks.forEach(invalidLink => {
const link = document.createElement(’a’);
link.href = invalidLink;
link.textContent = invalidLink;
link.style.color = 'red’;
invalidLinksCell.appendChild(link);
invalidLinksCell.appendChild(document.createElement(’br’));
});
row.appendChild(invalidLinksCell);

tbody.appendChild(row);
}
});

if (data.moreToProcess) {
setTimeout(processPosts, 1000);
} else {
console.log(’Przetwarzanie zakończone.’);
}
} catch (error) {
console.error(’Wystąpił błąd:’, error);
}
}

// Start przetwarzania
processPosts();
</script>
</body>
</html>