PageRenderTime 59ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/csv-importer/csv_importer.php

https://bitbucket.org/geetharani/gordon
PHP | 613 lines | 401 code | 67 blank | 145 comment | 84 complexity | 12deb8d6f0053cb7cfbb1a162bea6083 MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /*
  3. Plugin Name: CSV Importer
  4. Description: Import data as posts from a CSV file. <em>You can reach the author at <a href="mailto:d.v.kobozev@gmail.com">d.v.kobozev@gmail.com</a></em>.
  5. Version: 0.3.7
  6. Author: Denis Kobozev
  7. */
  8. /**
  9. * LICENSE: The MIT License {{{
  10. *
  11. * Copyright (c) <2009> <Denis Kobozev>
  12. *
  13. * Permission is hereby granted, free of charge, to any person obtaining a copy
  14. * of this software and associated documentation files (the "Software"), to deal
  15. * in the Software without restriction, including without limitation the rights
  16. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  17. * copies of the Software, and to permit persons to whom the Software is
  18. * furnished to do so, subject to the following conditions:
  19. *
  20. * The above copyright notice and this permission notice shall be included in
  21. * all copies or substantial portions of the Software.
  22. *
  23. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  25. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  26. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  28. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  29. * THE SOFTWARE.
  30. *
  31. * @author Denis Kobozev <d.v.kobozev@gmail.com>
  32. * @copyright 2009 Denis Kobozev
  33. * @license The MIT License
  34. * }}}
  35. */
  36. class CSVImporterPlugin {
  37. var $defaults = array(
  38. 'addr' => null,//csv_post_title
  39. 'ad_text' => null,//csv_post_content
  40. 'csv_post_type' => null,//csv_post_type
  41. 'csv_post_excerpt' => null,//csv_post_excerpt
  42. 'csv_post_date' => null,//csv_post_date
  43. 'csv_post_tags' => null,//csv_post_tags
  44. 'csv_post_categories' => null,//csv_post_categories
  45. 'csv_post_author' => null,//csv_post_author
  46. 'csv_post_slug' => null,//csv_post_slug
  47. 'csv_post_parent' => 0,//csv_post_parent
  48. );
  49. var $log = array();
  50. /**
  51. * Determine value of option $name from database, $default value or $params,
  52. * save it to the db if needed and return it.
  53. *
  54. * @param string $name
  55. * @param mixed $default
  56. * @param array $params
  57. * @return string
  58. */
  59. function process_option($name, $default, $params) {
  60. if (array_key_exists($name, $params)) {
  61. $value = stripslashes($params[$name]);
  62. } elseif (array_key_exists('_'.$name, $params)) {
  63. // unchecked checkbox value
  64. $value = stripslashes($params['_'.$name]);
  65. } else {
  66. $value = null;
  67. }
  68. $stored_value = get_option($name);
  69. if ($value == null) {
  70. if ($stored_value === false) {
  71. if (is_callable($default) &&
  72. method_exists($default[0], $default[1])) {
  73. $value = call_user_func($default);
  74. } else {
  75. $value = $default;
  76. }
  77. add_option($name, $value);
  78. } else {
  79. $value = $stored_value;
  80. }
  81. } else {
  82. if ($stored_value === false) {
  83. add_option($name, $value);
  84. } elseif ($stored_value != $value) {
  85. update_option($name, $value);
  86. }
  87. }
  88. return $value;
  89. }
  90. /**
  91. * Plugin's interface
  92. *
  93. * @return void
  94. */
  95. function form() {
  96. $opt_draft = $this->process_option('csv_importer_import_as_draft',
  97. 'publish', $_POST);
  98. $opt_cat = $this->process_option('csv_importer_cat', 0, $_POST);
  99. if ('POST' == $_SERVER['REQUEST_METHOD']) {
  100. $this->post(compact('opt_draft', 'opt_cat'));
  101. }
  102. // form HTML {{{
  103. ?>
  104. <div class="wrap">
  105. <h2>Import CSV</h2>
  106. <form class="add:the-list: validate" method="post" enctype="multipart/form-data">
  107. <!-- Import as draft -->
  108. <p>
  109. <input name="_csv_importer_import_as_draft" type="hidden" value="publish" />
  110. <label><input name="csv_importer_import_as_draft" type="checkbox" <?php if ('draft' == $opt_draft) { echo 'checked="checked"'; } ?> value="draft" /> Import posts as drafts</label>
  111. </p>
  112. <!-- Parent category -->
  113. <p>Organize into category <?php wp_dropdown_categories(array('show_option_all' => 'Select one ...', 'hide_empty' => 0, 'hierarchical' => 1, 'show_count' => 0, 'name' => 'csv_importer_cat', 'orderby' => 'name', 'selected' => $opt_cat));?><br/>
  114. <small>This will create new categories inside the category parent you choose.</small></p>
  115. <!-- File input -->
  116. <p><label for="csv_import">Upload file:</label><br/>
  117. <input name="csv_import" id="csv_import" type="file" value="" aria-required="true" /></p>
  118. <p class="submit"><input type="submit" class="button" name="submit" value="Import" /></p>
  119. </form>
  120. </div><!-- end wrap -->
  121. <?php
  122. // end form HTML }}}
  123. }
  124. function print_messages() {
  125. if (!empty($this->log)) {
  126. // messages HTML {{{
  127. ?>
  128. <div class="wrap">
  129. <?php if (!empty($this->log['error'])): ?>
  130. <div class="error">
  131. <?php foreach ($this->log['error'] as $error): ?>
  132. <p><?php echo $error; ?></p>
  133. <?php endforeach; ?>
  134. </div>
  135. <?php endif; ?>
  136. <?php if (!empty($this->log['notice'])): ?>
  137. <div class="updated fade">
  138. <?php foreach ($this->log['notice'] as $notice): ?>
  139. <p><?php echo $notice; ?></p>
  140. <?php endforeach; ?>
  141. </div>
  142. <?php endif; ?>
  143. </div><!-- end wrap -->
  144. <?php
  145. // end messages HTML }}}
  146. $this->log = array();
  147. }
  148. }
  149. /**
  150. * Handle POST submission
  151. *
  152. * @param array $options
  153. * @return void
  154. */
  155. function post($options) {
  156. if (empty($_FILES['csv_import']['tmp_name'])) {
  157. $this->log['error'][] = 'No file uploaded, aborting.';
  158. $this->print_messages();
  159. return;
  160. }
  161. require_once 'File_CSV_DataSource/DataSource.php';
  162. $time_start = microtime(true);
  163. $csv = new File_CSV_DataSource;
  164. $file = $_FILES['csv_import']['tmp_name'];
  165. $this->stripBOM($file);
  166. if (!$csv->load($file)) {
  167. $this->log['error'][] = 'Failed to load file, aborting.';
  168. $this->print_messages();
  169. return;
  170. }
  171. // pad shorter rows with empty values
  172. $csv->symmetrize();
  173. // WordPress sets the correct timezone for date functions somewhere
  174. // in the bowels of wp_insert_post(). We need strtotime() to return
  175. // correct time before the call to wp_insert_post().
  176. $tz = get_option('timezone_string');
  177. if ($tz && function_exists('date_default_timezone_set')) {
  178. date_default_timezone_set($tz);
  179. }
  180. $skipped = 0;
  181. $imported = 0;
  182. $comments = 0;
  183. foreach ($csv->connect() as $csv_data) {
  184. if ($post_id = $this->create_post($csv_data, $options)) {
  185. $imported++;
  186. $comments += $this->add_comments($post_id, $csv_data);
  187. $this->create_custom_fields($post_id, $csv_data);
  188. } else {
  189. $skipped++;
  190. }
  191. }
  192. if (file_exists($file)) {
  193. @unlink($file);
  194. }
  195. $exec_time = microtime(true) - $time_start;
  196. if ($skipped) {
  197. $this->log['notice'][] = "<b>Skipped {$skipped} posts (most likely due to empty title, body and excerpt).</b>";
  198. }
  199. $this->log['notice'][] = sprintf("<b>Imported {$imported} posts and {$comments} comments in %.2f seconds.</b>", $exec_time);
  200. $this->print_messages();
  201. }
  202. function create_post($data, $options) {
  203. extract($options);
  204. $data = array_merge($this->defaults, $data);
  205. $type = $data['csv_post_type'] ? $data['csv_post_type'] : 'post';
  206. $valid_type = (function_exists('post_type_exists') &&
  207. post_type_exists($type)) || in_array($type, array('post', 'page'));
  208. if (!$valid_type) {
  209. $this->log['error']["type-{$type}"] = sprintf(
  210. 'Unknown post type "%s".', $type);
  211. }
  212. $new_post = array(
  213. 'post_title' => convert_chars($data['csv_post_title']),
  214. 'post_content' => wpautop(convert_chars($data['csv_post_post'])),
  215. 'post_status' => $opt_draft,
  216. 'post_type' => $type,
  217. 'post_date' => $this->parse_date($data['csv_post_date']),
  218. 'post_excerpt' => convert_chars($data['csv_post_excerpt']),
  219. 'post_name' => $data['csv_post_slug'],
  220. 'post_author' => $this->get_auth_id($data['csv_post_author']),
  221. 'tax_input' => $this->get_taxonomies($data),
  222. 'post_parent' => $data['csv_post_parent'],
  223. );
  224. // pages don't have tags or categories
  225. if ('page' !== $type) {
  226. $new_post['tags_input'] = $data['csv_post_tags'];
  227. // Setup categories before inserting - this should make insertion
  228. // faster, but I don't exactly remember why :) Most likely because
  229. // we don't assign default cat to post when csv_post_categories
  230. // is not empty.
  231. $cats = $this->create_or_get_categories($data, $opt_cat);
  232. $new_post['post_category'] = $cats['post'];
  233. }
  234. // create!
  235. $id = wp_insert_post($new_post);
  236. if ('page' !== $type && !$id) {
  237. // cleanup new categories on failure
  238. foreach ($cats['cleanup'] as $c) {
  239. wp_delete_term($c, 'category');
  240. }
  241. }
  242. return $id;
  243. }
  244. /**
  245. * Return an array of category ids for a post.
  246. *
  247. * @param string $data csv_post_categories cell contents
  248. * @param integer $common_parent_id common parent id for all categories
  249. * @return array category ids
  250. */
  251. function create_or_get_categories($data, $common_parent_id) {
  252. $ids = array(
  253. 'post' => array(),
  254. 'cleanup' => array(),
  255. );
  256. $items = array_map('trim', explode(',', $data['csv_post_categories']));
  257. foreach ($items as $item) {
  258. if (is_numeric($item)) {
  259. if (get_category($item) !== null) {
  260. $ids['post'][] = $item;
  261. } else {
  262. $this->log['error'][] = "Category ID {$item} does not exist, skipping.";
  263. }
  264. } else {
  265. $parent_id = $common_parent_id;
  266. // item can be a single category name or a string such as
  267. // Parent > Child > Grandchild
  268. $categories = array_map('trim', explode('>', $item));
  269. if (count($categories) > 1 && is_numeric($categories[0])) {
  270. $parent_id = $categories[0];
  271. if (get_category($parent_id) !== null) {
  272. // valid id, everything's ok
  273. $categories = array_slice($categories, 1);
  274. } else {
  275. $this->log['error'][] = "Category ID {$parent_id} does not exist, skipping.";
  276. continue;
  277. }
  278. }
  279. foreach ($categories as $category) {
  280. if ($category) {
  281. $term = $this->term_exists($category, 'category', $parent_id);
  282. if ($term) {
  283. $term_id = $term['term_id'];
  284. } else {
  285. $term_id = wp_insert_category(array(
  286. 'cat_name' => $category,
  287. 'category_parent' => $parent_id,
  288. ));
  289. $ids['cleanup'][] = $term_id;
  290. }
  291. $parent_id = $term_id;
  292. }
  293. }
  294. $ids['post'][] = $term_id;
  295. }
  296. }
  297. return $ids;
  298. }
  299. /**
  300. * Parse taxonomy data from the file
  301. *
  302. * array(
  303. * // hierarchical taxonomy name => ID array
  304. * 'my taxonomy 1' => array(1, 2, 3, ...),
  305. * // non-hierarchical taxonomy name => term names string
  306. * 'my taxonomy 2' => array('term1', 'term2', ...),
  307. * )
  308. *
  309. * @param array $data
  310. * @return array
  311. */
  312. function get_taxonomies($data) {
  313. $taxonomies = array();
  314. foreach ($data as $k => $v) {
  315. if (preg_match('/^csv_ctax_(.*)$/', $k, $matches)) {
  316. $t_name = $matches[1];
  317. if ($this->taxonomy_exists($t_name)) {
  318. $taxonomies[$t_name] = $this->create_terms($t_name,
  319. $data[$k]);
  320. } else {
  321. $this->log['error'][] = "Unknown taxonomy $t_name";
  322. }
  323. }
  324. }
  325. return $taxonomies;
  326. }
  327. /**
  328. * Return an array of term IDs for hierarchical taxonomies or the original
  329. * string from CSV for non-hierarchical taxonomies. The original string
  330. * should have the same format as csv_post_tags.
  331. *
  332. * @param string $taxonomy
  333. * @param string $field
  334. * @return mixed
  335. */
  336. function create_terms($taxonomy, $field) {
  337. if (is_taxonomy_hierarchical($taxonomy)) {
  338. $term_ids = array();
  339. foreach ($this->_parse_tax($field) as $row) {
  340. list($parent, $child) = $row;
  341. $parent_ok = true;
  342. if ($parent) {
  343. $parent_info = $this->term_exists($parent, $taxonomy);
  344. if (!$parent_info) {
  345. // create parent
  346. $parent_info = wp_insert_term($parent, $taxonomy);
  347. }
  348. if (!is_wp_error($parent_info)) {
  349. $parent_id = $parent_info['term_id'];
  350. } else {
  351. // could not find or create parent
  352. $parent_ok = false;
  353. }
  354. } else {
  355. $parent_id = 0;
  356. }
  357. if ($parent_ok) {
  358. $child_info = $this->term_exists($child, $taxonomy, $parent_id);
  359. if (!$child_info) {
  360. // create child
  361. $child_info = wp_insert_term($child, $taxonomy,
  362. array('parent' => $parent_id));
  363. }
  364. if (!is_wp_error($child_info)) {
  365. $term_ids[] = $child_info['term_id'];
  366. }
  367. }
  368. }
  369. return $term_ids;
  370. } else {
  371. return $field;
  372. }
  373. }
  374. /**
  375. * Compatibility wrapper for WordPress term lookup.
  376. */
  377. function term_exists($term, $taxonomy = '', $parent = 0) {
  378. if (function_exists('term_exists')) { // 3.0 or later
  379. return term_exists($term, $taxonomy, $parent);
  380. } else {
  381. return is_term($term, $taxonomy, $parent);
  382. }
  383. }
  384. /**
  385. * Compatibility wrapper for WordPress taxonomy lookup.
  386. */
  387. function taxonomy_exists($taxonomy) {
  388. if (function_exists('taxonomy_exists')) { // 3.0 or later
  389. return taxonomy_exists($taxonomy);
  390. } else {
  391. return is_taxonomy($taxonomy);
  392. }
  393. }
  394. /**
  395. * Hierarchical taxonomy fields are tiny CSV files in their own right.
  396. *
  397. * @param string $field
  398. * @return array
  399. */
  400. function _parse_tax($field) {
  401. $data = array();
  402. if (function_exists('str_getcsv')) { // PHP 5 >= 5.3.0
  403. $lines = $this->split_lines($field);
  404. foreach ($lines as $line) {
  405. $data[] = str_getcsv($line, ',', '"');
  406. }
  407. } else {
  408. // Use temp files for older PHP versions. Reusing the tmp file for
  409. // the duration of the script might be faster, but not necessarily
  410. // significant.
  411. $handle = tmpfile();
  412. fwrite($handle, $field);
  413. fseek($handle, 0);
  414. while (($r = fgetcsv($handle, 999999, ',', '"')) !== false) {
  415. $data[] = $r;
  416. }
  417. fclose($handle);
  418. }
  419. return $data;
  420. }
  421. /**
  422. * Try to split lines of text correctly regardless of the platform the text
  423. * is coming from.
  424. */
  425. function split_lines($text) {
  426. $lines = preg_split("/(\r\n|\n|\r)/", $text);
  427. return $lines;
  428. }
  429. function add_comments($post_id, $data) {
  430. // First get a list of the comments for this post
  431. $comments = array();
  432. foreach ($data as $k => $v) {
  433. // comments start with cvs_comment_
  434. if ( preg_match('/^csv_comment_([^_]+)_(.*)/', $k, $matches) &&
  435. $v != '') {
  436. $comments[$matches[1]] = 1;
  437. }
  438. }
  439. // Sort this list which specifies the order they are inserted, in case
  440. // that matters somewhere
  441. ksort($comments);
  442. // Now go through each comment and insert it. More fields are possible
  443. // in principle (see docu of wp_insert_comment), but I didn't have data
  444. // for them so I didn't test them, so I didn't include them.
  445. $count = 0;
  446. foreach ($comments as $cid => $v) {
  447. $new_comment = array(
  448. 'comment_post_ID' => $post_id,
  449. 'comment_approved' => 1,
  450. );
  451. if (isset($data["csv_comment_{$cid}_author"])) {
  452. $new_comment['comment_author'] = convert_chars(
  453. $data["csv_comment_{$cid}_author"]);
  454. }
  455. if (isset($data["csv_comment_{$cid}_author_email"])) {
  456. $new_comment['comment_author_email'] = convert_chars(
  457. $data["csv_comment_{$cid}_author_email"]);
  458. }
  459. if (isset($data["csv_comment_{$cid}_url"])) {
  460. $new_comment['comment_author_url'] = convert_chars(
  461. $data["csv_comment_{$cid}_url"]);
  462. }
  463. if (isset($data["csv_comment_{$cid}_content"])) {
  464. $new_comment['comment_content'] = convert_chars(
  465. $data["csv_comment_{$cid}_content"]);
  466. }
  467. if (isset($data["csv_comment_{$cid}_date"])) {
  468. $new_comment['comment_date'] = $this->parse_date(
  469. $data["csv_comment_{$cid}_date"]);
  470. }
  471. $id = wp_insert_comment($new_comment);
  472. if ($id) {
  473. $count++;
  474. } else {
  475. $this->log['error'][] = "Could not add comment $cid";
  476. }
  477. }
  478. return $count;
  479. }
  480. function create_custom_fields($post_id, $data) {
  481. foreach ($data as $k => $v) {
  482. // anything that doesn't start with csv_ is a custom field
  483. if (!preg_match('/^csv_/', $k) && $v != '') {
  484. add_post_meta($post_id, $k, $v);
  485. }
  486. }
  487. }
  488. function get_auth_id($author) {
  489. if (is_numeric($author)) {
  490. return $author;
  491. }
  492. $author_data = get_userdatabylogin($author);
  493. return ($author_data) ? $author_data->ID : 0;
  494. }
  495. /**
  496. * Convert date in CSV file to 1999-12-31 23:52:00 format
  497. *
  498. * @param string $data
  499. * @return string
  500. */
  501. function parse_date($data) {
  502. $timestamp = strtotime($data);
  503. if (false === $timestamp) {
  504. return '';
  505. } else {
  506. return date('Y-m-d H:i:s', $timestamp);
  507. }
  508. }
  509. /**
  510. * Delete BOM from UTF-8 file.
  511. *
  512. * @param string $fname
  513. * @return void
  514. */
  515. function stripBOM($fname) {
  516. $res = fopen($fname, 'rb');
  517. if (false !== $res) {
  518. $bytes = fread($res, 3);
  519. if ($bytes == pack('CCC', 0xef, 0xbb, 0xbf)) {
  520. $this->log['notice'][] = 'Getting rid of byte order mark...';
  521. fclose($res);
  522. $contents = file_get_contents($fname);
  523. if (false === $contents) {
  524. trigger_error('Failed to get file contents.', E_USER_WARNING);
  525. }
  526. $contents = substr($contents, 3);
  527. $success = file_put_contents($fname, $contents);
  528. if (false === $success) {
  529. trigger_error('Failed to put file contents.', E_USER_WARNING);
  530. }
  531. } else {
  532. fclose($res);
  533. }
  534. } else {
  535. $this->log['error'][] = 'Failed to open file, aborting.';
  536. }
  537. }
  538. }
  539. function csv_admin_menu() {
  540. require_once ABSPATH . '/wp-admin/admin.php';
  541. $plugin = new CSVImporterPlugin;
  542. add_management_page('edit.php', 'CSV Importer', 'manage_options', __FILE__,
  543. array($plugin, 'form'));
  544. }
  545. //add_action('admin_menu', 'csv_admin_menu');
  546. ?>