Parent Directory
|
Revision Log
|
Revision Graph
By Daniel Berthereau: Added export of terms and vocabularies.
| 1 | <?php |
| 2 | // $Id: taxonomy_csv.api.inc,v 2.3 2009/10/05 09:43:12 danielkm Exp $ |
| 3 | |
| 4 | /** |
| 5 | * @file |
| 6 | * This API allows to use taxonomy_csv import/export functions from other module. |
| 7 | * |
| 8 | * Functions sets: |
| 9 | * 1a. Prepare and import a vocabulary : taxonomy_csv_vocabulary_import |
| 10 | * 1b. Prepare and export a vocabulary : taxonomy_csv_vocabulary_export |
| 11 | * 2a. Prepare and import a line : taxonomy_csv_line_import |
| 12 | * 2b. Prepare and export a line from a term: taxonomy_csv_line_export |
| 13 | * 3. Prepare and import a term : taxonomy_csv_term_import |
| 14 | * 4. Errors helpers |
| 15 | * 5. Infos and log messages |
| 16 | * |
| 17 | * |
| 18 | * Structure of import api: |
| 19 | * - 1. Batch prepare import of file or text |
| 20 | * - 2. Process import line by line (see below) |
| 21 | * - 3. Evaluate vocabulary and finish process |
| 22 | * |
| 23 | * Process import structure (line by line import from a batch set): |
| 24 | * - 1. Validate line |
| 25 | * 1. Clean input line |
| 26 | * 2. Check line items |
| 27 | * - 2. Prepare to process items matching import type (eventual loop) |
| 28 | * - 3. Process import |
| 29 | * 1. Find previous or existing term (see below) |
| 30 | * 2. Update or create term |
| 31 | * 3. Cache term |
| 32 | * |
| 33 | * Find a term before update or create it: |
| 34 | * - 1. In previous imported |
| 35 | * 1. In parent if structure |
| 36 | * 2. In whole cache in all cases |
| 37 | * 3. In extended cache if related (internal use only) |
| 38 | * - 2. In existing terms of the vocabulary (except ignore all) |
| 39 | * 1. In parent if structure |
| 40 | * 2. In whole vocabulary in all cases |
| 41 | * 3. In all vocabularies if related (internal use only) |
| 42 | * |
| 43 | * |
| 44 | * Structure of export api: |
| 45 | * - 1. Batch prepare of vocabulary |
| 46 | * - 2. Export depending on format |
| 47 | * |
| 48 | * |
| 49 | * To add a new csv scheme, need: |
| 50 | * - a define without space, |
| 51 | * - items in _taxonomy_csv_info_lists, |
| 52 | * - items in taxonomy_csv.js and taxonomy_csv.css, |
| 53 | * - a description in forms (taxonomy_csv.module), |
| 54 | * - an advanced help. |
| 55 | * - a case in _taxonomy_csv_check_items(), |
| 56 | * - a case in taxonomy_csv_import_line_items(), |
| 57 | * - eventually specific options. |
| 58 | * - a case in taxonomy_csv_export_line_items() if possible, |
| 59 | */ |
| 60 | |
| 61 | /** |
| 62 | * Available import/export schemas. |
| 63 | */ |
| 64 | define('TAXONOMY_CSV_FORMAT_ALONE_TERMS', 'alone_terms'); |
| 65 | define('TAXONOMY_CSV_FORMAT_FIELDS_LINKS', 'fields_links'); |
| 66 | define('TAXONOMY_CSV_FORMAT_FLAT', 'flat'); |
| 67 | define('TAXONOMY_CSV_FORMAT_TREE_STRUCTURE', 'tree_structure'); |
| 68 | define('TAXONOMY_CSV_FORMAT_POLYHIERARCHY', 'polyhierarchy'); |
| 69 | define('TAXONOMY_CSV_FORMAT_PARENTS', 'parents'); |
| 70 | define('TAXONOMY_CSV_FORMAT_CHILDREN', 'children'); |
| 71 | define('TAXONOMY_CSV_FORMAT_FIELDS', 'fields'); |
| 72 | define('TAXONOMY_CSV_FORMAT_RELATIONS', 'relations'); |
| 73 | define('TAXONOMY_CSV_FORMAT_DESCRIPTIONS', 'descriptions'); |
| 74 | define('TAXONOMY_CSV_FORMAT_WEIGHTS', 'weights'); |
| 75 | define('TAXONOMY_CSV_FORMAT_SYNONYMS', 'synonyms'); |
| 76 | define('TAXONOMY_CSV_FORMAT_TAXONOMY_MANAGER', 'taxonomy_manager'); |
| 77 | |
| 78 | /** |
| 79 | * Available import options. |
| 80 | */ |
| 81 | define('TAXONOMY_CSV_EXISTING_UPDATE', 'update'); // When no difference between merge and replace. |
| 82 | define('TAXONOMY_CSV_EXISTING_UPDATE_MERGE', 'update_merge'); |
| 83 | define('TAXONOMY_CSV_EXISTING_UPDATE_REPLACE', 'update_replace'); |
| 84 | define('TAXONOMY_CSV_EXISTING_IGNORE', 'ignore'); // When no difference between create and all. Equal to duplicate. |
| 85 | define('TAXONOMY_CSV_EXISTING_IGNORE_CREATE', 'ignore_create'); |
| 86 | define('TAXONOMY_CSV_EXISTING_IGNORE_ALL', 'ignore_all'); // Ignore even existing terms in additional columns. |
| 87 | define('TAXONOMY_CSV_EXISTING_IGNORE_PREVIOUS', 'ignore_previous'); // Internal only. |
| 88 | |
| 89 | /** |
| 90 | * List of watchdog options. |
| 91 | */ |
| 92 | define('TAXONOMY_CSV_WATCHDOG_ERROR', 3); // Stop import process. |
| 93 | define('TAXONOMY_CSV_WATCHDOG_WARNING', 4); // Stop line process and go to next one. |
| 94 | define('TAXONOMY_CSV_WATCHDOG_NOTICE', 5); // Continue current line process. |
| 95 | define('TAXONOMY_CSV_WATCHDOG_INFO', 6); // Successfully processed. |
| 96 | define('TAXONOMY_CSV_WATCHDOG_DEBUG', 7); // Internal use only. |
| 97 | define('TAXONOMY_CSV_WATCHDOG_NONE', 9); // Internal use only. |
| 98 | |
| 99 | /** |
| 100 | * Information about import process. Use too default Drupal constants: |
| 101 | * SAVED_NEW = 1 |
| 102 | * SAVED_UPDATED = 2 |
| 103 | * Possibly use of: |
| 104 | * SAVED_DELETED = 3 |
| 105 | */ |
| 106 | define('TAXONOMY_CSV_ERROR', 0); |
| 107 | define('TAXONOMY_CSV_NEW_UPDATED', 4); |
| 108 | define('TAXONOMY_CSV_UNCHANGED', 5); |
| 109 | |
| 110 | /** |
| 111 | * @defgroup taxonomy_csv_vocabulary Import/export a vocabulary |
| 112 | * @{ |
| 113 | * Functions allowing import of a vocabulary into a new or an existing one and |
| 114 | * export of a vocabulary to a file. |
| 115 | */ |
| 116 | |
| 117 | /** |
| 118 | * Prepare the import of a vocabulary. |
| 119 | * If not used in a form, don't forget to use batch_process(). |
| 120 | * |
| 121 | * @param $options |
| 122 | * Array of options: |
| 123 | * source_choice : 'path', 'url' or 'text' |
| 124 | * path : path to local csv file |
| 125 | * url : url to distant or local csv file |
| 126 | * file : object file if file is already uploaded |
| 127 | * text : csv text to import |
| 128 | * import_format : see _taxonomy_csv_info_lists('list_import_format') |
| 129 | * delimiter : one character csv delimiter (default: ",") |
| 130 | * enclosure : zero or one character csv enclosure (default: none, i.e. '"') |
| 131 | * vocabulary_target: 'autocreate' (default), 'duplicate' or 'existing' |
| 132 | * vocabulary_id : vocabulary id to duplicate or to import into |
| 133 | * existing_items : see _taxonomy_csv_info_lists('list_import_option') |
| 134 | * relations_create_subrelations: boolean. Specific to related terms import |
| 135 | * relations_all_vocabularies : boolean. Id |
| 136 | * internal_cache : boolean. tweak to use (default) or not internal cache |
| 137 | * hierarchy_check: boolean. tweak to check (default) or not hierarchy of vocabulary |
| 138 | * hierarchy_level: if hierarchy_check is false, set hierarchy level (0, 1 or 2) |
| 139 | * line_checks : boolean. tweak to check (default) or not format of lines |
| 140 | * utf8_check : boolean. tweak to check (default) or not utf8 format |
| 141 | * result_stats : boolean. Display (default) or not stats |
| 142 | * result_terms : boolean. Display (default) or not list of imported terms |
| 143 | * result_level : Log level: 'none', 'warnings' (default), 'notices' or 'infos' |
| 144 | * result_type : Display of log: 'by_message' (default) or 'by_line' |
| 145 | * Needed options are source_choice, path or url or text, import_format and existing_items. |
| 146 | * @return |
| 147 | * Array of errors or nothing (batch process to execute). |
| 148 | */ |
| 149 | function taxonomy_csv_vocabulary_import($options) { |
| 150 | // Check and eventually update options. |
| 151 | $result = _taxonomy_csv_vocabulary_import_check_options($options); |
| 152 | if (count($result)) { |
| 153 | return $result; |
| 154 | } |
| 155 | |
| 156 | // Remove useless option, because text is saved. |
| 157 | if ($options['source_choice'] == 'text') { |
| 158 | $options['text'] = ''; |
| 159 | } |
| 160 | |
| 161 | // Calculates number of lines to be imported. |
| 162 | // Automatically detects line endings. |
| 163 | ini_set('auto_detect_line_endings', '1'); |
| 164 | $handle = fopen($options['file']->filepath, 'r'); |
| 165 | $lines_count = 0; |
| 166 | while (fgetcsv($handle, 32768, $options['delimiter'], $options['enclosure'])) { |
| 167 | $lines_count++; |
| 168 | } |
| 169 | fclose($handle); |
| 170 | $options['total_lines'] = $lines_count; |
| 171 | if (!$options['total_lines']) { |
| 172 | return array('source_choice' => t('No term to import. Import finished.')); |
| 173 | } |
| 174 | |
| 175 | // Prepare vocabularies. |
| 176 | // If source is fields_links, check and eventual creation of vocabularies are made each line. |
| 177 | if ($options['import_format'] != TAXONOMY_CSV_FORMAT_FIELDS_LINKS) { |
| 178 | // User choose to autocreate or duplicate a vocabulary. |
| 179 | switch ($options['vocabulary_target']) { |
| 180 | case 'autocreate': |
| 181 | $new_vocabulary = taxonomy_csv_vocabulary_create(($options['source_choice'] != 'text') ? $options['file']->filename : ''); |
| 182 | $options['vocabulary_id'] = $new_vocabulary['vid']; |
| 183 | break; |
| 184 | |
| 185 | case 'duplicate': |
| 186 | $new_vocabulary = taxonomy_csv_vocabulary_duplicate($options['vocabulary_id']); |
| 187 | $options['vocabulary_id'] = $new_vocabulary['vid']; |
| 188 | break; |
| 189 | } |
| 190 | $options['vocabulary'] = taxonomy_vocabulary_load($options['vocabulary_id']); |
| 191 | } |
| 192 | |
| 193 | // Prepare import batch. |
| 194 | $batch = array( |
| 195 | 'title' => ($options['source_choice'] != 'text') ? |
| 196 | t('Importing terms from CSV file "%filename"...', array('%filename' => $options['file']->filename)) : |
| 197 | t('Importing terms from text...'), |
| 198 | 'init_message' => t('Starting uploading of datas...'), |
| 199 | 'progress_message' => '', |
| 200 | 'error_message' => t('An error occurred during the import.'), |
| 201 | 'finished' => '_taxonomy_csv_vocabulary_import_finished', |
| 202 | 'file' => dirname(drupal_get_filename('module', 'taxonomy_csv')) .'/taxonomy_csv.api.inc', |
| 203 | 'progressive' => TRUE, |
| 204 | 'operations' => array( |
| 205 | 0 => array('_taxonomy_csv_vocabulary_import_process', array($options)), |
| 206 | ), |
| 207 | ); |
| 208 | |
| 209 | batch_set($batch); |
| 210 | } |
| 211 | |
| 212 | /** |
| 213 | * Validate options of imported vocabulary or line. |
| 214 | * |
| 215 | * @param $options |
| 216 | * Array of options. |
| 217 | * @return |
| 218 | * Array of messages errors if any. |
| 219 | * By reference options are cleaned and completed. |
| 220 | */ |
| 221 | function _taxonomy_csv_vocabulary_import_check_options(&$options) { |
| 222 | $messages = array(); |
| 223 | |
| 224 | // Set default value for unset options. |
| 225 | foreach (array( |
| 226 | 'delimiter' => ',', |
| 227 | 'enclosure' => '', |
| 228 | 'vocabulary_target' => 'autocreate', |
| 229 | 'internal_cache' => TRUE, |
| 230 | 'hierarchy_check' => TRUE, |
| 231 | 'line_checks' => TRUE, |
| 232 | 'utf8_check' => TRUE, |
| 233 | 'result_stats' => 'result_stats', |
| 234 | 'result_terms' => 'result_terms', |
| 235 | 'result_level' => 'notices', |
| 236 | 'result_type' => 'by_message', |
| 237 | // Default options of specific imports. |
| 238 | 'relations_create_subrelations' => FALSE, |
| 239 | 'relations_all_vocabularies' => FALSE, |
| 240 | ) as $key => $value) { |
| 241 | if (!isset($options[$key])) { |
| 242 | $options[$key] = $value; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | // Check if there is write access and prepare file. |
| 247 | switch ($options['source_choice']) { |
| 248 | case 'path': |
| 249 | case 'url': |
| 250 | // Upload if not already uploaded. |
| 251 | if (!isset($options['file'])) { |
| 252 | // Upload from a path. |
| 253 | if ($options['source_choice'] == 'path') { |
| 254 | $options['file'] = file_save_upload($options['path']); |
| 255 | } |
| 256 | // Upload from an url. |
| 257 | else { |
| 258 | $filename = file_save_data( |
| 259 | file_get_contents($options['url']), |
| 260 | basename($options['url']), |
| 261 | 'FILE_EXISTS_RENAME'); |
| 262 | $options['file'] = new stdClass(); |
| 263 | $options['file']->filename = basename($filename); |
| 264 | $options['file']->filepath = $filename; |
| 265 | $options['file']->filesize = filesize($filename); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | if (!$options['file']) { |
| 270 | $messages['path'] = t("You choose to import a taxonomy by a file, but you don't set its name or its size is greater than the server's limit of !max_size.", array('!max_size' => format_size(parse_size(ini_get('upload_max_filesize'))))); |
| 271 | } |
| 272 | elseif (!$options['file']->filesize) { |
| 273 | $messages['path'] = t('Size of your file is null.'); |
| 274 | } |
| 275 | break; |
| 276 | |
| 277 | case 'text': |
| 278 | if (empty($options['text'])) { |
| 279 | $messages['text'] = t('You choose to import a taxonomy by a text, but the text is empty.'); |
| 280 | } |
| 281 | elseif (!isset($options['file'])) { |
| 282 | // Prepare import by text: save text as a temp file to simplify process and avoid memory congestion. |
| 283 | $filename = file_save_data( |
| 284 | $options['text'], |
| 285 | 'taxo_csv', |
| 286 | 'FILE_EXISTS_RENAME'); |
| 287 | $options['file'] = new stdClass(); |
| 288 | $options['file']->filename = basename($filename); |
| 289 | $options['file']->filepath = $filename; |
| 290 | $options['file']->filesize = filesize($filename); |
| 291 | |
| 292 | if (!$filename) { |
| 293 | $messages['text'] = t('Import by text needs access to temp directory. Import failed.'); |
| 294 | } |
| 295 | } |
| 296 | break; |
| 297 | |
| 298 | default: |
| 299 | $messages['source_choice'] = t('Source choice should be "path", "url" or "text".'); |
| 300 | } |
| 301 | |
| 302 | // Delimiter and enclosure greater than one character are useless with fgetcsv. |
| 303 | if (drupal_strlen($options['delimiter']) != 1) { |
| 304 | $messages['delimiter'] = t('Delimiter should be a one character string.'); |
| 305 | } |
| 306 | if (drupal_strlen($options['enclosure']) == 0) { |
| 307 | // With fgetcsv, $enclosure == '' bugs, so use default quote enclosure if none. |
| 308 | $options['enclosure'] = '"'; |
| 309 | } |
| 310 | elseif (drupal_strlen($options['enclosure']) > 1) { |
| 311 | $messages['enclosure'] = t('Enclosure lenght cannot be greater than one character.'); |
| 312 | } |
| 313 | if ($options['delimiter'] == $options['enclosure']) { |
| 314 | $messages['delimiter'] = t('Delimiter and enclosure cannot be same character.'); |
| 315 | } |
| 316 | |
| 317 | if (!in_array($options['vocabulary_target'], array( |
| 318 | 'autocreate', |
| 319 | 'duplicate', |
| 320 | 'existing', |
| 321 | ))) { |
| 322 | $messages['vocabulary_target'] = t('Destination target should be "autocreate", "duplicate" or "existing".'); |
| 323 | } |
| 324 | |
| 325 | if ($options['vocabulary_target'] == 'duplicate' |
| 326 | || $options['vocabulary_target'] == 'existing') { |
| 327 | $list_vocabularies = taxonomy_get_vocabularies(); |
| 328 | if (!isset($list_vocabularies[$options['vocabulary_id']])) { |
| 329 | $messages['vocabulary_id'] = t("You choose to use an existing vocabulary, but you haven't choose it."); |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | $list_import_format = _taxonomy_csv_info_lists('list_import_format'); |
| 334 | $list_import_option = _taxonomy_csv_info_lists('list_import_option'); |
| 335 | $list_import_format_allowed_import_option = _taxonomy_csv_info_lists('list_import_format_allowed_import_option'); |
| 336 | if (!array_key_exists($options['import_format'], $list_import_format)) { |
| 337 | $messages['import_format'] = t('Source content "!import_format" is not managed.', array( |
| 338 | '!import_format' => $list_import_format[$options['import_format']] |
| 339 | )); |
| 340 | } |
| 341 | elseif (!in_array($options['existing_items'], $list_import_format_allowed_import_option[$options['import_format']])) { |
| 342 | $messages['existing_items'] = t('Import option "%existing_items" cannot be used with source content "!import_format".', array( |
| 343 | '%existing_items' => $list_import_option[$options['existing_items']], |
| 344 | '!import_format' => $list_import_format[$options['import_format']], |
| 345 | )); |
| 346 | } |
| 347 | |
| 348 | if (!$options['existing_items']) { |
| 349 | $messages['existing_items'] = t('Please set what will become existing terms.'); |
| 350 | } |
| 351 | elseif (!array_key_exists($options['existing_items'], $list_import_option)) { |
| 352 | $messages['existing_items'] = t('Import option "!existing_items" is not managed.', array( |
| 353 | '!existing_items' => $list_import_option[$options['existing_items']] |
| 354 | )); |
| 355 | } |
| 356 | |
| 357 | if ($options['hierarchy_check'] |
| 358 | && ($options['hierarchy_level'] < 0 || $options['hierarchy_level'] > 2)) { |
| 359 | $messages['hierarchy_level'] = t('You need to set hierarchy level if hierarchy check of vocabulary is disabled.'); |
| 360 | } |
| 361 | |
| 362 | // Advertise if Pathauto module is activated. Issue http://drupal.org/node/540916. |
| 363 | if (module_exists('pathauto')) { |
| 364 | $messages['pathauto'] = t("<strong>Warning</strong>: Pathauto module is activated.<br /> |
| 365 | This module slows down taxonomy import process and only few terms can be imported. It's advised to disabled it manually in !modules_section. Settings aren't lost when you disable it - and not uninstall it -.<br /> |
| 366 | After import process, you can reactivate Pathauto and eventually bulk generate aliases for newly imported terms.", array( |
| 367 | '!modules_section' => l(t('modules section'), 'admin/build/modules'), |
| 368 | )); |
| 369 | } |
| 370 | |
| 371 | return $messages; |
| 372 | } |
| 373 | |
| 374 | /** |
| 375 | * Batch process of vocabulary import. |
| 376 | * |
| 377 | * @param $options |
| 378 | * Array of batch options. |
| 379 | * @param &$context |
| 380 | * Batch context to keep results and messages. |
| 381 | * @return |
| 382 | * NULL because use of &$context. |
| 383 | */ |
| 384 | function _taxonomy_csv_vocabulary_import_process($options, &$context) { |
| 385 | $first = FALSE; |
| 386 | |
| 387 | // First callback. |
| 388 | if (empty($context['sandbox'])) { |
| 389 | // Remember options as batch_set can't use form_storage. |
| 390 | // It allows too that first line in result is numbered 1 and not 0. |
| 391 | $context['results'][0] = $options; |
| 392 | |
| 393 | $first = TRUE; |
| 394 | |
| 395 | // Automatically detect line endings. |
| 396 | ini_set('auto_detect_line_endings', '1'); |
| 397 | |
| 398 | // Initialize some variables. |
| 399 | $context['results'][0]['current_line'] = 0; |
| 400 | $context['results'][0]['worst_line'] = 0; |
| 401 | $context['results'][0]['worst_message'] = 999; |
| 402 | $context['results'][0]['handle'] = fopen($options['file']->filepath, 'r'); |
| 403 | $context['sandbox']['handle_pointer'] = 0; |
| 404 | $context['sandbox']['max'] = $options['total_lines']; |
| 405 | $context['sandbox']['previous_items'] = array( |
| 406 | 'name' => array(), |
| 407 | 'tid' => array(), |
| 408 | ); |
| 409 | |
| 410 | // Enable or disable cache. |
| 411 | $result = _taxonomy_csv_term_cache($options['internal_cache'], 'use_cache'); |
| 412 | } |
| 413 | elseif (!is_resource($context['results'][0]['handle'])) { |
| 414 | // Recall file and set pointer in case of memory or time out. |
| 415 | $context['results'][0]['handle'] = fopen($options['file']->filepath, 'r'); |
| 416 | fseek($context['results'][0]['handle'], $context['sandbox']['handle_pointer']); |
| 417 | |
| 418 | // Reload internal static cache if it has been removed in case of long batch. |
| 419 | if ($options['internal_cache'] |
| 420 | && isset($context['results'][0]['imported_terms']) |
| 421 | && (_taxonomy_csv_term_cache('', 'count') == 0)) { |
| 422 | $result = _taxonomy_csv_term_cache($context['results'][0]['imported_terms'], 'set_cache'); |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | // Load and process one line. |
| 427 | $worst_line = &$context['results'][0]['worst_line']; |
| 428 | $worst_message = &$context['results'][0]['worst_message']; |
| 429 | $handle = &$context['results'][0]['handle']; |
| 430 | $line_number = &$context['results'][0]['current_line']; |
| 431 | $previous_items = &$context['sandbox']['previous_items']; |
| 432 | $line = fgetcsv($handle, 32768, $options['delimiter'], $options['enclosure']); |
| 433 | if ($line) { |
| 434 | $line_number++; |
| 435 | |
| 436 | // Remember pointer in case of memory or time out. |
| 437 | $context['sandbox']['handle_pointer'] = ftell($handle); |
| 438 | |
| 439 | // Skip eventual UTF-8 byte order mark. |
| 440 | if ($first) { |
| 441 | if (strncmp($line[0], "\xEF\xBB\xBF", 3) === 0) { |
| 442 | $line[0] = substr($line[0], 3); |
| 443 | $first = FALSE; |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | // Process import of current line. |
| 448 | $result = taxonomy_csv_line_import($line, $options, $previous_items); |
| 449 | |
| 450 | // Remember processed line. |
| 451 | $previous_items['name'] = $result['name']; |
| 452 | $previous_items['tid'] = $result['tid']; |
| 453 | |
| 454 | // Remember worst message of imported lines. |
| 455 | $worst_message_new = _taxonomy_csv_message_get_worst_message($result['msg']); |
| 456 | if ($worst_message_new < $worst_message) { |
| 457 | $worst_message = $worst_message_new; |
| 458 | $worst_line = $line_number; |
| 459 | }; |
| 460 | |
| 461 | if ($options['internal_cache']) { |
| 462 | // Remember only wanted logs. |
| 463 | switch ($options['result_level']) { |
| 464 | case 'none': |
| 465 | break; |
| 466 | case 'warnings': |
| 467 | $list_messages = array(); |
| 468 | foreach ($result['msg'] as $value) { |
| 469 | foreach ($value as $msg) { |
| 470 | if (_taxonomy_csv_message_get_level($msg) <= TAXONOMY_CSV_WATCHDOG_WARNING) { |
| 471 | $list_messages[$msg] = $msg; |
| 472 | } |
| 473 | } |
| 474 | } |
| 475 | if (count($list_messages)) { |
| 476 | sort($list_messages); |
| 477 | $context['results'][$line_number] = $list_messages; |
| 478 | } |
| 479 | break; |
| 480 | case 'notices': |
| 481 | $list_messages = array(); |
| 482 | foreach ($result['msg'] as $value) { |
| 483 | foreach ($value as $msg) { |
| 484 | if (_taxonomy_csv_message_get_level($msg) <= TAXONOMY_CSV_WATCHDOG_NOTICE) { |
| 485 | $list_messages[$msg] = $msg; |
| 486 | } |
| 487 | } |
| 488 | } |
| 489 | sort($list_messages); |
| 490 | $context['results'][$line_number] = $list_messages; |
| 491 | break; |
| 492 | case 'infos': |
| 493 | $list_messages = array(); |
| 494 | foreach ($result['msg'] as $value) { |
| 495 | foreach ($value as $msg) { |
| 496 | $list_messages[$msg] = $msg; |
| 497 | } |
| 498 | } |
| 499 | sort($list_messages); |
| 500 | $context['results'][$line_number] = $list_messages; |
| 501 | break; |
| 502 | case 'full': |
| 503 | $context['results'][$line_number] = $result; |
| 504 | break; |
| 505 | } |
| 506 | |
| 507 | // Save static cache to keep informations about process in case of batch timeout. |
| 508 | $context['results'][0]['imported_terms'] =& _taxonomy_csv_term_cache('', 'dump'); |
| 509 | } |
| 510 | |
| 511 | // Inform about progress. |
| 512 | $context['message'] = t('Line !line_number of !total_lines processed: !line', array( |
| 513 | '!line_number' => $line_number, |
| 514 | '!total_lines' => $options['total_lines'], |
| 515 | // $previous_items['name'] can be used, except with taxonomy_manager format. |
| 516 | '!line' => implode(', ', $line), |
| 517 | )); |
| 518 | |
| 519 | // Check worst message of imported lines and update progress. |
| 520 | if (_taxonomy_csv_message_get_level($worst_message) > TAXONOMY_CSV_WATCHDOG_ERROR) { |
| 521 | $context['finished'] = $line_number / $context['sandbox']['max']; |
| 522 | } |
| 523 | else { |
| 524 | $context['finished'] = 1; |
| 525 | if ($options['internal_cache']) { |
| 526 | $result = _taxonomy_csv_term_cache('', 'clear'); |
| 527 | } |
| 528 | } |
| 529 | } |
| 530 | // Keep last previous items with fields_links in order to check vocabulary. |
| 531 | if ($options['import_format'] == TAXONOMY_CSV_FORMAT_FIELDS_LINKS) { |
| 532 | $context['results'][0]['last_items'] = $previous_items; |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | /** |
| 537 | * Callback for finished batch import and display result informations. |
| 538 | */ |
| 539 | function _taxonomy_csv_vocabulary_import_finished($success, $results, $operations) { |
| 540 | // $results[0] is used to save options and some infos (imported terms), as batch process can't use $form_state. |
| 541 | $options = &$results[0]; |
| 542 | unset($results[0]); |
| 543 | |
| 544 | // Close imported file. |
| 545 | if ($options['handle']) { |
| 546 | fclose($options['handle']); |
| 547 | } |
| 548 | |
| 549 | // Clear internal cache. |
| 550 | if ($options['internal_cache']) { |
| 551 | $result = _taxonomy_csv_term_cache('', 'clear'); |
| 552 | } |
| 553 | |
| 554 | // Display general info. |
| 555 | drupal_set_message(_taxonomy_csv_info_chosen_options($options), 'status'); |
| 556 | |
| 557 | // Check and info on used or created vocabularies. |
| 558 | $vocabularies = ($options['import_format'] == TAXONOMY_CSV_FORMAT_FIELDS_LINKS) ? |
| 559 | $options['last_items']['tid']['vocabulary'] : |
| 560 | array($options['vocabulary_id']); |
| 561 | $message = t('!count used or created.', array('!count' => format_plural(count($vocabularies), 'A vocabulary has been', '@count vocabularies have been'))); |
| 562 | drupal_set_message($message, 'status'); |
| 563 | foreach ($vocabularies as $vocabulary_id) { |
| 564 | // Check and update hierarchy of vocabularies. |
| 565 | $vocabulary = taxonomy_vocabulary_load($vocabulary_id); |
| 566 | $new_hierarchy[$vocabulary_id] = ($options['hierarchy_check']) ? |
| 567 | taxonomy_csv_vocabulary_check_hierarchy($vocabulary_id) : |
| 568 | taxonomy_csv_vocabulary_check_hierarchy($vocabulary_id, $options['hierarchy_level']); |
| 569 | |
| 570 | // Display general info about vocabulary. |
| 571 | $message = ($options['import_format'] == TAXONOMY_CSV_FORMAT_FIELDS_LINKS) ? |
| 572 | t('Vocabulary "%vocabulary_name" has been used or created.', array('%vocabulary_name' => $vocabulary->name)) : |
| 573 | _taxonomy_csv_info_vocabulary_destination($vocabulary, $options['vocabulary_target']); |
| 574 | $message .= '<br />'; |
| 575 | $message .= _taxonomy_csv_info_vocabulary_result($vocabulary, $options['vocabulary_target'], $new_hierarchy[$vocabulary_id]) .'<br />'; |
| 576 | if (!$options['hierarchy_check']) { |
| 577 | $message .= t('Hierarchy level has been manually set.') .'<br />'; |
| 578 | } |
| 579 | drupal_set_message($message, 'status'); |
| 580 | } |
| 581 | |
| 582 | // Prepare batch result message. |
| 583 | if (!$success) { |
| 584 | $message = t('Importation failed'); |
| 585 | $message .= '<br />'. t('This issue is related to import process or to size import and probably not to content.'); |
| 586 | if ($options['current_line'] == $options['total_lines']) { |
| 587 | $message .= '<br />'. t('Import process was successful, but the evaluation of hierarchy level has failed, because it is highly memory consumming. You need to change its value with default Drupal functions or set it in Tweaks section of Taxonomy csv.') .' '. t('This problem can be caused too by a high log level. You can reduce it in advanced options.'); |
| 588 | } |
| 589 | else { |
| 590 | $message .= '<br />'. t('Import process was successful until the line !line_count of a total of !total_lines. You can first check your file on this line and check file uploading.', array( |
| 591 | '!line_count' => $options['current_line'], |
| 592 | '!total_lines' => $options['total_lines'], |
| 593 | )); |
| 594 | } |
| 595 | $message .= '<br />'. t('You can reduce log level or disable internal cache to avoid this error.'); |
| 596 | $message .= '<br />'. t('You can reinstall module from a fresh release or submit an issue on <a href="!link">Taxonomy CSV import/export module</a>.', array( |
| 597 | '!link' => url('http://drupal.org/project/issues/taxonomy_csv/'), |
| 598 | )); |
| 599 | |
| 600 | drupal_set_message($message, 'error'); |
| 601 | } |
| 602 | else { |
| 603 | // Short summary information. |
| 604 | switch (_taxonomy_csv_message_get_level($options['worst_message'])) { |
| 605 | case TAXONOMY_CSV_WATCHDOG_ERROR: |
| 606 | drupal_set_message(t('Errors have been reported during import process. Process failed at line !line_number with error: !message', array( |
| 607 | '!line_number' => $options['worst_line'], |
| 608 | '!message' => _taxonomy_csv_info_result_text($options['worst_message']), |
| 609 | )), 'error'); |
| 610 | break; |
| 611 | case TAXONOMY_CSV_WATCHDOG_WARNING: |
| 612 | drupal_set_message(t('Warnings have been reported during import process (bad formatted lines). First line skipped is line !line_number with message: !message', array( |
| 613 | '!line_number' => $options['worst_line'], |
| 614 | '!message' => _taxonomy_csv_info_result_text($options['worst_message']), |
| 615 | )), 'error'); |
| 616 | break; |
| 617 | case TAXONOMY_CSV_WATCHDOG_NOTICE: |
| 618 | drupal_set_message(t('Notices have been reported during import process (bad formatted or empty lines). Lines processed. First notice on line !line_number with message: !message', array( |
| 619 | '!line_number' => $options['worst_line'], |
| 620 | '!message' => _taxonomy_csv_info_result_text($options['worst_message']), |
| 621 | )), 'warning'); |
| 622 | break; |
| 623 | case TAXONOMY_CSV_WATCHDOG_INFO: |
| 624 | default: |
| 625 | drupal_set_message(t('No error, warnings or notices have been reported during import process.'), 'status'); |
| 626 | break; |
| 627 | } |
| 628 | |
| 629 | if (!$options['line_checks']) { |
| 630 | drupal_set_message(t('Line checks have been disabled. Some warnings and notices may have not been reported.'), 'warning'); |
| 631 | } |
| 632 | |
| 633 | if (!$options['internal_cache']) { |
| 634 | drupal_set_message(t('No more information about process because internal cache is disabled.'), 'notice'); |
| 635 | } |
| 636 | else { |
| 637 | // Display stats and eventually lists about imported terms. |
| 638 | if ($options['result_stats'] || $options['result_terms']) { |
| 639 | if (isset($options['imported_terms'])) { |
| 640 | if ($options['result_terms']) { |
| 641 | drupal_set_message(_taxonomy_csv_info_terms_stats($options['imported_terms'], 'full'), 'status'); |
| 642 | } |
| 643 | elseif ($options['result_stats']) { |
| 644 | drupal_set_message(_taxonomy_csv_info_terms_stats($options['imported_terms'], 'stats'), 'status'); |
| 645 | } |
| 646 | } |
| 647 | else { |
| 648 | drupal_set_message(t('No term was imported.'), 'warning'); |
| 649 | } |
| 650 | } |
| 651 | |
| 652 | // Display detailled result of import. |
| 653 | if ($options['result_level'] != 'none') { |
| 654 | if (count($results)) { |
| 655 | drupal_set_message(t('Available informations about lines import.')); |
| 656 | call_user_func("_taxonomy_csv_info_result_{$options['result_type']}", $results, $options['result_level']); |
| 657 | } |
| 658 | else { |
| 659 | drupal_set_message(t('No more information reported about lines import.')); |
| 660 | } |
| 661 | } |
| 662 | } |
| 663 | } |
| 664 | } |
| 665 | |
| 666 | /** |
| 667 | * Prepare the export of a vocabulary. |
| 668 | * If not used in a form, don't forget to use batch_process(). |
| 669 | * |
| 670 | * @param $options |
| 671 | * Array of options: |
| 672 | * export_format: see _taxonomy_csv_info_lists('list_export_format') |
| 673 | * vocabulary_id: vocabulary id to export (default: 0, which means all) |
| 674 | * delimiter : one character csv delimiter (default: ",") |
| 675 | * enclosure : zero or one character csv enclosure (default: "") |
| 676 | * line_ending : end of line character: 'Unix' (default), 'Mac' or 'Microsoft DOS' |
| 677 | * order : order of exported terms: 'name' (default), 'tid' or 'weight' |
| 678 | * fields_links_terms_ids : specific to fields_links: 'name_if_needed' (default), 'name', 'tid' |
| 679 | * fields_links_vocabularies_ids: specific to fields_links: 'none' (default), 'name', 'vid' |
| 680 | * Only export_format is needed. |
| 681 | * @return |
| 682 | * Array of errors or nothing (batch process to execute). |
| 683 | */ |
| 684 | function taxonomy_csv_vocabulary_export($options) { |
| 685 | // Check and eventually update options. |
| 686 | $result = _taxonomy_csv_vocabulary_export_check_options($options); |
| 687 | if (count($result)) { |
| 688 | return $result; |
| 689 | } |
| 690 | |
| 691 | // Csv variables. |
| 692 | $options['separator'] = $options['enclosure'] . $options['delimiter'] . $options['enclosure']; |
| 693 | $line_ending = array( |
| 694 | 'Unix' => "\n", |
| 695 | 'Mac' => "\r", |
| 696 | 'Microsoft DOS' => "\r\n", |
| 697 | ); |
| 698 | $options['end_of_line'] = $line_ending[$options['line_ending']]; |
| 699 | |
| 700 | // Prepare export batch. |
| 701 | $batch = array( |
| 702 | 'title' => t('Exporting terms to CSV file...'), |
| 703 | 'init_message' => t('Starting downloading of datas...'), |
| 704 | 'progress_message' => '', |
| 705 | 'error_message' => t('An error occurred during the export.'), |
| 706 | 'finished' => '_taxonomy_csv_vocabulary_export_finished', |
| 707 | 'file' => dirname(drupal_get_filename('module', 'taxonomy_csv')) .'/taxonomy_csv.api.inc', |
| 708 | 'progressive' => TRUE, |
| 709 | 'operations' => array( |
| 710 | 0 => array('_taxonomy_csv_vocabulary_export_process', array($options)) |
| 711 | ), |
| 712 | ); |
| 713 | |
| 714 | batch_set($batch); |
| 715 | } |
| 716 | |
| 717 | /** |
| 718 | * Validate options of exported vocabulary or line. |
| 719 | * |
| 720 | * @param $options |
| 721 | * Array of options. |
| 722 | * @return |
| 723 | * Array of messages errors if any. |
| 724 | * By reference options are cleaned and completed. |
| 725 | */ |
| 726 | function _taxonomy_csv_vocabulary_export_check_options(&$options) { |
| 727 | $messages = array(); |
| 728 | |
| 729 | // Set default value for unset options. |
| 730 | foreach (array( |
| 731 | 'vocabulary_id' => 0, |
| 732 | 'delimiter' => ',', |
| 733 | 'enclosure' => '', |
| 734 | 'line_ending' => 'Unix', |
| 735 | 'order' => 'name', |
| 736 | // Default options of specific imports. |
| 737 | 'fields_links_terms_ids' => 'name_if_needed', |
| 738 | 'fields_links_vocabularies_ids' => 'none', |
| 739 | ) as $key => $value) { |
| 740 | if (!isset($options[$key])) { |
| 741 | $options[$key] = $value; |
| 742 | } |
| 743 | } |
| 744 | |
| 745 | // Check if there is write access and prepare file (first time only). |
| 746 | if (!isset($options['file'])) { |
| 747 | // Set filename. |
| 748 | if ($options['vocabulary_id']) { |
| 749 | $vocabulary = taxonomy_vocabulary_load($options['vocabulary_id']); |
| 750 | $vocabulary_name = $vocabulary->name; |
| 751 | } |
| 752 | else { |
| 753 | $vocabulary_name = t('Taxonomy'); |
| 754 | } |
| 755 | // Create file. |
| 756 | $filename = file_save_data( |
| 757 | '', |
| 758 | "$vocabulary_name.csv", |
| 759 | 'FILE_EXISTS_RENAME'); |
| 760 | if (!$filename) { |
| 761 | $messages['file'] = t('Export needs access to temp directory. Export failed.'); |
| 762 | } |
| 763 | else { |
| 764 | $options['file'] = new stdClass(); |
| 765 | $options['file']->filename = basename($filename); |
| 766 | $options['file']->filepath = $filename; |
| 767 | $options['file']->filesize = filesize($filename); |
| 768 | } |
| 769 | } |
| 770 | |
| 771 | $list_export_format = _taxonomy_csv_info_lists('list_export_format'); |
| 772 | if (!array_key_exists($options['export_format'], $list_export_format)) { |
| 773 | $messages['export_format'] = t('Export format "!export_format" is not managed.', array( |
| 774 | '!export_format' => $list_export_format[$options['export_format']], |
| 775 | )); |
| 776 | } |
| 777 | |
| 778 | $list_vocabularies = taxonomy_get_vocabularies(); |
| 779 | if (!$list_vocabularies) { |
| 780 | $messages['vocabulary_id'] = t('No vocabulary to export.'); |
| 781 | } |
| 782 | elseif ($options['vocabulary_id']) { |
| 783 | if (!isset($list_vocabularies[$options['vocabulary_id']])) { |
| 784 | $messages['vocabulary_id'] = t("You choose to export a vocabulary, but it doesn't exist."); |
| 785 | } |
| 786 | } |
| 787 | |
| 788 | // Delimiter and enclosure greater than one character are forbidden. |
| 789 | if (drupal_strlen($options['delimiter']) != 1) { |
| 790 | $messages['delimiter'] = t('Delimiter should be a one character string.'); |
| 791 | } |
| 792 | if (drupal_strlen($options['enclosure']) > 1) { |
| 793 | $messages['enclosure'] = t('Enclosure lenght cannot be greater than one character.'); |
| 794 | } |
| 795 | if ($options['delimiter'] == $options['enclosure']) { |
| 796 | $messages['delimiter'] = t('Delimiter and enclosure cannot be same character.'); |
| 797 | } |
| 798 | |
| 799 | if (!in_array($options['line_ending'], array( |
| 800 | 'Unix', |
| 801 | 'Mac', |
| 802 | 'Microsoft DOS', |
| 803 | ))) { |
| 804 | $messages['line_ending'] = t('Line ending should be "Unix", "Mac" or "Microsoft DOS".'); |
| 805 | } |
| 806 | |
| 807 | if (!in_array($options['order'], array( |
| 808 | 'name', |
| 809 | 'tid', |
| 810 | 'weight', |
| 811 | ))) { |
| 812 | $messages['order'] = t('Order should be "name", "tid" or "weight".'); |
| 813 | } |
| 814 | |
| 815 | if (!in_array($options['fields_links_terms_ids'], array( |
| 816 | 'name_if_needed', |
| 817 | 'name', |
| 818 | 'tid', |
| 819 | ))) { |
| 820 | $messages['fields_links_terms_ids'] = t('Terms identifiants should be "name_if_needed", "name" or "tid".'); |
| 821 | } |
| 822 | |
| 823 | if (!in_array($options['fields_links_vocabularies_ids'], array( |
| 824 | 'none', |
| 825 | 'name', |
| 826 | 'vid', |
| 827 | ))) { |
| 828 | $messages['fields_links_vocabularies_ids'] = t('Vocabularies identifiants should be "none", "name" or "vid".'); |
| 829 | } |
| 830 | |
| 831 | // When multiple vocabularies are exported, vocabulary names or tids is always needed. |
| 832 | if ($options['fields_links_vocabularies_ids'] == 'none') { |
| 833 | if ($options['vocabulary_id'] == 0) { |
| 834 | $options['fields_links_vocabularies_ids'] = 'name'; |
| 835 | } |
| 836 | else { |
| 837 | // Check if all related terms are related to main vocabulary. |
| 838 | ///TODO. Currently, check is made for each term in line_export. |
| 839 | } |
| 840 | } |
| 841 | |
| 842 | // Calculates number of terms to be exported. |
| 843 | $sql = " |
| 844 | SELECT COUNT(*) |
| 845 | FROM {term_data} |
| 846 | "; |
| 847 | $args = array(); |
| 848 | if ($options['vocabulary_id']) { |
| 849 | $sql .= ' WHERE vid = %d'; |
| 850 | $args[] = $options['vocabulary_id']; |
| 851 | } |
| 852 | $options['total_terms'] = array_shift(db_fetch_array(db_query($sql, $args))); |
| 853 | if (!$options['total_terms']) { |
| 854 | $message['vocabulary_id'] = t('No term to export. Export finished.'); |
| 855 | } |
| 856 | |
| 857 | return $messages; |
| 858 | } |
| 859 | |
| 860 | /** |
| 861 | * Batch process of vocabulary export. |
| 862 | * |
| 863 | * @param $options |
| 864 | * Array of batch options. |
| 865 | * @param &$context |
| 866 | * Batch context to keep results and messages. |
| 867 | * @return |
| 868 | * NULL because use of &$context. |
| 869 | */ |
| 870 | function _taxonomy_csv_vocabulary_export_process($options, &$context) { |
| 871 | // First callback. |
| 872 | if (empty($context['sandbox'])) { |
| 873 | // Remember options as batch_set can't use form_storage. |
| 874 | // It allows too that first line in result is numbered 1 and not 0. |
| 875 | $context['results'][0] = $options; |
| 876 | |
| 877 | // Initialize some variables. |
| 878 | $context['results'][0]['current_term'] = 0; |
| 879 | $context['results'][0]['current_name'] = ''; |
| 880 | $context['results'][0]['worst_term'] = 0; |
| 881 | $context['results'][0]['worst_name'] = ''; |
| 882 | $context['results'][0]['worst_message'] = 999; |
| 883 | // No pointer because new line is appended to file. |
| 884 | $context['results'][0]['handle'] = fopen($options['file']->filepath, 'a+'); |
| 885 | $context['sandbox']['max'] = $options['total_terms']; |
| 886 | |
| 887 | // Prepare terms to be exported. |
| 888 | $sql = " |
| 889 | SELECT * |
| 890 | FROM {term_data} |
| 891 | "; |
| 892 | $args = array(); |
| 893 | if ($options['vocabulary_id']) { |
| 894 | $sql .= ' WHERE vid = %d'; |
| 895 | $args[] = $options['vocabulary_id']; |
| 896 | } |
| 897 | // Use descendant order, because each term will be poped (quicker than shift). |
| 898 | $sql .= ' ORDER BY %s DESC'; |
| 899 | $args[] = $options['order']; |
| 900 | $result = db_query($sql, $args); |
| 901 | while ($term = db_fetch_array($result)) { |
| 902 | $context['sandbox']['terms'][] = $term; |
| 903 | } |
| 904 | |
| 905 | // Prepare list of duplicate terms for fields_links export. |
| 906 | $context['results'][0]['duplicate_terms'] = array(); |
| 907 | $sql = ' |
| 908 | SELECT t1.tid, t1.name |
| 909 | FROM {term_data} t1 |
| 910 | LEFT OUTER JOIN {term_data} t2 ON t1.tid != t2.tid AND LOWER(t1.name) = LOWER(t2.name) |
| 911 | WHERE t2.tid IS NOT NULL |
| 912 | '; |
| 913 | $args = array(); |
| 914 | if ($options['vocabulary_id']) { |
| 915 | $sql .= ' AND t1.vid = %d'; |
| 916 | $args[] = $options['vocabulary_id']; |
| 917 | } |
| 918 | $sql .= ' |
| 919 | ORDER BY {t1.tid} ASC |
| 920 | '; |
| 921 | $result = db_query($sql, $args); |
| 922 | while ($term = db_fetch_object($result)) { |
| 923 | $context['results'][0]['duplicate_terms'][$term->tid] = $term->name; |
| 924 | } |
| 925 | } |
| 926 | elseif (!is_resource($context['results'][0]['handle'])) { |
| 927 | // Recall file in case of memory or time out. No pointer, because terms are appended. |
| 928 | $context['results'][0]['handle'] = fopen($options['file']->filepath, 'a+'); |
| 929 | } |
| 930 | |
| 931 | // Load and process one term. |
| 932 | $worst_term = &$context['results'][0]['worst_term']; |
| 933 | $worst_name = &$context['results'][0]['worst_name']; |
| 934 | $worst_message = &$context['results'][0]['worst_message']; |
| 935 | $handle = &$context['results'][0]['handle']; |
| 936 | $duplicate_terms = &$context['results'][0]['duplicate_terms']; |
| 937 | $term_number = &$context['results'][0]['current_term']; |
| 938 | $current_name = &$context['results'][0]['current_name']; |
| 939 | $term = array_pop($context['sandbox']['terms']); |
| 940 | if ($term) { |
| 941 | $term_number++; |
| 942 | |
| 943 | // Remember current name in case of error. |
| 944 | $current_name = $term['name']; |
| 945 | |
| 946 | // Process export of current term. |
| 947 | $result = taxonomy_csv_line_export($term, $options, $duplicate_terms); |
| 948 | |
| 949 | // Check if separator, enclosure or line ending exist in line. |
| 950 | $check_line = implode('', $result['line']); |
| 951 | if ((strpos($check_line, $options['separator']) !== FALSE) |
| 952 | || (($options['enclosure'] != '') |
| 953 | && (strpos($check_line, $options['enclosure']) !== FALSE)) |
| 954 | || (($options['enclosure'] == '') |
| 955 | && (strpos($check_line, $options['end_of_line']) !== FALSE))) { |
| 956 | $result['msg'][] = array(313); // Error delimiter or enclosure. |
| 957 | } |
| 958 | |
| 959 | // Save line to file. |
| 960 | $line = $options['enclosure'] . implode($options['separator'], $result['line']) . $options['enclosure'] . $options['end_of_line']; |
| 961 | if (fwrite($handle, $line) === FALSE) { |
| 962 | $result['msg'][] = array(312); // Unable to write to file. |
| 963 | } |
| 964 | |
| 965 | // Remember worst message of exported terms. |
| 966 | $worst_message_new = _taxonomy_csv_message_get_worst_message($result['msg']); |
| 967 | if ($worst_message_new < $worst_message) { |
| 968 | $worst_term = $term_number; |
| 969 | $worst_name = $current_name; |
| 970 | $worst_message = $worst_message_new; |
| 971 | }; |
| 972 | |
| 973 | // Inform about progress. |
| 974 | $context['message'] = t('Term !term_number of !total_terms processed: %term', array( |
| 975 | '!term_number' => $term_number, |
| 976 | '!total_terms' => $options['total_terms'], |
| 977 | '%term' => $current_name, |
| 978 | )); |
| 979 | |
| 980 | // Check worst message of exported lines and update progress. |
| 981 | if (_taxonomy_csv_message_get_level($worst_message) > TAXONOMY_CSV_WATCHDOG_ERROR) { |
| 982 | $context['finished'] = $term_number / $context['sandbox']['max']; |
| 983 | } |
| 984 | else { |
| 985 | $context['finished'] = 1; |
| 986 | } |
| 987 | } |
| 988 | } |
| 989 | |
| 990 | /** |
| 991 | * Callback for finished batch export and display result informations. |
| 992 | */ |
| 993 | function _taxonomy_csv_vocabulary_export_finished($success, $results, $operations) { |
| 994 | // $results[0] is used to save options and some infos, as batch process can't use $form_state. |
| 995 | $options = &$results[0]; |
| 996 | unset($results[0]); |
| 997 | |
| 998 | // Close exported file. |
| 999 | if ($options['handle']) { |
| 1000 | fclose($options['handle']); |
| 1001 | } |
| 1002 | |
| 1003 | // General infos. |
| 1004 | $message = t('!term_count / !total_terms terms of chosen vocabularies have been exported to file <a href="!filepath">!filename</a> (!filesize KB). Click on link to view it or right click to download it.', array( |
| 1005 | '!term_count' => $options['current_term'], |
| 1006 | '!total_terms' => $options['total_terms'], |
| 1007 | '!filepath' => $options['file']->filepath, |
| 1008 | '!filename' => $options['file']->filename, |
| 1009 | '!filesize' => number_format(filesize($options['file']->filepath) / 1000, 1), |
| 1010 | )) .'<br />'; |
| 1011 | // Main check of end process. |
| 1012 | $status = (($options['current_term'] == $options['total_terms']) && ($options['current_term'] > 0)) ? 'status' : 'error'; |
| 1013 | drupal_set_message($message, $status); |
| 1014 | |
| 1015 | // Infos on duplicate term names. |
| 1016 | $duplicate_count = count($options['duplicate_terms']); |
| 1017 | // Duplicate term names exist. Array flip allows to display only duplicate names. |
| 1018 | if ($duplicate_count) { |
| 1019 | $message = t('!count duplicate term names have been found. Error can occur when you will import this list, except if you choose "fields and links" export format or import option "ignore existing items".', array('!count' => $duplicate_count)) .'<br />'. |
| 1020 | t('List of duplicate term names:') .'<br />"'. |
| 1021 | implode('", "', array_keys(array_flip($options['duplicate_terms']))) .'"'; |
| 1022 | $status = 'warning'; |
| 1023 | } |
| 1024 | else { |
| 1025 | // No duplicate term names. |
| 1026 | $message = t('No duplicate term name has been found.'); |
| 1027 | $status = 'status'; |
| 1028 | } |
| 1029 | drupal_set_message($message, $status); |
| 1030 | |
| 1031 | // Prepare batch result message. |
| 1032 | if (!$success) { |
| 1033 | $message = t('Exportation failed'); |
| 1034 | $message .= '<br />'. t('This issue is related to export process and may be caused by a memory overrun of the database.'); |
| 1035 | if ($options['current_term'] == $options['total_terms']) { |
| 1036 | $message .= '<br />'. t('All terms were exported, but a unknown error appears after the end of process.'); |
| 1037 | } |
| 1038 | else { |
| 1039 | $message .= '<br />'. t('Export process was successful until the term !term_count (%term_name) of a total of !total_terms.', array( |
| 1040 | '!term_count' => $options['current_term'], |
| 1041 | '%term_name' => $options['current_name'], |
| 1042 | '!total_terms' => $options['total_terms'], |
| 1043 | )); |
| 1044 | } |
| 1045 | $message .= '<br />'. t('You can reinstall module from a fresh release or submit an issue on <a href="!link">Taxonomy CSV import/export module</a>.', array( |
| 1046 | '!link' => url('http://drupal.org/project/issues/taxonomy_csv/'), |
| 1047 | )); |
| 1048 | |
| 1049 | drupal_set_message($message, 'error'); |
| 1050 | } |
| 1051 | else { |
| 1052 | // Short summary information. |
| 1053 | switch (_taxonomy_csv_message_get_level($options['worst_message'])) { |
| 1054 | case TAXONOMY_CSV_WATCHDOG_ERROR: |
| 1055 | drupal_set_message(t('Errors have been reported during export process. Process failed at term !worst_count (%worst_name) with error: !message', array( |
| 1056 | '!worst_count' => $options['worst_term'], |
| 1057 | '%worst_name' => $options['worst_name'], |
| 1058 | '!message' => _taxonomy_csv_info_result_text($options['worst_message']), |
| 1059 | )), 'error'); |
| 1060 | break; |
| 1061 | case TAXONOMY_CSV_WATCHDOG_WARNING: |
| 1062 | drupal_set_message(t('Warnings have been reported during export process (bad formatted terms). First term skipped is term !worst_count (%worst_name) with message: !message', array( |
| 1063 | '!worst_count' => $options['worst_term'], |
| 1064 | '%worst_name' => $options['worst_name'], |
| 1065 | '!message' => _taxonomy_csv_info_result_text($options['worst_message']), |
| 1066 | )), 'error'); |
| 1067 | break; |
| 1068 | case TAXONOMY_CSV_WATCHDOG_NOTICE: |
| 1069 | drupal_set_message(t('Notices have been reported during export process (bad formatted or empty terms). Terms processed. First notice on term !worst_count (%worst_name) with message: !message', array( |
| 1070 | '!worst_count' => $options['worst_term'], |
| 1071 | '%worst_name' => $options['worst_name'], |
| 1072 | '!message' => _taxonomy_csv_info_result_text($options['worst_message']), |
| 1073 | )), 'warning'); |
| 1074 | break; |
| 1075 | case TAXONOMY_CSV_WATCHDOG_INFO: |
| 1076 | default: |
| 1077 | drupal_set_message(t('No error, warnings or notices have been reported during export process.'), 'status'); |
| 1078 | break; |
| 1079 | } |
| 1080 | } |
| 1081 | } |
| 1082 | |
| 1083 | /** |
| 1084 | * Creates vocabulary by its name and returns an array with its vid and its name. |
| 1085 | */ |
| 1086 | function taxonomy_csv_vocabulary_create($vocabulary_name = '') { |
| 1087 | // Create an empty vocabulary. Relations and hierarchy are updated after import. |
| 1088 | $vocabulary = array( |
| 1089 | 'name' => _taxonomy_csv_vocabulary_create_name($vocabulary_name), |
| 1090 | 'description' => t('Auto created vocabulary by taxonomy_csv module'), |
| 1091 | 'help' => '', |
| 1092 | 'relations' => TRUE, |
| 1093 | 'hierarchy' => 2, |
| 1094 | 'multiple' => TRUE, |
| 1095 | 'required' => FALSE, |
| 1096 | 'tags' => FALSE, |
| 1097 | 'module' => 'taxonomy', |
| 1098 | 'weight' => 0, |
| 1099 | 'nodes' => array(), |
| 1100 | ); |
| 1101 | |
| 1102 | taxonomy_save_vocabulary($vocabulary); |
| 1103 | |
| 1104 | return array( |
| 1105 | 'vid' => taxonomy_csv_vocabulary_get_id($vocabulary['name']), |
| 1106 | 'name' => $vocabulary['name'], |
| 1107 | ); |
| 1108 | } |
| 1109 | |
| 1110 | /** |
| 1111 | * Duplicates a vocabulary by its vid and returns an array with vid and name. |
| 1112 | * If not exist, creates an empty vocabulary. |
| 1113 | */ |
| 1114 | function taxonomy_csv_vocabulary_duplicate($vocabulary_id) { |
| 1115 | $original_vocabulary = taxonomy_vocabulary_load($vocabulary_id); |
| 1116 | if ($original_vocabulary) { |
| 1117 | // Creates an unused name. Check if name begins with 'Copy of #name' in order to serialize name. |
| 1118 | $name = t('Copy of [!vocabulary_name]', array('!vocabulary_name' => $original_vocabulary->name)); |
| 1119 | $name = _taxonomy_csv_vocabulary_create_name((strpos($original_vocabulary->name, $name) === FALSE) ? $name : $original_vocabulary->name); |
| 1120 | |
| 1121 | // Duplicate original vocabulary, except relations and hierarchy, updated after import. |
| 1122 | $duplicated_vocabulary = array( |
| 1123 | 'name' => $name, |
| 1124 | 'description' => $original_vocabulary->description, |
| 1125 | 'help' => $original_vocabulary->help, |
| 1126 | 'relations' => $original_vocabulary->relations, |
| 1127 | 'hierarchy' => $original_vocabulary->hierarchy, |
| 1128 | 'multiple' => $original_vocabulary->multiple, |
| 1129 | 'required' => $original_vocabulary->required, |
| 1130 | 'tags' => $original_vocabulary->tags, |
| 1131 | 'module' => $original_vocabulary->module, |
| 1132 | 'weight' => $original_vocabulary->weight, |
| 1133 | 'nodes' => array(), |
| 1134 | ); |
| 1135 | |
| 1136 | taxonomy_save_vocabulary($duplicated_vocabulary); |
| 1137 | |
| 1138 | $duplicated_vocabulary['vid'] = taxonomy_csv_vocabulary_get_id($duplicated_vocabulary['name']); |
| 1139 | |
| 1140 | // Get all terms and attributes of original vocabulary |
| 1141 | // and copy them in the new one in two steps. |
| 1142 | $original_terms = taxonomy_get_tree($original_vocabulary->vid); |
| 1143 | |
| 1144 | // First step: copy each term except relations and parents. |
| 1145 | $duplicated_terms = array(); |
| 1146 | foreach ($original_terms as $original_term) { |
| 1147 | $duplicated_terms[$original_term->tid] = array( |
| 1148 | 'vid' => $duplicated_vocabulary['vid'], |
| 1149 | 'name' => $original_term->name, |
| 1150 | 'description' => $original_term->description, |
| 1151 | 'weight' => $original_term->weight, |
| 1152 | 'synonyms' => taxonomy_get_synonyms($original_term->tid), |