views_data_export.drush.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. <?php
  2. /**
  3. * Implementation of hook_drush_command().
  4. */
  5. function views_data_export_drush_command() {
  6. $items = array();
  7. $items['views-data-export'] = array (
  8. 'aliases' => array (
  9. 'vde',
  10. ),
  11. 'description' => 'Fully executes a views_data_export display of a view and writes the output to file.',
  12. 'arguments' => array (
  13. 'view_name' => 'The name of the view',
  14. 'display_id' => 'The id of the views_data_export display to execute on the view',
  15. 'output_file' => 'The file to write the results to - will be overwritten if it already exists',
  16. ),
  17. 'options' => array (
  18. 'arguments' => 'Comma separated list of arguments to be passed to the view.',
  19. 'format' => 'csv,doc,txt,xls or xml. These options are ignored if the display_id passed is a "views_data_export" display.',
  20. 'separator' => 'csv only: What character separates the fields (default:,)',
  21. 'trim-whitespace' => 'csv only: Trim whitespace from either side of fields (default:1)',
  22. 'header-row' => 'csv only: Make the first row a row of headers (default:1)',
  23. 'quote-values' => 'csv only: Surround each field in quotes (default:1)',
  24. ),
  25. 'examples' => array (
  26. 'drush views-data-export myviewname views_data_export_1 output.csv' => 'Export myviewname:views_data_export_1 and write the output to output.csv in the current directory',
  27. ),
  28. 'drupal dependencies' => array (
  29. 'views_data_export',
  30. ),
  31. 'core' => array('7'),
  32. );
  33. return $items;
  34. }
  35. /**
  36. * Implementation of hook_drush_help().
  37. *
  38. * This function is called whenever a drush user calls
  39. * 'drush help <name-of-your-command>'
  40. *
  41. * @param
  42. * A string with the help section (prepend with 'drush:')
  43. *
  44. * @return
  45. * A string with the help text for your command.
  46. */
  47. function views_data_export_drush_help($section) {
  48. switch ($section) {
  49. case 'drush:views-data-export':
  50. return dt("This command may be used to fully execute a views_data_export display of a view, batched if need be, and write the output to a file.");
  51. }
  52. }
  53. /**
  54. * Implementation of drush_hook_COMMAND_validate().
  55. */
  56. function drush_views_data_export_validate() {
  57. // Because of a bug in the way that Drush 4 computes the name of functions to
  58. // call from a Drush command, we may end up getting called twice, so we just
  59. // don't do anything on subsequent invocations.
  60. static $already_run = FALSE;
  61. if ($already_run) {
  62. return;
  63. }
  64. $already_run = TRUE;
  65. $args = drush_get_arguments();
  66. array_shift($args);
  67. if (count($args) !== 3) {
  68. return drush_set_error('ARGUMENTS_REQUIRED', dt('All arguments are required.'));
  69. }
  70. if (!$view = views_get_view($args[0])) {
  71. return drush_set_error('VIEW_DOES_NOT_EXIST', dt('The view !view does not exist.', array ('!view' => $args[0])));
  72. }
  73. if (!$view->set_display($args[1])) {
  74. return drush_set_error('VIEW_DOES_NOT_EXIST', dt('The view !view does not have the !display display.', array ('!view' => $args[0], '!display' => $args[1])));
  75. }
  76. else {
  77. if ($view->current_display != $args[1]) {
  78. drush_log(dt('Using different display from specified display: @display', array('@display' => $view->current_display)), 'notice');
  79. }
  80. drush_set_option('views_data_export_display_id', $view->current_display);
  81. }
  82. $format = drush_get_option('format');
  83. $valid_formats = array('csv', 'doc', 'txt', 'xls', 'xml');
  84. if (!empty($format) && !in_array($format, $valid_formats)) {
  85. return drush_set_error('VIEWS_DATA_EXPORT_INVALID_OPTION', dt('The "--format" option is invalid, please supply one of the following: !formats', array('!formats' => implode(', ', $valid_formats))));
  86. }
  87. }
  88. /**
  89. * Drush command callback to export a views data to a file.
  90. *
  91. * @see drush_views_data_export_validate().
  92. * @see views_data_export_views_data_export_batch_alter().
  93. */
  94. function drush_views_data_export($view_name, $display_id, $output_file) {
  95. // Because of a bug in the way that Drush 4 computes the name of functions to
  96. // call from a Drush command, we may end up getting called twice, so we just
  97. // don't do anything on subsequent invocations.
  98. static $already_run = FALSE;
  99. if ($already_run) {
  100. return;
  101. }
  102. $already_run = TRUE;
  103. // Set the display to the one that we computed earlier.
  104. $display_id = drush_get_option('views_data_export_display_id', 'default');
  105. $view = views_get_view($view_name);
  106. // If the given display_id is not views_data_alter then
  107. // we programatically clone it to a views_data_alter display
  108. // and then execute that one instead
  109. if ($view->display[$display_id]->display_plugin != 'views_data_export') {
  110. //drush_log("Display '$display_id' is not views_data_export. Making one that is and executing that instead =).", 'success');
  111. $format = drush_get_option('format');
  112. $settings = array();
  113. switch ($format) {
  114. case 'doc':
  115. case 'xls':
  116. case 'xml':
  117. case 'txt':
  118. $settings['display_options']['style_plugin'] = 'views_data_export_' . $format;
  119. break;
  120. case 'csv':
  121. default:
  122. $settings['display_options']['style_plugin'] = 'views_data_export_csv';
  123. if ($separator = drush_get_option('separator')) {
  124. $settings['display_options']['style_options']['separator'] = $separator;
  125. }
  126. if (!$trim = drush_get_option('trim-whitespace')) {
  127. $settings['display_options']['style_options']['trim'] = 0;
  128. }
  129. if (!$header = drush_get_option('header-row')) {
  130. $settings['display_options']['style_options']['header'] = 0;
  131. }
  132. if (!$quote = drush_get_option('quote-values')) {
  133. $settings['display_options']['style_options']['quote'] = 0;
  134. }
  135. // Seperator
  136. }
  137. $display_id = _drush_views_data_export_clone_display($view, $display_id, $settings);
  138. }
  139. $view->set_display($display_id);
  140. // We execute the view normally, and take advantage
  141. // of an alter function to interject later and batch it ourselves
  142. $options = array();
  143. // Let's see if the given $output_file is a absolute path.
  144. if (strpos($output_file, '/') === 0) {
  145. $options['output_file'] = $output_file;
  146. }
  147. else {
  148. $options['output_file'] = realpath(drush_get_context('DRUSH_OLDCWD', getcwd())) . '/' . $output_file;
  149. }
  150. $arguments = drush_get_option('arguments', '');
  151. if(!empty($arguments) && is_array($arguments)) {
  152. $arguments = explode(',', $arguments);
  153. }
  154. if ($view->display_handler->is_batched()) {
  155. // This is a batched export, and needs to be handled as such.
  156. _drush_views_data_export_override_batch($view_name, $display_id, $options);
  157. $view->execute_display($display_id, $arguments);
  158. }
  159. else {
  160. // This export isn't batched.
  161. ob_start();
  162. $view->execute_display($display_id, $arguments);
  163. // Get the results, and clean the output buffer.
  164. $result = ob_get_contents();
  165. // Clean the buffer.
  166. ob_end_clean();
  167. // Save the results to file.
  168. // Copy file over
  169. if (file_put_contents($options['output_file'], $result)) {
  170. drush_log("Data export saved to " . $options['output_file'], 'success');
  171. }
  172. else {
  173. drush_set_error('VIEWS_DATA_EXPORT_COPY_ERROR', dt("The file could not be copied to the selected destination"));
  174. }
  175. }
  176. }
  177. /**
  178. * Helper function that indicates that we want to
  179. * override the batch that the views_data_export view creates
  180. * on it's initial time through.
  181. *
  182. * Also provides a place to stash options that need to stay around
  183. * until the end of the batch
  184. */
  185. function _drush_views_data_export_override_batch($view = NULL, $display = NULL, $options = TRUE) {
  186. static $_views;
  187. if (isset($view)) {
  188. $_views[$view][$display] = $options;
  189. }
  190. return $_views;
  191. }
  192. /**
  193. * Implementation of hook_views_data_export_batch_alter()
  194. */
  195. function views_data_export_views_data_export_batch_alter(&$batch, &$final_destination, &$querystring) {
  196. // Copy the batch, because we're going to monkey with it, a lot!
  197. $new_batch = $batch;
  198. $view_name = $new_batch['view_name'];
  199. $display_id = $new_batch['display_id'];
  200. $ok_to_override = _drush_views_data_export_override_batch();
  201. // Make sure we do nothing if we are called not following the execution of
  202. // our drush command. This could happen if the file with this function in it
  203. // is included during the normal execution of the view
  204. if (!$ok_to_override[$view_name][$display_id]) {
  205. return;
  206. }
  207. $options = $ok_to_override[$view_name][$display_id];
  208. // We actually never return from the drupal_alter, but
  209. // use drush's batch system to run the same batch
  210. // Add a final callback
  211. $new_batch['operations'][] = array(
  212. '_drush_views_data_export_batch_finished', array($batch['eid'], $options['output_file']),
  213. );
  214. batch_set($new_batch);
  215. $new_batch =& batch_get();
  216. // Drush handles the different processes, so instruct BatchAPI not to.
  217. $new_batch['progressive'] = FALSE;
  218. // Process the batch using drush.
  219. drush_backend_batch_process();
  220. // Instruct the view display plugin that it shouldn't set a batch.
  221. $batch = array();
  222. }
  223. /**
  224. * Get's called at the end of the drush batch process that generated our export
  225. */
  226. function _drush_views_data_export_batch_finished($eid, $output_file, &$context) {
  227. // Fetch export info
  228. $export = views_data_export_get($eid);
  229. // Perform cleanup
  230. $view = views_data_export_view_retrieve($eid);
  231. $view->set_display($export->view_display_id);
  232. $view->display_handler->batched_execution_state = $export;
  233. $view->display_handler->remove_index();
  234. // Get path to temp file
  235. $temp_file = $view->display_handler->outputfile_path();
  236. // Copy file over
  237. if (@drush_op('copy', $temp_file, $output_file)) {
  238. drush_log("Data export saved to " . $output_file, 'success');
  239. }
  240. else {
  241. drush_set_error('VIEWS_DATA_EXPORT_COPY_ERROR', dt("The file could not be copied to the selected destination"));
  242. }
  243. }
  244. /**
  245. * Helper function that takes a view and returns a clone of it
  246. * that has cloned a given display to one of type views_data_export
  247. *
  248. * @param &$view
  249. * Modified to contain the new display
  250. *
  251. * @return
  252. * The new display_id
  253. */
  254. function _drush_views_data_export_clone_display(&$view, $display_id, $settings = array()) {
  255. // Create the new display
  256. $new_display_id = _drush_views_data_export_generate_display_id($view, 'views_data_export');
  257. $view->display[$new_display_id] = clone $view->display[$display_id];
  258. // Ensure we have settings we'll need for our display
  259. $default_settings = array (
  260. 'id' => $new_display_id,
  261. 'display_plugin' => 'views_data_export',
  262. 'position' => 99,
  263. 'display_options' => array (
  264. 'style_plugin' => 'views_data_export_csv',
  265. 'style_options' => array(
  266. 'attach_text' => 'CSV',
  267. 'provide_file' => 1,
  268. 'filename' => 'view-%view.csv',
  269. 'parent_sort' => 1,
  270. 'separator' => ',',
  271. 'quote' => 1,
  272. 'trim' => 1,
  273. 'header' => 1,
  274. ),
  275. 'use_batch' => 'batch',
  276. 'path' => '',
  277. 'displays' => array (
  278. 'default' => 'default',
  279. ),
  280. ),
  281. );
  282. $settings = array_replace_recursive($default_settings, $settings);
  283. $view->display[$new_display_id] = (object)array_replace_recursive((array)$view->display[$new_display_id], $settings);
  284. return $new_display_id;
  285. }
  286. /**
  287. * Generate a display id of a certain plugin type.
  288. * See http://drupal.org/files/issues/348975-clone-display.patch
  289. *
  290. * @param $type
  291. * Which plugin should be used for the new display id.
  292. */
  293. function _drush_views_data_export_generate_display_id($view, $type) {
  294. // 'default' is singular and is unique, so just go with 'default'
  295. // for it. For all others, start counting.
  296. if ($type == 'default') {
  297. return 'default';
  298. }
  299. // Initial id.
  300. $id = $type . '_1';
  301. $count = 1;
  302. // Loop through IDs based upon our style plugin name until
  303. // we find one that is unused.
  304. while (!empty($view->display[$id])) {
  305. $id = $type . '_' . ++$count;
  306. }
  307. return $id;
  308. }
  309. /**
  310. * If we're using PHP < 5.3.0 then we'll need
  311. * to define this function ourselves.
  312. * See: http://phpmyanmar.com/phpcodes/manual/function.array-replace-recursive.php
  313. */
  314. if (!function_exists('array_replace_recursive')) {
  315. function array_replace_recursive($array, $array1) {
  316. // Get array arguments
  317. $arrays = func_get_args();
  318. // Define the original array
  319. $original = array_shift($arrays);
  320. // Loop through arrays
  321. foreach ($arrays as $array) {
  322. // Loop through array key/value pairs
  323. foreach ($array as $key => $value) {
  324. // Value is an array
  325. if (is_array($value)) {
  326. // Traverse the array; replace or add result to original array
  327. $original[$key] = array_replace_recursive($original[$key], $array[$key]);
  328. }
  329. // Value is not an array
  330. else {
  331. // Replace or add current value to original array
  332. $original[$key] = $value;
  333. }
  334. }
  335. }
  336. // Return the joined array
  337. return $original;
  338. }
  339. }