wordpress_import.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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->query(
  150. "INSERT INTO categories (name, description, created_at) VALUES (?, ?, NOW())",
  151. [$wpCategory['name'], $wpCategory['description'] ?? '']
  152. );
  153. $imported++;
  154. $this->log("Imported category: {$wpCategory['name']}");
  155. } catch (Exception $e) {
  156. $this->errors[] = "Error importing category '{$wpCategory['name']}': " . $e->getMessage();
  157. $this->log("Error importing category '{$wpCategory['name']}': " . $e->getMessage(), 'error');
  158. }
  159. }
  160. $this->log("Categories import completed: {$imported} imported, {$skipped} skipped");
  161. return ['imported' => $imported, 'skipped' => $skipped];
  162. }
  163. /**
  164. * Import WordPress users
  165. */
  166. public function importUsers() {
  167. $this->log('Starting users import');
  168. $imported = 0;
  169. $skipped = 0;
  170. // Connect to WordPress database if not already connected
  171. if ($this->wpDb === null) {
  172. $this->connectWordPress();
  173. }
  174. // Get WordPress users
  175. $stmt = $this->wpDb->query("
  176. SELECT ID, user_login, user_email, user_nicename, user_registered, display_name
  177. FROM wp_users
  178. WHERE user_status = 0
  179. ORDER BY ID
  180. ");
  181. $users = $stmt->fetchAll();
  182. foreach ($users as $wpUser) {
  183. try {
  184. // Check if user already exists
  185. $existing = $this->targetDb->fetch(
  186. "SELECT id FROM users WHERE username = ?",
  187. [$wpUser['user_login']]
  188. );
  189. if ($existing) {
  190. $skipped++;
  191. $this->log("User '{$wpUser['user_login']}' already exists, skipping");
  192. continue;
  193. }
  194. // Determine user role (WordPress usermeta table)
  195. $role = $this->getUserRole($wpUser['ID']);
  196. // Insert new user
  197. $this->targetDb->query(
  198. "INSERT INTO users (username, email, role, auth_type, created_at) VALUES (?, ?, ?, 'wordpress', ?)",
  199. [
  200. $wpUser['user_login'],
  201. $wpUser['user_email'],
  202. $role,
  203. $wpUser['user_registered']
  204. ]
  205. );
  206. $imported++;
  207. $this->log("Imported user: {$wpUser['user_login']} (role: {$role})");
  208. } catch (Exception $e) {
  209. $this->errors[] = "Error importing user '{$wpUser['user_login']}': " . $e->getMessage();
  210. $this->log("Error importing user '{$wpUser['user_login']}': " . $e->getMessage(), 'error');
  211. }
  212. }
  213. $this->log("Users import completed: {$imported} imported, {$skipped} skipped");
  214. return ['imported' => $imported, 'skipped' => $skipped];
  215. }
  216. /**
  217. * Import WordPress posts
  218. */
  219. public function importPosts() {
  220. $this->log('Starting posts import');
  221. $imported = 0;
  222. $skipped = 0;
  223. try {
  224. // Connect to WordPress database if not already connected
  225. if ($this->wpDb === null) {
  226. $this->connectWordPress();
  227. }
  228. // Get WordPress posts with author info in one query
  229. $stmt = $this->wpDb->query("
  230. SELECT p.ID, p.post_title, p.post_content, p.post_excerpt, p.post_date,
  231. p.post_modified, p.post_status, p.post_author, p.post_name,
  232. u.display_name as author_name
  233. FROM wp_posts p
  234. LEFT JOIN wp_users u ON p.post_author = u.ID
  235. WHERE p.post_type = 'post' AND p.post_status IN ('publish', 'draft')
  236. ORDER BY p.post_date
  237. LIMIT 1000
  238. ");
  239. $posts = $stmt->fetchAll();
  240. // Get all categories for all posts in one query
  241. $postIds = array_column($posts, 'ID');
  242. $categoriesMap = [];
  243. if (!empty($postIds)) {
  244. $placeholders = str_repeat('?,', count($postIds) - 1) . '?';
  245. $categoriesStmt = $this->wpDb->prepare("
  246. SELECT tr.object_id as post_id, t.name as category_name
  247. FROM wp_term_relationships tr
  248. JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  249. JOIN wp_terms t ON tt.term_id = t.term_id
  250. WHERE tt.taxonomy = 'category' AND tr.object_id IN ($placeholders)
  251. ");
  252. $categoriesStmt->execute($postIds);
  253. foreach ($categoriesStmt->fetchAll() as $cat) {
  254. $categoriesMap[$cat['post_id']][] = $cat['category_name'];
  255. }
  256. }
  257. foreach ($posts as $wpPost) {
  258. try {
  259. // Generate slug from post_name or title
  260. $slug = !empty($wpPost['post_name']) ? $wpPost['post_name'] : $this->generateSlug($wpPost['post_title']);
  261. // Map WordPress status to our status
  262. $status = ($wpPost['post_status'] === 'publish') ? 'published' : 'draft';
  263. // Use author name from query or fallback
  264. $author = $wpPost['author_name'] ?: 'Unknown Author';
  265. // Get categories from preloaded map
  266. $categories = $categoriesMap[$wpPost['ID']] ?? [];
  267. // Insert post
  268. $this->targetDb->query(
  269. "INSERT INTO publications (title, slug, content, summary, author, status, created_at, updated_at, published_at)
  270. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
  271. [
  272. $wpPost['post_title'],
  273. $slug,
  274. $this->processContent($wpPost['post_content']),
  275. $wpPost['post_excerpt'] ?? '',
  276. $author,
  277. $status,
  278. $wpPost['post_date'],
  279. $wpPost['post_modified'],
  280. ($status === 'published') ? $wpPost['post_date'] : null
  281. ]
  282. );
  283. $publicationId = $this->targetDb->lastInsertId();
  284. // Link categories
  285. if (!empty($categories)) {
  286. $this->linkPostCategories($publicationId, $categories);
  287. }
  288. $imported++;
  289. $this->log("Imported post: '{$wpPost['post_title']}'");
  290. } catch (Exception $e) {
  291. $this->errors[] = "Error importing post '{$wpPost['post_title']}': " . $e->getMessage();
  292. $this->log("Error importing post '{$wpPost['post_title']}': " . $e->getMessage(), 'error');
  293. }
  294. }
  295. } catch (Exception $e) {
  296. $this->errors[] = "Error in posts import: " . $e->getMessage();
  297. $this->log("Error in posts import: " . $e->getMessage(), 'error');
  298. }
  299. $this->log("Posts import completed: {$imported} imported, {$skipped} skipped");
  300. return ['imported' => $imported, 'skipped' => $skipped];
  301. }
  302. /**
  303. * Import WordPress comments
  304. */
  305. public function importComments() {
  306. $this->log('Starting comments import');
  307. $imported = 0;
  308. $skipped = 0;
  309. try {
  310. // Connect to WordPress database if not already connected
  311. if ($this->wpDb === null) {
  312. $this->connectWordPress();
  313. }
  314. // Get WordPress comments with post info in one query
  315. $stmt = $this->wpDb->query("
  316. SELECT c.comment_ID, c.comment_post_ID, c.comment_author, c.comment_author_email,
  317. c.comment_content, c.comment_date, c.comment_approved, c.comment_parent,
  318. p.post_name, p.post_title
  319. FROM wp_comments c
  320. JOIN wp_posts p ON c.comment_post_ID = p.ID
  321. WHERE p.post_type = 'post'
  322. ORDER BY c.comment_date
  323. LIMIT 2000
  324. ");
  325. $comments = $stmt->fetchAll();
  326. // Build a map of post IDs to publication IDs
  327. $postIds = array_unique(array_column($comments, 'comment_post_ID'));
  328. $publicationMap = [];
  329. if (!empty($postIds)) {
  330. $placeholders = str_repeat('?,', count($postIds) - 1) . '?';
  331. $pubStmt = $this->targetDb->prepare("
  332. SELECT id, slug, title FROM publications
  333. WHERE slug IN ($placeholders) OR title IN ($placeholders)
  334. ");
  335. // Duplicate the post IDs for slug and title matching
  336. $allParams = array_merge($postIds, $postIds);
  337. $pubStmt->execute($allParams);
  338. foreach ($pubStmt->fetchAll() as $pub) {
  339. // Map both slug and title for easier lookup
  340. $publicationMap[strtolower($pub['slug'])] = $pub['id'];
  341. $publicationMap[strtolower($pub['title'])] = $pub['id'];
  342. }
  343. }
  344. foreach ($comments as $wpComment) {
  345. try {
  346. // Find corresponding publication using preloaded map
  347. $publicationId = null;
  348. $postSlug = !empty($wpComment['post_name']) ? strtolower($wpComment['post_name']) : null;
  349. $postTitle = strtolower($wpComment['post_title']);
  350. if ($postSlug && isset($publicationMap[$postSlug])) {
  351. $publicationId = $publicationMap[$postSlug];
  352. } elseif (isset($publicationMap[$postTitle])) {
  353. $publicationId = $publicationMap[$postTitle];
  354. }
  355. if (!$publicationId) {
  356. $skipped++;
  357. $this->log("Comment skipped - no matching publication found for post ID {$wpComment['comment_post_ID']}");
  358. continue;
  359. }
  360. // Map comment status
  361. $status = ($wpComment['comment_approved'] === '1') ? 'approved' : 'pending';
  362. // Handle parent comment (skip for now to avoid complexity)
  363. $parentId = null;
  364. // Insert comment
  365. $this->targetDb->query(
  366. "INSERT INTO comments (publication_id, parent_id, name, email, content, status, created_at, admin_reply)
  367. VALUES (?, ?, ?, ?, ?, ?, ?, FALSE)",
  368. [
  369. $publicationId,
  370. $parentId,
  371. $wpComment['comment_author'],
  372. $wpComment['comment_author_email'],
  373. $wpComment['comment_content'],
  374. $status,
  375. $wpComment['comment_date']
  376. ]
  377. );
  378. $commentId = $this->targetDb->getConnection()->lastInsertId();
  379. // Store WordPress comment ID for parent mapping
  380. $this->targetDb->query(
  381. "UPDATE comments SET wp_comment_id = ? WHERE id = ?",
  382. [$wpComment['comment_ID'], $commentId]
  383. );
  384. $imported++;
  385. $this->log("Imported comment for post ID {$wpComment['comment_post_ID']}");
  386. } catch (Exception $e) {
  387. $this->errors[] = "Error importing comment: " . $e->getMessage();
  388. $this->log("Error importing comment: " . $e->getMessage(), 'error');
  389. }
  390. }
  391. } catch (Exception $e) {
  392. $this->errors[] = "Error in comments import: " . $e->getMessage();
  393. $this->log("Error in comments import: " . $e->getMessage(), 'error');
  394. }
  395. $this->log("Comments import completed: {$imported} imported, {$skipped} skipped");
  396. return ['imported' => $imported, 'skipped' => $skipped];
  397. }
  398. /**
  399. * Helper methods
  400. */
  401. private function getUserRole($userId) {
  402. $stmt = $this->wpDb->prepare("
  403. SELECT meta_value FROM wp_usermeta
  404. WHERE user_id = ? AND meta_key = 'wp_capabilities'
  405. ");
  406. $stmt->execute([$userId]);
  407. $capabilities = $stmt->fetchColumn();
  408. if ($capabilities && strpos($capabilities, 'administrator') !== false) {
  409. return 'admin';
  410. }
  411. return 'editor'; // Default role
  412. }
  413. private function getAuthorName($authorId) {
  414. $stmt = $this->wpDb->prepare("SELECT display_name FROM wp_users WHERE ID = ?");
  415. $stmt->execute([$authorId]);
  416. $name = $stmt->fetchColumn();
  417. return $name ?: 'Unknown Author';
  418. }
  419. private function getPostCategories($postId) {
  420. $stmt = $this->wpDb->prepare("
  421. SELECT t.name FROM wp_terms t
  422. JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
  423. JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
  424. WHERE tr.object_id = ? AND tt.taxonomy = 'category'
  425. ");
  426. $stmt->execute([$postId]);
  427. return $stmt->fetchAll(PDO::FETCH_COLUMN);
  428. }
  429. private function getPostSlugById($postId) {
  430. $stmt = $this->wpDb->prepare("SELECT post_name FROM wp_posts WHERE ID = ?");
  431. $stmt->execute([$postId]);
  432. return $stmt->fetchColumn() ?: '';
  433. }
  434. private function getPostTitleById($postId) {
  435. $stmt = $this->wpDb->prepare("SELECT post_title FROM wp_posts WHERE ID = ?");
  436. $stmt->execute([$postId]);
  437. return $stmt->fetchColumn() ?: '';
  438. }
  439. private function linkPostCategories($publicationId, $categories) {
  440. foreach ($categories as $categoryName) {
  441. $category = $this->targetDb->fetch(
  442. "SELECT id FROM categories WHERE name = ?",
  443. [$categoryName]
  444. );
  445. if ($category) {
  446. $this->targetDb->query(
  447. "INSERT IGNORE INTO publication_categories (publication_id, category_id) VALUES (?, ?)",
  448. [$publicationId, $category['id']]
  449. );
  450. }
  451. }
  452. }
  453. private function processContent($content) {
  454. // Basic WordPress content processing
  455. // You can extend this to handle shortcodes, etc.
  456. $content = str_replace('[caption]', '', $content);
  457. $content = str_replace('[/caption]', '', $content);
  458. $content = preg_replace('/\[gallery.*?\]/', '', $content);
  459. return $content;
  460. }
  461. private function generateSlug($title) {
  462. $slug = strtolower($title);
  463. $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
  464. $slug = trim($slug, '-');
  465. return $slug;
  466. }
  467. private function log($message, $level = 'info') {
  468. $this->importLog[] = [
  469. 'timestamp' => date('Y-m-d H:i:s'),
  470. 'level' => $level,
  471. 'message' => $message
  472. ];
  473. }
  474. /**
  475. * Get statistics methods
  476. */
  477. public function getPostCount() {
  478. $stmt = $this->wpDb->query("SELECT COUNT(*) FROM wp_posts WHERE post_type = 'post'");
  479. return $stmt->fetchColumn();
  480. }
  481. public function getPageCount() {
  482. $stmt = $this->wpDb->query("SELECT COUNT(*) FROM wp_posts WHERE post_type = 'page'");
  483. return $stmt->fetchColumn();
  484. }
  485. public function getCategoryCount() {
  486. $stmt = $this->wpDb->query("
  487. SELECT COUNT(*) FROM wp_term_taxonomy
  488. WHERE taxonomy = 'category'
  489. ");
  490. return $stmt->fetchColumn();
  491. }
  492. public function getUserCount() {
  493. $stmt = $this->wpDb->query("SELECT COUNT(*) FROM wp_users");
  494. return $stmt->fetchColumn();
  495. }
  496. public function getCommentCount() {
  497. $stmt = $this->wpDb->query("SELECT COUNT(*) FROM wp_comments");
  498. return $stmt->fetchColumn();
  499. }
  500. /**
  501. * Get import log
  502. */
  503. public function getLog() {
  504. return $this->importLog;
  505. }
  506. /**
  507. * Get errors
  508. */
  509. public function getErrors() {
  510. return $this->errors;
  511. }
  512. }