wordpress_import.php 19 KB

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