PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/woocommerce/includes/export/abstract-wc-csv-exporter.php

https://bitbucket.org/babinkochana/triptrills
PHP | 487 lines | 262 code | 44 blank | 181 comment | 13 complexity | 6d3db8336894a022b2b231c0bb2d7cb9 MD5 | raw file
Possible License(s): MIT, Apache-2.0, GPL-3.0, 0BSD, GPL-2.0
  1. <?php
  2. /**
  3. * Handles CSV export.
  4. *
  5. * @package WooCommerce/Export
  6. * @version 3.1.0
  7. */
  8. if ( ! defined( 'ABSPATH' ) ) {
  9. exit;
  10. }
  11. /**
  12. * WC_CSV_Exporter Class.
  13. */
  14. abstract class WC_CSV_Exporter {
  15. /**
  16. * Type of export used in filter names.
  17. *
  18. * @var string
  19. */
  20. protected $export_type = '';
  21. /**
  22. * Filename to export to.
  23. *
  24. * @var string
  25. */
  26. protected $filename = 'wc-export.csv';
  27. /**
  28. * Batch limit.
  29. *
  30. * @var integer
  31. */
  32. protected $limit = 50;
  33. /**
  34. * Number exported.
  35. *
  36. * @var integer
  37. */
  38. protected $exported_row_count = 0;
  39. /**
  40. * Raw data to export.
  41. *
  42. * @var array
  43. */
  44. protected $row_data = array();
  45. /**
  46. * Total rows to export.
  47. *
  48. * @var integer
  49. */
  50. protected $total_rows = 0;
  51. /**
  52. * Columns ids and names.
  53. *
  54. * @var array
  55. */
  56. protected $column_names = array();
  57. /**
  58. * List of columns to export, or empty for all.
  59. *
  60. * @var array
  61. */
  62. protected $columns_to_export = array();
  63. /**
  64. * Prepare data that will be exported.
  65. */
  66. abstract public function prepare_data_to_export();
  67. /**
  68. * Return an array of supported column names and ids.
  69. *
  70. * @since 3.1.0
  71. * @return array
  72. */
  73. public function get_column_names() {
  74. return apply_filters( "woocommerce_{$this->export_type}_export_column_names", $this->column_names, $this );
  75. }
  76. /**
  77. * Set column names.
  78. *
  79. * @since 3.1.0
  80. * @param array $column_names Column names array.
  81. */
  82. public function set_column_names( $column_names ) {
  83. $this->column_names = array();
  84. foreach ( $column_names as $column_id => $column_name ) {
  85. $this->column_names[ wc_clean( $column_id ) ] = wc_clean( $column_name );
  86. }
  87. }
  88. /**
  89. * Return an array of columns to export.
  90. *
  91. * @since 3.1.0
  92. * @return array
  93. */
  94. public function get_columns_to_export() {
  95. return $this->columns_to_export;
  96. }
  97. /**
  98. * Set columns to export.
  99. *
  100. * @since 3.1.0
  101. * @param array $columns Columns array.
  102. */
  103. public function set_columns_to_export( $columns ) {
  104. $this->columns_to_export = array_map( 'wc_clean', $columns );
  105. }
  106. /**
  107. * See if a column is to be exported or not.
  108. *
  109. * @since 3.1.0
  110. * @param string $column_id ID of the column being exported.
  111. * @return boolean
  112. */
  113. public function is_column_exporting( $column_id ) {
  114. $column_id = strstr( $column_id, ':' ) ? current( explode( ':', $column_id ) ) : $column_id;
  115. $columns_to_export = $this->get_columns_to_export();
  116. if ( empty( $columns_to_export ) ) {
  117. return true;
  118. }
  119. if ( in_array( $column_id, $columns_to_export, true ) || 'meta' === $column_id ) {
  120. return true;
  121. }
  122. return false;
  123. }
  124. /**
  125. * Return default columns.
  126. *
  127. * @since 3.1.0
  128. * @return array
  129. */
  130. public function get_default_column_names() {
  131. return array();
  132. }
  133. /**
  134. * Do the export.
  135. *
  136. * @since 3.1.0
  137. */
  138. public function export() {
  139. $this->prepare_data_to_export();
  140. $this->send_headers();
  141. $this->send_content( chr( 239 ) . chr( 187 ) . chr( 191 ) . $this->export_column_headers() . $this->get_csv_data() );
  142. die();
  143. }
  144. /**
  145. * Set the export headers.
  146. *
  147. * @since 3.1.0
  148. */
  149. public function send_headers() {
  150. if ( function_exists( 'gc_enable' ) ) {
  151. gc_enable(); // phpcs:ignore PHPCompatibility.PHP.NewFunctions.gc_enableFound
  152. }
  153. if ( function_exists( 'apache_setenv' ) ) {
  154. @apache_setenv( 'no-gzip', 1 ); // @codingStandardsIgnoreLine
  155. }
  156. @ini_set( 'zlib.output_compression', 'Off' ); // @codingStandardsIgnoreLine
  157. @ini_set( 'output_buffering', 'Off' ); // @codingStandardsIgnoreLine
  158. @ini_set( 'output_handler', '' ); // @codingStandardsIgnoreLine
  159. ignore_user_abort( true );
  160. wc_set_time_limit( 0 );
  161. wc_nocache_headers();
  162. header( 'Content-Type: text/csv; charset=utf-8' );
  163. header( 'Content-Disposition: attachment; filename=' . $this->get_filename() );
  164. header( 'Pragma: no-cache' );
  165. header( 'Expires: 0' );
  166. }
  167. /**
  168. * Set filename to export to.
  169. *
  170. * @param string $filename Filename to export to.
  171. */
  172. public function set_filename( $filename ) {
  173. $this->filename = sanitize_file_name( str_replace( '.csv', '', $filename ) . '.csv' );
  174. }
  175. /**
  176. * Generate and return a filename.
  177. *
  178. * @return string
  179. */
  180. public function get_filename() {
  181. return sanitize_file_name( apply_filters( "woocommerce_{$this->export_type}_export_get_filename", $this->filename ) );
  182. }
  183. /**
  184. * Set the export content.
  185. *
  186. * @since 3.1.0
  187. * @param string $csv_data All CSV content.
  188. */
  189. public function send_content( $csv_data ) {
  190. echo $csv_data; // @codingStandardsIgnoreLine
  191. }
  192. /**
  193. * Get CSV data for this export.
  194. *
  195. * @since 3.1.0
  196. * @return string
  197. */
  198. protected function get_csv_data() {
  199. return $this->export_rows();
  200. }
  201. /**
  202. * Export column headers in CSV format.
  203. *
  204. * @since 3.1.0
  205. * @return string
  206. */
  207. protected function export_column_headers() {
  208. $columns = $this->get_column_names();
  209. $export_row = array();
  210. $buffer = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
  211. ob_start();
  212. foreach ( $columns as $column_id => $column_name ) {
  213. if ( ! $this->is_column_exporting( $column_id ) ) {
  214. continue;
  215. }
  216. $export_row[] = $this->format_data( $column_name );
  217. }
  218. $this->fputcsv( $buffer, $export_row );
  219. return ob_get_clean();
  220. }
  221. /**
  222. * Get data that will be exported.
  223. *
  224. * @since 3.1.0
  225. * @return array
  226. */
  227. protected function get_data_to_export() {
  228. return $this->row_data;
  229. }
  230. /**
  231. * Export rows in CSV format.
  232. *
  233. * @since 3.1.0
  234. * @return string
  235. */
  236. protected function export_rows() {
  237. $data = $this->get_data_to_export();
  238. $buffer = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
  239. ob_start();
  240. array_walk( $data, array( $this, 'export_row' ), $buffer );
  241. return apply_filters( "woocommerce_{$this->export_type}_export_rows", ob_get_clean(), $this );
  242. }
  243. /**
  244. * Export rows to an array ready for the CSV.
  245. *
  246. * @since 3.1.0
  247. * @param array $row_data Data to export.
  248. * @param string $key Column being exported.
  249. * @param resource $buffer Output buffer.
  250. */
  251. protected function export_row( $row_data, $key, $buffer ) {
  252. $columns = $this->get_column_names();
  253. $export_row = array();
  254. foreach ( $columns as $column_id => $column_name ) {
  255. if ( ! $this->is_column_exporting( $column_id ) ) {
  256. continue;
  257. }
  258. if ( isset( $row_data[ $column_id ] ) ) {
  259. $export_row[] = $this->format_data( $row_data[ $column_id ] );
  260. } else {
  261. $export_row[] = '';
  262. }
  263. }
  264. $this->fputcsv( $buffer, $export_row );
  265. ++ $this->exported_row_count;
  266. }
  267. /**
  268. * Get batch limit.
  269. *
  270. * @since 3.1.0
  271. * @return int
  272. */
  273. public function get_limit() {
  274. return apply_filters( "woocommerce_{$this->export_type}_export_batch_limit", $this->limit, $this );
  275. }
  276. /**
  277. * Set batch limit.
  278. *
  279. * @since 3.1.0
  280. * @param int $limit Limit to export.
  281. */
  282. public function set_limit( $limit ) {
  283. $this->limit = absint( $limit );
  284. }
  285. /**
  286. * Get count of records exported.
  287. *
  288. * @since 3.1.0
  289. * @return int
  290. */
  291. public function get_total_exported() {
  292. return $this->exported_row_count;
  293. }
  294. /**
  295. * Escape a string to be used in a CSV context
  296. *
  297. * Malicious input can inject formulas into CSV files, opening up the possibility
  298. * for phishing attacks and disclosure of sensitive information.
  299. *
  300. * Additionally, Excel exposes the ability to launch arbitrary commands through
  301. * the DDE protocol.
  302. *
  303. * @see http://www.contextis.com/resources/blog/comma-separated-vulnerabilities/
  304. * @see https://hackerone.com/reports/72785
  305. *
  306. * @since 3.1.0
  307. * @param string $data CSV field to escape.
  308. * @return string
  309. */
  310. public function escape_data( $data ) {
  311. $active_content_triggers = array( '=', '+', '-', '@' );
  312. if ( in_array( mb_substr( $data, 0, 1 ), $active_content_triggers, true ) ) {
  313. $data = "'" . $data;
  314. }
  315. return $data;
  316. }
  317. /**
  318. * Format and escape data ready for the CSV file.
  319. *
  320. * @since 3.1.0
  321. * @param string $data Data to format.
  322. * @return string
  323. */
  324. public function format_data( $data ) {
  325. if ( ! is_scalar( $data ) ) {
  326. if ( is_a( $data, 'WC_Datetime' ) ) {
  327. $data = $data->date( 'Y-m-d G:i:s' );
  328. } else {
  329. $data = ''; // Not supported.
  330. }
  331. } elseif ( is_bool( $data ) ) {
  332. $data = $data ? 1 : 0;
  333. }
  334. $use_mb = function_exists( 'mb_convert_encoding' );
  335. if ( $use_mb ) {
  336. $encoding = mb_detect_encoding( $data, 'UTF-8, ISO-8859-1', true );
  337. $data = 'UTF-8' === $encoding ? $data : utf8_encode( $data );
  338. }
  339. return $this->escape_data( $data );
  340. }
  341. /**
  342. * Format term ids to names.
  343. *
  344. * @since 3.1.0
  345. * @param array $term_ids Term IDs to format.
  346. * @param string $taxonomy Taxonomy name.
  347. * @return string
  348. */
  349. public function format_term_ids( $term_ids, $taxonomy ) {
  350. $term_ids = wp_parse_id_list( $term_ids );
  351. if ( ! count( $term_ids ) ) {
  352. return '';
  353. }
  354. $formatted_terms = array();
  355. if ( is_taxonomy_hierarchical( $taxonomy ) ) {
  356. foreach ( $term_ids as $term_id ) {
  357. $formatted_term = array();
  358. $ancestor_ids = array_reverse( get_ancestors( $term_id, $taxonomy ) );
  359. foreach ( $ancestor_ids as $ancestor_id ) {
  360. $term = get_term( $ancestor_id, $taxonomy );
  361. if ( $term && ! is_wp_error( $term ) ) {
  362. $formatted_term[] = $term->name;
  363. }
  364. }
  365. $term = get_term( $term_id, $taxonomy );
  366. if ( $term && ! is_wp_error( $term ) ) {
  367. $formatted_term[] = $term->name;
  368. }
  369. $formatted_terms[] = implode( ' > ', $formatted_term );
  370. }
  371. } else {
  372. foreach ( $term_ids as $term_id ) {
  373. $term = get_term( $term_id, $taxonomy );
  374. if ( $term && ! is_wp_error( $term ) ) {
  375. $formatted_terms[] = $term->name;
  376. }
  377. }
  378. }
  379. return $this->implode_values( $formatted_terms );
  380. }
  381. /**
  382. * Implode CSV cell values using commas by default, and wrapping values
  383. * which contain the separator.
  384. *
  385. * @since 3.2.0
  386. * @param array $values Values to implode.
  387. * @return string
  388. */
  389. protected function implode_values( $values ) {
  390. $values_to_implode = array();
  391. foreach ( $values as $value ) {
  392. $value = (string) is_scalar( $value ) ? $value : '';
  393. $values_to_implode[] = str_replace( ',', '\\,', $value );
  394. }
  395. return implode( ', ', $values_to_implode );
  396. }
  397. /**
  398. * Write to the CSV file, ensuring escaping works across versions of
  399. * PHP.
  400. *
  401. * PHP 5.5.4 uses '\' as the default escape character. This is not RFC-4180 compliant.
  402. * \0 disables the escape character.
  403. *
  404. * @see https://bugs.php.net/bug.php?id=43225
  405. * @see https://bugs.php.net/bug.php?id=50686
  406. * @see https://github.com/woocommerce/woocommerce/issues/19514
  407. * @since 3.4.0
  408. * @param resource $buffer Resource we are writing to.
  409. * @param array $export_row Row to export.
  410. */
  411. protected function fputcsv( $buffer, $export_row ) {
  412. if ( version_compare( PHP_VERSION, '5.5.4', '<' ) ) {
  413. ob_start();
  414. $temp = fopen( 'php://output', 'w' ); // @codingStandardsIgnoreLine
  415. fputcsv( $temp, $export_row, ",", '"' ); // @codingStandardsIgnoreLine
  416. fclose( $temp ); // @codingStandardsIgnoreLine
  417. $row = ob_get_clean();
  418. $row = str_replace( '\\"', '\\""', $row );
  419. fwrite( $buffer, $row ); // @codingStandardsIgnoreLine
  420. } else {
  421. fputcsv( $buffer, $export_row, ",", '"', "\0" ); // @codingStandardsIgnoreLine
  422. }
  423. }
  424. }