wordpress_import.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. <?php
  2. /**
  3. * WordPress Database Import Tool
  4. * Imports posts, categories, users, and comments from WordPress to the publication system
  5. */
  6. class WordPressImport {
  7. private $wpDb;
  8. private $targetDb;
  9. private $wpConfig;
  10. private $importLog = [];
  11. private $errors = [];
  12. public function __construct($wpConfig) {
  13. error_log('WordPressImport constructor called');
  14. $this->wpConfig = $wpConfig;
  15. $this->targetDb = Database::getInstance();
  16. error_log('WordPressImport constructor completed');
  17. // Don't connect in constructor - connect on demand to avoid hanging
  18. }
  19. /**
  20. * Connect to WordPress database
  21. */
  22. private function connectWordPress() {
  23. try {
  24. // Set timeout options
  25. $options = [
  26. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  27. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  28. PDO::ATTR_TIMEOUT => 10, // 10 second timeout
  29. PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"
  30. ];
  31. $dsn = "mysql:host={$this->wpConfig['host']};dbname={$this->wpConfig['database']};charset=utf8mb4";
  32. $this->wpDb = new PDO($dsn, $this->wpConfig['username'], $this->wpConfig['password'], $options);
  33. // Test connection with a simple query
  34. $this->wpDb->query("SELECT 1");
  35. $this->log('Connected to WordPress database successfully');
  36. } catch (PDOException $e) {
  37. throw new Exception("Failed to connect to WordPress database: " . $e->getMessage());
  38. } catch (Exception $e) {
  39. throw new Exception("WordPress database connection error: " . $e->getMessage());
  40. }
  41. }
  42. /**
  43. * Test WordPress connection and verify structure
  44. */
  45. public function testConnection() {
  46. try {
  47. // Connect first
  48. $this->connectWordPress();
  49. // Check if WordPress tables exist
  50. $tables = ['wp_posts', 'wp_users', 'wp_terms', 'wp_term_taxonomy', 'wp_term_relationships', 'wp_comments'];
  51. $existingTables = [];
  52. foreach ($tables as $table) {
  53. $stmt = $this->wpDb->query("SHOW TABLES LIKE '{$table}'");
  54. if ($stmt->rowCount() > 0) {
  55. $existingTables[] = $table;
  56. }
  57. }
  58. if (count($existingTables) < 6) {
  59. throw new Exception("WordPress database structure incomplete. Missing tables: " . implode(', ', array_diff($tables, $existingTables)));
  60. }
  61. // Get basic stats
  62. $stats = [
  63. 'posts' => $this->getPostCount(),
  64. 'pages' => $this->getPageCount(),
  65. 'categories' => $this->getCategoryCount(),
  66. 'users' => $this->getUserCount(),
  67. 'comments' => $this->getCommentCount()
  68. ];
  69. return ['success' => true, 'stats' => $stats, 'tables' => $existingTables];
  70. } catch (Exception $e) {
  71. return ['success' => false, 'error' => $e->getMessage()];
  72. }
  73. }
  74. /**
  75. * Import all WordPress data
  76. */
  77. public function importAll($options = []) {
  78. error_log('importAll() called with options: ' . print_r($options, true));
  79. $results = [];
  80. try {
  81. // Connect to WordPress database first
  82. error_log('About to call connectWordPress()');
  83. $this->connectWordPress();
  84. error_log('connectWordPress() completed');
  85. // Start transaction
  86. error_log('About to start transaction');
  87. $this->targetDb->beginTransaction();
  88. error_log('Transaction started');
  89. // Import categories first (posts depend on them)
  90. if ($options['import_categories'] ?? true) {
  91. $results['categories'] = $this->importCategories();
  92. }
  93. // Import users
  94. if ($options['import_users'] ?? true) {
  95. $results['users'] = $this->importUsers();
  96. }
  97. // Import posts
  98. if ($options['import_posts'] ?? true) {
  99. $results['posts'] = $this->importPosts();
  100. }
  101. // Import comments
  102. if ($options['import_comments'] ?? true) {
  103. $results['comments'] = $this->importComments();
  104. }
  105. // Commit transaction
  106. $this->targetDb->commit();
  107. $this->log('Import completed successfully');
  108. return ['success' => true, 'results' => $results, 'log' => $this->importLog];
  109. } catch (Exception $e) {
  110. $this->targetDb->rollBack();
  111. $this->errors[] = $e->getMessage();
  112. $this->log('Import failed: ' . $e->getMessage(), 'error');
  113. return ['success' => false, 'error' => $e->getMessage(), 'log' => $this->importLog, 'errors' => $this->errors];
  114. }
  115. }
  116. /**
  117. * Import WordPress categories
  118. */
  119. public function importCategories() {
  120. $this->log('Starting categories import');
  121. $imported = 0;
  122. $skipped = 0;
  123. // Connect to WordPress database if not already connected
  124. if ($this->wpDb === null) {
  125. $this->connectWordPress();
  126. }
  127. // Get WordPress categories
  128. $stmt = $this->wpDb->query("
  129. SELECT t.name, tt.description, tt.term_id
  130. FROM wp_terms t
  131. JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  132. WHERE tt.taxonomy = 'category'
  133. ORDER BY t.name
  134. ");
  135. $categories = $stmt->fetchAll();
  136. foreach ($categories as $wpCategory) {
  137. try {
  138. // Check if category already exists
  139. $existing = $this->targetDb->fetch(
  140. "SELECT id FROM categories WHERE name = ?",
  141. [$wpCategory['name']]
  142. );
  143. if ($existing) {
  144. $skipped++;
  145. $this->log("Category '{$wpCategory['name']}' already exists, skipping");
  146. continue;
  147. }
  148. // Insert new category
  149. $this->targetDb->execute(
  150. "INSERT INTO categories (name, description, created_at) VALUES (?, ?, NOW())",
  151. [
  152. $wpCategory['name'],
  153. $wpCategory['description'] ?? ''
  154. ]
  155. );
  156. $imported++;
  157. $this->log("Imported category: {$wpCategory['name']}");
  158. } catch (Exception $e) {
  159. $this->errors[] = "Error importing category '{$wpCategory['name']}': " . $e->getMessage();
  160. $this->log("Error importing category '{$wpCategory['name']}': " . $e->getMessage(), 'error');
  161. }
  162. }
  163. $this->log("Categories import completed: {$imported} imported, {$skipped} skipped");
  164. return ['imported' => $imported, 'skipped' => $skipped];
  165. }
  166. /**
  167. * Import WordPress users
  168. */
  169. public function importUsers() {
  170. $this->log('Starting users import');
  171. $imported = 0;
  172. $skipped = 0;
  173. // Connect to WordPress database if not already connected
  174. if ($this->wpDb === null) {
  175. $this->connectWordPress();
  176. }
  177. // Get WordPress users
  178. $stmt = $this->wpDb->query("
  179. SELECT ID, user_login, user_email, user_nicename, user_registered, display_name
  180. FROM wp_users
  181. WHERE user_status = 0
  182. ORDER BY ID
  183. ");
  184. $users = $stmt->fetchAll();
  185. foreach ($users as $wpUser) {
  186. try {
  187. // Check if user already exists
  188. $existing = $this->targetDb->fetch(
  189. "SELECT id FROM users WHERE username = ?",
  190. [$wpUser['user_login']]
  191. );
  192. if ($existing) {
  193. $skipped++;
  194. $this->log("User '{$wpUser['user_login']}' already exists, skipping");
  195. continue;
  196. }
  197. // Determine user role (WordPress usermeta table)
  198. $role = $this->getUserRole($wpUser['ID']);
  199. // Insert new user
  200. $this->targetDb->execute(
  201. "INSERT INTO users (username, email, role, auth_type, created_at) VALUES (?, ?, ?, 'wordpress', ?)",
  202. [
  203. $wpUser['user_login'],
  204. $wpUser['user_email'],
  205. $role,
  206. $wpUser['user_registered']
  207. ]
  208. );
  209. $imported++;
  210. $this->log("Imported user: {$wpUser['user_login']} (role: {$role})");
  211. } catch (Exception $e) {
  212. $this->errors[] = "Error importing user '{$wpUser['user_login']}': " . $e->getMessage();
  213. $this->log("Error importing user '{$wpUser['user_login']}': " . $e->getMessage(), 'error');
  214. }
  215. }
  216. $this->log("Users import completed: {$imported} imported, {$skipped} skipped");
  217. return ['imported' => $imported, 'skipped' => $skipped];
  218. }
  219. /**
  220. * Import WordPress posts
  221. */
  222. public function importPosts() {
  223. $this->log('Starting posts import');
  224. $imported = 0;
  225. $skipped = 0;
  226. try {
  227. // Connect to WordPress database if not already connected
  228. if ($this->wpDb === null) {
  229. $this->connectWordPress();
  230. }
  231. // Get WordPress posts with author info in one query
  232. $stmt = $this->wpDb->query("
  233. SELECT p.ID, p.post_title, p.post_content, p.post_excerpt, p.post_date,
  234. p.post_modified, p.post_status, p.post_author, p.post_name,
  235. u.display_name as author_name
  236. FROM wp_posts p
  237. LEFT JOIN wp_users u ON p.post_author = u.ID
  238. WHERE p.post_type = 'post' AND p.post_status IN ('publish', 'draft')
  239. ORDER BY p.post_date
  240. LIMIT 1000
  241. ");
  242. $posts = $stmt->fetchAll();
  243. // Get all categories for all posts in one query
  244. $postIds = array_column($posts, 'ID');
  245. $categoriesMap = [];
  246. if (!empty($postIds)) {
  247. $placeholders = str_repeat('?,', count($postIds) - 1) . '?';
  248. $categoriesStmt = $this->wpDb->prepare("
  249. SELECT tr.object_id as post_id, t.name as category_name
  250. FROM wp_term_relationships tr
  251. JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  252. JOIN wp_terms t ON tt.term_id = t.term_id
  253. WHERE tt.taxonomy = 'category' AND tr.object_id IN ($placeholders)
  254. ");
  255. $categoriesStmt->execute($postIds);
  256. foreach ($categoriesStmt->fetchAll() as $cat) {
  257. $categoriesMap[$cat['post_id']][] = $cat['category_name'];
  258. }
  259. }
  260. foreach ($posts as $wpPost) {
  261. try {
  262. // Generate slug from post_name or title
  263. $slug = !empty($wpPost['post_name']) ? $wpPost['post_name'] : $this->generateSlug($wpPost['post_title']);
  264. // Map WordPress status to our status
  265. $status = ($wpPost['post_status'] === 'publish') ? 'published' : 'draft';
  266. // Use author name from query or fallback
  267. $author = $wpPost['author_name'] ?: 'Unknown Author';
  268. // Get categories from preloaded map
  269. $categories = $categoriesMap[$wpPost['ID']] ?? [];
  270. // Insert post
  271. $this->targetDb->execute(
  272. "INSERT INTO publications (title, slug, content, summary, author, status, created_at, updated_at, published_at)
  273. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
  274. [
  275. $wpPost['post_title'],
  276. $slug,
  277. $this->processContent($wpPost['post_content']),
  278. $wpPost['post_excerpt'] ?? '',
  279. $author,
  280. $status,
  281. $wpPost['post_date'],
  282. $wpPost['post_modified'],
  283. ($status === 'published') ? $wpPost['post_date'] : null
  284. ]
  285. );
  286. $publicationId = $this->targetDb->lastInsertId();
  287. // Link categories
  288. if (!empty($categories)) {
  289. $this->linkPostCategories($publicationId, $categories);
  290. }
  291. $imported++;
  292. $this->log("Imported post: '{$wpPost['post_title']}'");
  293. } catch (Exception $e) {
  294. $this->errors[] = "Error importing post '{$wpPost['post_title']}': " . $e->getMessage();
  295. $this->log("Error importing post '{$wpPost['post_title']}': " . $e->getMessage(), 'error');
  296. }
  297. }
  298. } catch (Exception $e) {
  299. $this->errors[] = "Error in posts import: " . $e->getMessage();
  300. $this->log("Error in posts import: " . $e->getMessage(), 'error');
  301. }
  302. $this->log("Posts import completed: {$imported} imported, {$skipped} skipped");
  303. return ['imported' => $imported, 'skipped' => $skipped];
  304. }
  305. /**
  306. * Import WordPress comments
  307. */
  308. public function importComments() {
  309. $this->log('Starting comments import');
  310. $imported = 0;
  311. $skipped = 0;
  312. try {
  313. // Connect to WordPress database if not already connected
  314. if ($this->wpDb === null) {
  315. $this->connectWordPress();
  316. }
  317. // Get WordPress comments with post info in one query
  318. $stmt = $this->wpDb->query("
  319. SELECT c.comment_ID, c.comment_post_ID, c.comment_author, c.comment_author_email,
  320. c.comment_content, c.comment_date, c.comment_approved, c.comment_parent,
  321. p.post_name, p.post_title
  322. FROM wp_comments c
  323. JOIN wp_posts p ON c.comment_post_ID = p.ID
  324. WHERE p.post_type = 'post'
  325. ORDER BY c.comment_date
  326. LIMIT 2000
  327. ");
  328. $comments = $stmt->fetchAll();
  329. // Build a map of post IDs to publication IDs
  330. $postIds = array_unique(array_column($comments, 'comment_post_ID'));
  331. $publicationMap = [];
  332. if (!empty($postIds)) {
  333. $placeholders = str_repeat('?,', count($postIds) - 1) . '?';
  334. $pubStmt = $this->targetDb->prepare("
  335. SELECT id, slug, title FROM publications
  336. WHERE slug IN ($placeholders) OR title IN ($placeholders)
  337. ");
  338. // Duplicate the post IDs for slug and title matching
  339. $allParams = array_merge($postIds, $postIds);
  340. $pubStmt->execute($allParams);
  341. foreach ($pubStmt->fetchAll() as $pub) {
  342. // Map both slug and title for easier lookup
  343. $publicationMap[strtolower($pub['slug'])] = $pub['id'];
  344. $publicationMap[strtolower($pub['title'])] = $pub['id'];
  345. }
  346. }
  347. foreach ($comments as $wpComment) {
  348. try {
  349. // Find corresponding publication using preloaded map
  350. $publicationId = null;
  351. $postSlug = !empty($wpComment['post_name']) ? strtolower($wpComment['post_name']) : null;
  352. $postTitle = strtolower($wpComment['post_title']);
  353. if ($postSlug && isset($publicationMap[$postSlug])) {
  354. $publicationId = $publicationMap[$postSlug];
  355. } elseif (isset($publicationMap[$postTitle])) {
  356. $publicationId = $publicationMap[$postTitle];
  357. }
  358. if (!$publicationId) {
  359. $skipped++;
  360. $this->log("Comment skipped - no matching publication found for post ID {$wpComment['comment_post_ID']}");
  361. continue;
  362. }
  363. // Map comment status
  364. $status = ($wpComment['comment_approved'] === '1') ? 'approved' : 'pending';
  365. // Handle parent comment (skip for now to avoid complexity)
  366. $parentId = null;
  367. // Insert comment
  368. $this->targetDb->execute(
  369. "INSERT INTO comments (publication_id, parent_id, name, email, content, status, created_at, admin_reply)
  370. VALUES (?, ?, ?, ?, ?, ?, ?, FALSE)",
  371. [
  372. $publicationId,
  373. $parentId,
  374. $wpComment['comment_author'],
  375. $wpComment['comment_author_email'],
  376. $wpComment['comment_content'],
  377. $status,
  378. $wpComment['comment_date']
  379. ]
  380. );
  381. $commentId = $this->targetDb->lastInsertId();
  382. // Store WordPress comment ID for parent mapping
  383. $this->targetDb->execute(
  384. "UPDATE comments SET wp_comment_id = ? WHERE id = ?",
  385. [$wpComment['comment_ID'], $commentId]
  386. );
  387. $imported++;
  388. $this->log("Imported comment for post ID {$wpComment['comment_post_ID']}");
  389. } catch (Exception $e) {
  390. $this->errors[] = "Error importing comment: " . $e->getMessage();
  391. $this->log("Error importing comment: " . $e->getMessage(), 'error');
  392. }
  393. }
  394. } catch (Exception $e) {
  395. $this->errors[] = "Error in comments import: " . $e->getMessage();
  396. $this->log("Error in comments import: " . $e->getMessage(), 'error');
  397. }
  398. $this->log("Comments import completed: {$imported} imported, {$skipped} skipped");
  399. return ['imported' => $imported, 'skipped' => $skipped];
  400. }
  401. /**
  402. * Helper methods
  403. */
  404. private function getUserRole($userId) {
  405. $stmt = $this->wpDb->prepare("
  406. SELECT meta_value FROM wp_usermeta
  407. WHERE user_id = ? AND meta_key = 'wp_capabilities'
  408. ");
  409. $stmt->execute([$userId]);
  410. $capabilities = $stmt->fetchColumn();
  411. if ($capabilities && strpos($capabilities, 'administrator') !== false) {
  412. return 'admin';
  413. }
  414. return 'editor'; // Default role
  415. }
  416. private function getAuthorName($authorId) {
  417. $stmt = $this->wpDb->prepare("SELECT display_name FROM wp_users WHERE ID = ?");
  418. $stmt->execute([$authorId]);
  419. $name = $stmt->fetchColumn();
  420. return $name ?: 'Unknown Author';
  421. }
  422. private function getPostCategories($postId) {
  423. $stmt = $this->wpDb->prepare("
  424. SELECT t.name FROM wp_terms t
  425. JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  426. JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
  427. WHERE tr.object_id = ? AND tt.taxonomy = 'category'
  428. ");
  429. $stmt->execute([$postId]);
  430. return $stmt->fetchAll(PDO::FETCH_COLUMN);
  431. }
  432. private function getPostSlugById($postId) {
  433. $stmt = $this->wpDb->prepare("SELECT post_name FROM wp_posts WHERE ID = ?");
  434. $stmt->execute([$postId]);
  435. return $stmt->fetchColumn() ?: '';
  436. }
  437. private function getPostTitleById($postId) {
  438. $stmt = $this->wpDb->prepare("SELECT post_title FROM wp_posts WHERE ID = ?");
  439. $stmt->execute([$postId]);
  440. return $stmt->fetchColumn() ?: '';
  441. }
  442. private function linkPostCategories($publicationId, $categories) {
  443. foreach ($categories as $categoryName) {
  444. $category = $this->targetDb->fetch(
  445. "SELECT id FROM categories WHERE name = ?",
  446. [$categoryName]
  447. );
  448. if ($category) {
  449. $this->targetDb->execute(
  450. "INSERT IGNORE INTO publication_categories (publication_id, category_id) VALUES (?, ?)",
  451. [$publicationId, $category['id']]
  452. );
  453. }
  454. }
  455. }
  456. private function processContent($content) {
  457. // Basic WordPress content processing
  458. // You can extend this to handle shortcodes, etc.
  459. $content = str_replace('[caption]', '', $content);
  460. $content = str_replace('[/caption]', '', $content);
  461. $content = preg_replace('/\[gallery.*?\]/', '', $content);
  462. return $content;
  463. }
  464. private function generateSlug($title) {
  465. $slug = strtolower($title);
  466. $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
  467. $slug = trim($slug, '-');
  468. return $slug;
  469. }
  470. private function log($message, $level = 'info') {
  471. $this->importLog[] = [
  472. 'timestamp' => date('Y-m-d H:i:s'),
  473. 'level' => $level,
  474. 'message' => $message
  475. ];
  476. }
  477. /**
  478. * Get statistics methods
  479. */
  480. public function getPostCount() {
  481. $stmt = $this->wpDb->query("SELECT COUNT(*) FROM wp_posts WHERE post_type = 'post'");
  482. return $stmt->fetchColumn();
  483. }
  484. public function getPageCount() {
  485. $stmt = $this->wpDb->query("SELECT COUNT(*) FROM wp_posts WHERE post_type = 'page'");
  486. return $stmt->fetchColumn();
  487. }
  488. public function getCategoryCount() {
  489. $stmt = $this->wpDb->query("
  490. SELECT COUNT(*) FROM wp_term_taxonomy
  491. WHERE taxonomy = 'category'
  492. ");
  493. return $stmt->fetchColumn();
  494. }
  495. public function getUserCount() {
  496. $stmt = $this->wpDb->query("SELECT COUNT(*) FROM wp_users");
  497. return $stmt->fetchColumn();
  498. }
  499. public function getCommentCount() {
  500. $stmt = $this->wpDb->query("SELECT COUNT(*) FROM wp_comments");
  501. return $stmt->fetchColumn();
  502. }
  503. /**
  504. * Get import log
  505. */
  506. public function getLog() {
  507. return $this->importLog;
  508. }
  509. /**
  510. * Get errors
  511. */
  512. public function getErrors() {
  513. return $this->errors;
  514. }
  515. }