Projet

Général

Profil

Paste
Télécharger (39,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / feeds / feeds_ui / feeds_ui.admin.inc @ a192dc0b

1
<?php
2

    
3
/**
4
 * @file
5
 * Contains all page callbacks, forms and theming functions for Feeds
6
 * administrative pages.
7
 */
8

    
9
/**
10
 * Introductory help for admin/structure/feeds/%feeds_importer page
11
 */
12
function feeds_ui_edit_help() {
13
  return t('
14
    <p>
15
    You can create as many Feeds importer configurations as you would like to. Each can have a distinct purpose like letting your users aggregate RSS feeds or importing a CSV file for content migration. Here are a couple of things that are important to understand in order to get started with Feeds:
16
    </p>
17
    <ul>
18
    <li>
19
    Every importer configuration consists of basic settings, a fetcher, a parser and a processor and their settings.
20
    </li>
21
    <li>
22
    The <strong>basic settings</strong> define the general behavior of the importer. <strong>Fetchers</strong> are responsible for loading data, <strong>parsers</strong> for organizing it and <strong>processors</strong> for "doing stuff" with it, usually storing it.
23
    </li>
24
    <li>
25
    In Basic settings, you can <strong>attach an importer configuration to a content type</strong>. This is useful when many imports of a kind should be created, for example in an RSS aggregation scenario. If you don\'t attach a configuration to a content type, you can use it on the !import page.
26
    </li>
27
    <li>
28
    Imports can be <strong>scheduled periodically</strong> - see the periodic import select box in the Basic settings.
29
    </li>
30
    <li>
31
    Processors can have <strong>mappings</strong> in addition to settings. Mappings allow you to define what elements of a data feed should be mapped to what content fields on a granular level. For instance, you can specify that a feed item\'s author should be mapped to a node\'s body.
32
    </li>
33
    </ul>
34
    ', array('!import' => l(t('Import'), 'import')));
35
}
36

    
37
/**
38
 * Help text for mapping.
39
 */
40
function feeds_ui_mapping_help() {
41
  return t('
42
  <p>
43
  Define which elements of a single item of a feed (= <em>Sources</em>) map to which content pieces in Drupal (= <em>Targets</em>). Make sure that at least one definition has a <em>Unique target</em>. A unique target means that a value for a target can only occur once. E. g. only one item with the URL <em>http://example.com/content/1</em> can exist.
44
  </p>
45
  ');
46
}
47

    
48
/**
49
 * Build overview of available configurations.
50
 */
51
function feeds_ui_overview_form($form, &$form_status) {
52
  $form = $form['enabled'] = $form['disabled'] = array();
53

    
54
  $form['#header'] = array(
55
    t('Name'),
56
    t('Description'),
57
    t('Attached to'),
58
    t('Status'),
59
    t('Operations'),
60
    t('Enabled'),
61
  );
62
  foreach (feeds_importer_load_all(TRUE) as $importer) {
63
    $importer_form = array();
64
    $importer_form['name']['#markup'] = check_plain($importer->config['name']);
65
    $importer_form['description']['#markup'] = check_plain($importer->config['description']);
66
    if (empty($importer->config['content_type'])) {
67
      $importer_form['attached']['#markup'] = '[none]';
68
    }
69
    else {
70
      if (!$importer->disabled) {
71
        $importer_form['attached']['#markup'] = l(node_type_get_name($importer->config['content_type']), 'node/add/' . str_replace('_', '-', $importer->config['content_type']));
72
      }
73
      else {
74
        $importer_form['attached']['#markup'] = check_plain(node_type_get_name($importer->config['content_type']));
75
      }
76
    }
77

    
78
    if ($importer->export_type == EXPORT_IN_CODE) {
79
      $status = t('Default');
80
      $edit = t('Override');
81
      $delete = '';
82
    }
83
    elseif ($importer->export_type == EXPORT_IN_DATABASE) {
84
      $status = t('Normal');
85
      $edit = t('Edit');
86
      $delete = t('Delete');
87
    }
88
    elseif ($importer->export_type == (EXPORT_IN_CODE | EXPORT_IN_DATABASE)) {
89
      $status = t('Overridden');
90
      $edit = t('Edit');
91
      $delete = t('Revert');
92
    }
93
    $importer_form['status'] = array(
94
      '#markup' => $status,
95
    );
96
    $importer_form['operations'] = array(
97
      '#markup' =>
98
        l($edit, 'admin/structure/feeds/' . $importer->id) . ' | ' .
99
        l(t('Export'), 'admin/structure/feeds/' . $importer->id . '/export') . ' | ' .
100
        l(t('Clone'), 'admin/structure/feeds/' . $importer->id . '/clone') .
101
        (empty($delete) ? '' :  ' | ' . l($delete, 'admin/structure/feeds/' . $importer->id . '/delete')),
102
    );
103

    
104
    $importer_form[$importer->id] = array(
105
      '#type' => 'checkbox',
106
      '#default_value' => !$importer->disabled,
107
    );
108

    
109
    if ($importer->disabled) {
110
      $form['disabled'][$importer->id] = $importer_form;
111
    }
112
    else {
113
      $form['enabled'][$importer->id] = $importer_form;
114
    }
115
  }
116
  $form['submit'] = array(
117
    '#type' => 'submit',
118
    '#value' => t('Save'),
119
  );
120
  return $form;
121
}
122

    
123
/**
124
 * Submit handler for feeds_ui_overview_form().
125
 */
126
function feeds_ui_overview_form_submit($form, &$form_state) {
127

    
128
  $disabled = array();
129
  foreach (feeds_importer_load_all(TRUE) as $importer) {
130
    $disabled[$importer->id] = !$form_state['values'][$importer->id];
131
  }
132
  variable_set('default_feeds_importer', $disabled);
133
  feeds_cache_clear();
134
}
135

    
136
/**
137
 * Create a new configuration.
138
 *
139
 * @param $form_state
140
 *  Form API form state array.
141
 * @param $from_importer
142
 *   FeedsImporter object. If given, form will create a new importer as a copy
143
 *   of $from_importer.
144
 */
145
function feeds_ui_create_form($form, &$form_state, $from_importer = NULL) {
146
  $form['#from_importer'] = $from_importer;
147
  $form['name'] = array(
148
    '#type' => 'textfield',
149
    '#title' => t('Name'),
150
    '#description' => t('A natural name for this configuration. Example: RSS Feed. You can always change this name later.'),
151
    '#required' => TRUE,
152
    '#maxlength' => 128,
153
  );
154
  $form['id'] = array(
155
    '#type' => 'machine_name',
156
    '#required' => TRUE,
157
    '#maxlength' => 128,
158
    '#machine_name' => array(
159
      'exists' => 'feeds_ui_importer_machine_name_exists',
160
    ),
161
  );
162
  $form['description'] = array(
163
    '#type' => 'textfield',
164
    '#title' => t('Description'),
165
    '#description' => t('A description of this configuration.'),
166
  );
167
  $form['submit'] = array(
168
    '#type' => 'submit',
169
    '#value' => t('Create'),
170
  );
171
  return $form;
172
}
173

    
174
/**
175
 * Validation callback for the importer machine name field.
176
 */
177
function feeds_ui_importer_machine_name_exists($id) {
178
  if ($id == 'create') {
179
    // Create is a reserved path for the add importer form.
180
    return TRUE;
181
  }
182
  ctools_include('export');
183
  if (ctools_export_load_object('feeds_importer', 'conditions', array('id' => $id))) {
184
    return TRUE;
185
  }
186
}
187

    
188
/**
189
 * Validation handler for feeds_build_create_form().
190
 */
191
function feeds_ui_create_form_validate($form, &$form_state) {
192
  if (!empty($form_state['values']['id'])) {
193
    $importer = feeds_importer($form_state['values']['id']);
194
    $importer->configFormValidate($form_state['values']);
195
  }
196
}
197

    
198
/**
199
 * Submit handler for feeds_build_create_form().
200
 */
201
function feeds_ui_create_form_submit($form, &$form_state) {
202
  // Create feed.
203
  $importer = feeds_importer($form_state['values']['id']);
204
  // If from_importer is given, copy its configuration.
205
  if (!empty($form['#from_importer'])) {
206
    $importer->copy($form['#from_importer']);
207
  }
208
  // In any case, we want to set this configuration's title and description.
209
  $importer->addConfig($form_state['values']);
210
  $importer->save();
211

    
212
  // Set a message and redirect to settings form.
213
  if (empty($form['#from_importer'])) {
214
    drupal_set_message(t('Your configuration has been created with default settings. If they do not fit your use case you can adjust them here.'));
215
  }
216
  else {
217
    drupal_set_message(t('A clone of the @name configuration has been created.', array('@name' => $form['#from_importer']->config['name'])));
218
  }
219
  $form_state['redirect'] = 'admin/structure/feeds/' . $importer->id;
220
  feeds_cache_clear();
221
}
222

    
223
/**
224
 * Delete configuration form.
225
 */
226
function feeds_ui_delete_form($form, &$form_state, $importer) {
227
  $form['#importer'] = $importer->id;
228
  if ($importer->export_type & EXPORT_IN_CODE) {
229
    $title = t('Would you really like to revert the importer @importer?', array('@importer' => $importer->config['name']));
230
    $button_label = t('Revert');
231
  }
232
  else {
233
    $title = t('Would you really like to delete the importer @importer?', array('@importer' => $importer->config['name']));
234
    $button_label = t('Delete');
235
  }
236
  return confirm_form(
237
    $form,
238
    $title,
239
    'admin/structure/feeds',
240
    t('This action cannot be undone.'),
241
    $button_label
242
  );
243
}
244

    
245
/**
246
 * Submit handler for feeds_ui_delete_form().
247
 */
248
function feeds_ui_delete_form_submit($form, &$form_state) {
249
  $form_state['redirect'] = 'admin/structure/feeds';
250

    
251
  // Remove importer.
252
  feeds_importer($form['#importer'])->delete();
253

    
254
  // Clear cache, deleting a configuration may have an affect on menu tree.
255
  feeds_cache_clear();
256
}
257

    
258
/**
259
 * Export a feed configuration.
260
 */
261
function feeds_ui_export_form($form, &$form_state, $importer) {
262
  $code = feeds_export($importer->id);
263

    
264
  $form['export'] = array(
265
    '#title' => t('Export feed configuration'),
266
    '#type' => 'textarea',
267
    '#value' => $code,
268
    '#rows' => substr_count($code, "\n"),
269
  );
270
  return $form;
271
}
272

    
273
/**
274
 * Edit feed configuration.
275
 */
276
function feeds_ui_edit_page(FeedsImporter $importer, $active = 'help', $plugin_key = '') {
277
  // Get plugins and configuration.
278
  $plugins = FeedsPlugin::all();
279

    
280
  $config = $importer->config;
281
  // Base path for changing the active container.
282
  $path = 'admin/structure/feeds/' . $importer->id;
283

    
284
  $active_container = array(
285
    'class' => array('active-container'),
286
    'actions' => array(l(t('Help'), $path)),
287
  );
288
  switch ($active) {
289
    case 'help':
290
      $active_container['title'] = t('Getting started');
291
      $active_container['body'] = '<div class="help feeds-admin-ui">' . feeds_ui_edit_help() . '</div>';
292
      unset($active_container['actions']);
293
      break;
294

    
295
    case 'fetcher':
296
    case 'parser':
297
    case 'processor':
298
      $active_container['title'] = t('Select a !plugin_type', array('!plugin_type' => $active));
299
      $active_container['body'] = drupal_get_form('feeds_ui_plugin_form', $importer, $active);
300
      break;
301

    
302
    case 'settings':
303
      drupal_add_js(drupal_get_path('module', 'ctools') . '/js/dependent.js');
304
      ctools_include('dependent');
305
      if (empty($plugin_key)) {
306
        $active_container['title'] = t('Basic settings');
307
        $active_container['body'] = feeds_get_form($importer, 'configForm');
308
      }
309
      // feeds_plugin() returns a correct result because feed has been
310
      // instantiated previously.
311
      elseif (in_array($plugin_key, array_keys($plugins)) && $plugin = feeds_plugin($plugin_key, $importer->id)) {
312
        $active_container['title'] = t('Settings for !plugin', array('!plugin' => $plugins[$plugin_key]['name']));
313
        $active_container['body'] = feeds_get_form($plugin, 'configForm');
314
      }
315
      break;
316

    
317
    case 'mapping':
318
      $processor_name = isset($plugins[$config['processor']['plugin_key']]['name']) ? $plugins[$config['processor']['plugin_key']]['name'] : $plugins['FeedsMissingPlugin']['name'];
319
      $active_container['title'] = t('Mapping for @processor', array('@processor' => $processor_name));
320
      $active_container['body'] = drupal_get_form('feeds_ui_mapping_form', $importer);
321
      break;
322
  }
323

    
324
  // Build config info.
325
  $config_info = $info = array();
326
  $info['class'] = array('config-set');
327

    
328
  // Basic information.
329
  $items = array();
330
  $items[] = t('Attached to: @type', array('@type' => $importer->config['content_type'] ? node_type_get_name($importer->config['content_type']) : t('[none]')));
331
  if ($importer->config['import_period'] == FEEDS_SCHEDULE_NEVER) {
332
    $import_period = t('off');
333
  }
334
  elseif ($importer->config['import_period'] == 0) {
335
    $import_period = t('as often as possible');
336
  }
337
  else {
338
    $import_period = t('every !interval', array('!interval' => format_interval($importer->config['import_period'])));
339
  }
340
  $items[] = t('Periodic import: !import_period', array('!import_period' => $import_period));
341
  $items[] = $importer->config['import_on_create'] ? t('Import on submission') : t('Do not import on submission');
342

    
343
  $info['title'] = t('Basic settings');
344
  $info['body'] = array(
345
    array(
346
      'body' => theme('item_list', array('items' => $items)),
347
      'actions' => array(l(t('Settings'), $path . '/settings')),
348
    ),
349
  );
350
  $config_info[] = $info;
351

    
352
  // Fetcher.
353
  $fetcher = isset($plugins[$config['fetcher']['plugin_key']]) ? $plugins[$config['fetcher']['plugin_key']] : $plugins['FeedsMissingPlugin'];
354
  $actions = array();
355
  if ($importer->fetcher->hasConfigForm()) {
356
    $actions = array(l(t('Settings'), $path . '/settings/' . $config['fetcher']['plugin_key']));
357
  }
358
  $info['title'] = t('Fetcher');
359
  $info['body'] = array(
360
    array(
361
      'title' => $fetcher['name'],
362
      'body' => $fetcher['description'],
363
      'actions' => $actions,
364
    ),
365
  );
366
  $info['actions'] = array(l(t('Change'), $path . '/fetcher'));
367
  $config_info[] = $info;
368

    
369
  // Parser.
370
  $parser = isset($plugins[$config['parser']['plugin_key']]) ? $plugins[$config['parser']['plugin_key']] : $plugins['FeedsMissingPlugin'];
371
  $actions = array();
372
  if ($importer->parser->hasConfigForm()) {
373
    $actions = array(l(t('Settings'), $path . '/settings/' . $config['parser']['plugin_key']));
374
  }
375
  $info['title'] = t('Parser');
376
  $info['body'] = array(
377
    array(
378
      'title' => $parser['name'],
379
      'body' => $parser['description'],
380
      'actions' => $actions,
381
    )
382
  );
383
  $info['actions'] = array(l(t('Change'), $path . '/parser'));
384
  $config_info[] = $info;
385

    
386
  // Processor.
387
  $processor = isset($plugins[$config['processor']['plugin_key']]) ? $plugins[$config['processor']['plugin_key']] : $plugins['FeedsMissingPlugin'];
388
  $actions = array();
389
  if ($importer->processor->hasConfigForm()) {
390
    $actions[] = l(t('Settings'), $path . '/settings/' . $config['processor']['plugin_key']);
391
  }
392
  $actions[] = l(t('Mapping'), $path . '/mapping');
393
  $info['title'] = t('Processor');
394
  $info['body'] = array(
395
    array(
396
      'title' => $processor['name'],
397
      'body' => $processor['description'],
398
      'actions' => $actions,
399
    )
400
  );
401
  $info['actions'] = array(l(t('Change'), $path . '/processor'));
402
  $config_info[] = $info;
403

    
404
  return theme('feeds_ui_edit_page', array(
405
    'info' => $config_info,
406
    'active' => $active_container,
407
  ));
408
}
409

    
410
/**
411
 * Build a form of plugins to pick from.
412
 *
413
 * @param $form_state
414
 *   Form API form state array.
415
 * @param $importer
416
 *   FeedsImporter object.
417
 * @param $type
418
 *   Plugin type. One of 'fetcher', 'parser', 'processor'.
419
 *
420
 * @return
421
 *   A Form API form definition.
422
 */
423
function feeds_ui_plugin_form($form, &$form_state, $importer, $type) {
424
  $plugins = FeedsPlugin::byType($type);
425

    
426
  $form['#importer'] = $importer->id;
427
  $form['#plugin_type'] = $type;
428

    
429
  $importer_key = $importer->config[$type]['plugin_key'];
430

    
431
  foreach ($plugins as $key => $plugin) {
432

    
433
    $form['plugin_key'][$key] = array(
434
      '#type' => 'radio',
435
      '#parents' => array('plugin_key'),
436
      '#title' => check_plain($plugin['name']),
437
      '#description' => filter_xss(isset($plugin['help']) ? $plugin['help'] : $plugin['description']),
438
      '#return_value' => $key,
439
      '#default_value' => ($key == $importer_key) ? $key : '',
440
    );
441
  }
442
  $form['submit'] = array(
443
    '#type' => 'submit',
444
    '#value' => t('Save'),
445
  );
446
  return $form;
447
}
448

    
449
/**
450
 * Submit handler for feeds_ui_plugin_form().
451
 */
452
function feeds_ui_plugin_form_submit($form, &$form_state) {
453
  // Set the plugin and save feed.
454
  $importer = feeds_importer($form['#importer']);
455
  $importer->setPlugin($form_state['values']['plugin_key']);
456
  $importer->save();
457
  drupal_set_message(t('Changed @type plugin.', array('@type' => $form['#plugin_type'])));
458
}
459

    
460
/**
461
 * Theme feeds_ui_plugin_form().
462
 */
463
function theme_feeds_ui_plugin_form($variables) {
464
  $form = $variables['form'];
465
  $output = '';
466

    
467
  foreach (element_children($form['plugin_key']) as $key) {
468

    
469
    // Assemble container, render form elements.
470
    $container = array(
471
      'title' => $form['plugin_key'][$key]['#title'],
472
      'body' => isset($form['plugin_key'][$key]['#description']) ? $form['plugin_key'][$key]['#description'] : '',
473
    );
474
    $form['plugin_key'][$key]['#title'] = t('Select');
475
    $form['plugin_key'][$key]['#attributes']['class'] = array('feeds-ui-radio-link');
476
    unset($form['plugin_key'][$key]['#description']);
477
    $container['actions'] = array(drupal_render($form['plugin_key'][$key]));
478

    
479
    $output .= theme('feeds_ui_container', array('container' => $container));
480
  }
481

    
482
  $output .= drupal_render_children($form);
483
  return $output;
484
}
485

    
486
/**
487
 * Edit mapping.
488
 *
489
 * @todo Completely merge this into config form handling. This is just a
490
 *   shared form of configuration, most of the common functionality can live in
491
 *   FeedsProcessor, a flag can tell whether mapping is supported or not.
492
 */
493
function feeds_ui_mapping_form($form, &$form_state, $importer) {
494
  $form['#importer'] = $importer->id;
495
  $form['#mappings'] = $mappings = $importer->processor->getMappings();
496
  $form['help']['#markup'] = feeds_ui_mapping_help();
497
  $form['#prefix'] = '<div id="feeds-ui-mapping-form-wrapper">';
498
  $form['#suffix'] = '</div>';
499

    
500
  // Show message when target configuration gets changed.
501
  if (!empty($form_state['mapping_settings'])) {
502
    $form['#mapping_settings'] = $form_state['mapping_settings'];
503
    $form['changed'] = array(
504
      '#theme_wrappers' => array('container'),
505
      '#attributes' => array('class' => array('messages', 'warning')),
506
      '#markup' => t('* Changes made to target configuration are stored temporarily. Click Save to make your changes permanent.'),
507
    );
508
  }
509

    
510
  // Get mapping sources from parsers and targets from processor, format them
511
  // for output.
512
  // Some parsers do not define mapping sources but let them define on the fly.
513
  if ($sources = $importer->parser->getMappingSources()) {
514
    $source_options = _feeds_ui_format_options($sources);
515
    foreach ($sources as $k => $source) {
516
      if (!empty($source['deprecated'])) {
517
        continue;
518
      }
519
      $legend['sources'][$k]['name']['#markup'] = empty($source['name']) ? $k : $source['name'];
520
      $legend['sources'][$k]['description']['#markup'] = empty($source['description']) ? '' : $source['description'];
521
    }
522
  }
523
  else {
524
    $legend['sources']['#markup'] = t('This parser supports free source definitions. Enter the name of the source field in lower case into the Source text field above.');
525
  }
526
  $targets = $importer->processor->getMappingTargets();
527
  $target_options = _feeds_ui_format_options($targets);
528
  $legend['targets'] = array();
529
  foreach ($targets as $k => $target) {
530
    if (!empty($target['deprecated'])) {
531
      continue;
532
    }
533
    $legend['targets'][$k]['name']['#markup'] = empty($target['name']) ? $k : $target['name'];
534
    $legend['targets'][$k]['description']['#markup'] = empty($target['description']) ? '' : $target['description'];
535
  }
536

    
537
  // Legend explaining source and target elements.
538
  $form['legendset'] = array(
539
    '#type' => 'fieldset',
540
    '#title' => t('Legend'),
541
    '#collapsible' => TRUE,
542
    '#collapsed' => TRUE,
543
    '#tree' => TRUE,
544
  );
545
  $form['legendset']['legend'] = $legend;
546

    
547
  // Add config forms and remove flags to mappings.
548
  $form['config'] = $form['remove_flags'] = $form['mapping_weight'] = array(
549
    '#tree' => TRUE,
550
  );
551
  if (is_array($mappings)) {
552

    
553
    $delta = count($mappings) + 2;
554

    
555
    foreach ($mappings as $i => $mapping) {
556
      if (isset($targets[$mapping['target']])) {
557
        $form['config'][$i] = feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targets[$mapping['target']]);
558
      }
559

    
560
      $form['remove_flags'][$i] = array(
561
        '#type' => 'checkbox',
562
        '#title' => t('Remove'),
563
        '#prefix' => '<div class="feeds-ui-checkbox-link">',
564
        '#suffix' => '</div>',
565
      );
566

    
567
      $form['mapping_weight'][$i] = array(
568
        '#type' => 'weight',
569
        '#title' => '',
570
        '#default_value' => $i,
571
        '#delta' => $delta,
572
        '#attributes' => array(
573
          'class' => array(
574
            'feeds-ui-mapping-weight'
575
          ),
576
        ),
577
      );
578
    }
579
  }
580

    
581
  if (isset($source_options)) {
582
    $form['source'] = array(
583
      '#type' => 'select',
584
      '#title' => t('Source'),
585
      '#title_display' => 'invisible',
586
      '#options' => $source_options,
587
      '#empty_option' => t('- Select a source -'),
588
      '#description' => t('An element from the feed.'),
589
    );
590
  }
591
  else {
592
    $form['source'] = array(
593
      '#type' => 'textfield',
594
      '#title' => t('Source'),
595
      '#title_display' => 'invisible',
596
      '#size' => 20,
597
      '#default_value' => '',
598
      '#description' => t('The name of source field.'),
599
    );
600
  }
601
  $form['target'] = array(
602
    '#type' => 'select',
603
    '#title' => t('Target'),
604
    '#title_display' => 'invisible',
605
    '#options' => $target_options,
606
    '#empty_option' => t('- Select a target -'),
607
    '#description' => t('The field that stores the data.'),
608
  );
609

    
610
  $form['actions'] = array('#type' => 'actions');
611
  $form['actions']['save'] = array(
612
    '#type' => 'submit',
613
    '#value' => t('Save'),
614
  );
615
  return $form;
616
}
617

    
618
/**
619
 * Per mapper configuration form that is a part of feeds_ui_mapping_form().
620
 */
621
function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $target) {
622
  $form_state += array(
623
    'mapping_settings_edit' => NULL,
624
    'mapping_settings' => array(),
625
  );
626

    
627
  $base_button = array(
628
    '#submit' => array('feeds_ui_mapping_form_multistep_submit'),
629
    '#ajax' => array(
630
      'callback' => 'feeds_ui_mapping_settings_form_callback',
631
      'wrapper' => 'feeds-ui-mapping-form-wrapper',
632
      'effect' => 'fade',
633
      'progress' => 'none',
634
    ),
635
    '#i' => $i,
636
  );
637

    
638
  if (isset($form_state['mapping_settings'][$i])) {
639
    $mapping = $form_state['mapping_settings'][$i] + $mapping;
640
  }
641

    
642
  if ($form_state['mapping_settings_edit'] === $i) {
643
    $settings_form = array();
644

    
645
    foreach ($target['form_callbacks'] as $callback) {
646
      $settings_form += call_user_func($callback, $mapping, $target, $form, $form_state);
647
    }
648

    
649
    // Merge in the optional unique form.
650
    $settings_form += feeds_ui_mapping_settings_optional_unique_form($mapping, $target, $form, $form_state);
651

    
652
    return array(
653
      '#type' => 'container',
654
      'settings' => $settings_form,
655
      'save_settings' => $base_button + array(
656
        '#type' => 'submit',
657
        '#name' => 'mapping_settings_update_' . $i,
658
        '#value' => t('Update'),
659
        '#op' => 'update',
660
      ),
661
      'cancel_settings' => $base_button + array(
662
        '#type' => 'submit',
663
        '#name' => 'mapping_settings_cancel_' . $i,
664
        '#value' => t('Cancel'),
665
        '#op' => 'cancel',
666
      ),
667
    );
668
  }
669
  else {
670
    // Build the summary.
671
    $summary = array();
672

    
673
    foreach ($target['summary_callbacks'] as $callback) {
674
      $summary[] = call_user_func($callback, $mapping, $target, $form, $form_state);
675
    }
676

    
677
    // Filter out empty summary values.
678
    $summary = implode('<br />', array_filter($summary));
679

    
680
    // Append the optional unique summary.
681
    if ($optional_unique_summary = feeds_ui_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state)) {
682
      $summary .= ' ' . $optional_unique_summary;
683
    }
684

    
685
    if ($summary) {
686
      return array(
687
        'summary' => array(
688
          '#prefix' => '<div>',
689
          '#markup' => $summary,
690
          '#suffix' => '</div>',
691
        ),
692
       'edit_settings' => $base_button + array(
693
          '#type' => 'image_button',
694
          '#name' => 'mapping_settings_edit_' . $i,
695
          '#src' => 'misc/configure.png',
696
          '#attributes' => array('alt' => t('Edit')),
697
          '#op' => 'edit',
698
        ),
699
      );
700
    }
701
  }
702
  return array();
703
}
704

    
705
/**
706
 * Submit callback for a per mapper configuration form. Switches between edit
707
 * and summary mode.
708
 */
709
function feeds_ui_mapping_form_multistep_submit($form, &$form_state) {
710
  $trigger = $form_state['triggering_element'];
711

    
712
  switch ($trigger['#op']) {
713
    case 'edit':
714
      $form_state['mapping_settings_edit'] = $trigger['#i'];
715
      break;
716

    
717
    case 'update':
718
      $values = $form_state['values']['config'][$trigger['#i']]['settings'];
719
      $form_state['mapping_settings'][$trigger['#i']] = $values;
720
      unset($form_state['mapping_settings_edit']);
721
      break;
722

    
723
    case 'cancel':
724
      unset($form_state['mapping_settings_edit']);
725
      break;
726
  }
727

    
728
  $form_state['rebuild'] = TRUE;
729
}
730

    
731
/**
732
 * AJAX callback that returns the whole feeds_ui_mapping_form().
733
 */
734
function feeds_ui_mapping_settings_form_callback($form, $form_state) {
735
  return $form;
736
}
737

    
738
/**
739
 * Validation handler for feeds_ui_mapping_form().
740
 */
741
function feeds_ui_mapping_form_validate($form, &$form_state) {
742
  if (!strlen($form_state['values']['source']) xor !strlen($form_state['values']['target'])) {
743

    
744
    // Check triggering_element here so we can react differently for ajax
745
    // submissions.
746
    switch ($form_state['triggering_element']['#name']) {
747

    
748
      // Regular form submission.
749
      case 'op':
750
        if (!strlen($form_state['values']['source'])) {
751
          form_error($form['source'], t('You must select a mapping source.'));
752
        }
753
        else {
754
          form_error($form['target'], t('You must select a mapping target.'));
755
        }
756
        break;
757

    
758
      // Be more relaxed on ajax submission.
759
      default:
760
        form_set_value($form['source'], '', $form_state);
761
        form_set_value($form['target'], '', $form_state);
762
        break;
763
    }
764
  }
765
}
766

    
767
/**
768
 * Submission handler for feeds_ui_mapping_form().
769
 */
770
function feeds_ui_mapping_form_submit($form, &$form_state) {
771
  $importer = feeds_importer($form['#importer']);
772
  $processor = $importer->processor;
773

    
774
  $form_state += array(
775
    'mapping_settings' => array(),
776
    'mapping_settings_edit' => NULL,
777
  );
778

    
779
  // If an item is in edit mode, prepare it for saving.
780
  if ($form_state['mapping_settings_edit'] !== NULL) {
781
    $values = $form_state['values']['config'][$form_state['mapping_settings_edit']]['settings'];
782
    $form_state['mapping_settings'][$form_state['mapping_settings_edit']] = $values;
783
  }
784

    
785
  // We may set some settings to mappings that we remove in the subsequent step,
786
  // that's fine.
787
  $mappings = $form['#mappings'];
788
  foreach ($form_state['mapping_settings'] as $k => $v) {
789
    $mappings[$k] = array(
790
      'source' => $mappings[$k]['source'],
791
      'target' => $mappings[$k]['target'],
792
    ) + $v;
793
  }
794

    
795
  if (!empty($form_state['values']['remove_flags'])) {
796
    $remove_flags = array_keys(array_filter($form_state['values']['remove_flags']));
797

    
798
    foreach ($remove_flags as $k) {
799
      unset($mappings[$k]);
800
      unset($form_state['values']['mapping_weight'][$k]);
801
      drupal_set_message(t('Mapping has been removed.'), 'status', FALSE);
802
    }
803
  }
804

    
805
  // Keep our keys clean.
806
  $mappings = array_values($mappings);
807

    
808
  if (!empty($mappings)) {
809
    array_multisort($form_state['values']['mapping_weight'], $mappings);
810
  }
811

    
812
  $processor->addConfig(array('mappings' => $mappings));
813

    
814
  if (strlen($form_state['values']['source']) && strlen($form_state['values']['target'])) {
815
    try {
816
      $mappings = $processor->getMappings();
817
      $mappings[] = array(
818
        'source' => $form_state['values']['source'],
819
        'target' => $form_state['values']['target'],
820
        'unique' => FALSE,
821
      );
822
      $processor->addConfig(array('mappings' => $mappings));
823
      drupal_set_message(t('Mapping has been added.'));
824
    }
825
    catch (Exception $e) {
826
      drupal_set_message($e->getMessage(), 'error');
827
    }
828
  }
829

    
830
  $importer->save();
831
  drupal_set_message(t('Your changes have been saved.'));
832
}
833

    
834
/**
835
 * Walk the result of FeedsParser::getMappingSources() or
836
 * FeedsProcessor::getMappingTargets() and format them into
837
 * a Form API options array.
838
 */
839
function _feeds_ui_format_options($options, $show_deprecated = FALSE) {
840
  $result = array();
841
  foreach ($options as $k => $v) {
842
    if (!$show_deprecated && is_array($v) && !empty($v['deprecated'])) {
843
      continue;
844
    }
845
    if (is_array($v) && !empty($v['name'])) {
846
      $result[$k] = $v['name'] . ' (' . $k . ')';
847
      if (!empty($v['deprecated'])) {
848
        $result[$k] .= ' - ' . t('DEPRECATED');
849
      }
850
    }
851
    elseif (is_array($v)) {
852
      $result[$k] = $k;
853
    }
854
    else {
855
      $result[$k] = $v;
856
    }
857
  }
858
  asort($result);
859
  return $result;
860
}
861

    
862
/**
863
 * Per mapping settings summary callback. Shows whether a mapping is used as
864
 * unique or not.
865
 */
866
function feeds_ui_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state) {
867
  if (!empty($target['optional_unique'])) {
868
    if ($mapping['unique']) {
869
      return t('Used as <strong>unique</strong>.');
870
    }
871
    else {
872
      return t('Not used as unique.');
873
    }
874
  }
875
}
876

    
877
/**
878
 * Per mapping settings form callback. Lets the user choose if a target is as
879
 * unique or not.
880
 */
881
function feeds_ui_mapping_settings_optional_unique_form($mapping, $target, $form, $form_state) {
882
  $settings_form = array();
883

    
884
  if (!empty($target['optional_unique'])) {
885
    $settings_form['unique'] = array(
886
      '#type' => 'checkbox',
887
      '#title' => t('Unique'),
888
      '#default_value' => !empty($mapping['unique']),
889
    );
890
  }
891

    
892
  return $settings_form;
893
}
894

    
895
/**
896
 * Theme feeds_ui_overview_form().
897
 */
898
function theme_feeds_ui_overview_form($variables) {
899
  $form = $variables['form'];
900
  drupal_add_css(drupal_get_path('module', 'feeds_ui') . '/feeds_ui.css');
901

    
902
  // Iterate through all importers and build a table.
903
  $rows = array();
904
  foreach (array('enabled', 'disabled') as $type) {
905
    if (isset($form[$type])) {
906
      foreach (element_children($form[$type]) as $id) {
907
        $row = array();
908
        foreach (element_children($form[$type][$id]) as $col) {
909
          $row[$col] = array(
910
            'data' => drupal_render($form[$type][$id][$col]),
911
            'class' => array($type),
912
          );
913
        }
914
        $rows[] = array(
915
          'data' => $row,
916
          'class' => array($type),
917
        );
918
      }
919
    }
920
  }
921

    
922
  $output = theme('table', array(
923
    'header' => $form['#header'],
924
    'rows' => $rows,
925
    'attributes' => array('class' => array('feeds-admin-importers')),
926
    'empty' => t('No importers available.'),
927
  ));
928

    
929
  if (!empty($rows)) {
930
    $output .= drupal_render_children($form);
931
  }
932

    
933
  return $output;
934
}
935

    
936
/**
937
 * Theme feeds_ui_edit_page().
938
 */
939
function theme_feeds_ui_edit_page($variables) {
940
  $config_info = $variables['info'];
941
  $active_container = $variables['active'];
942
  drupal_add_css(drupal_get_path('module', 'feeds_ui') . '/feeds_ui.css');
943

    
944
  // Outer wrapper.
945
  $output = '<div class="feeds-settings clearfix">';
946

    
947
  // Build left bar.
948
  $output .= '<div class="left-bar">';
949
  foreach ($config_info as $info) {
950
    $output .= theme('feeds_ui_container', array('container' => $info));
951
  }
952
  $output .= '</div>';
953

    
954
  // Build configuration space.
955
  $output .= '<div class="configuration">';
956
  $output .= '<div class="configuration-squeeze">';
957
  $output .= theme('feeds_ui_container', array('container' => $active_container));
958
  $output .= '</div>';
959
  $output .= '</div>';
960

    
961
  $output .= '</div>'; // ''<div class="feeds-settings">';
962

    
963
  return $output;
964
}
965

    
966
/**
967
 * Render a simple container. A container can have a title, a description and
968
 * one or more actions. Recursive.
969
 *
970
 * @todo Replace with theme_fieldset or a wrapper to theme_fieldset?
971
 *
972
 * @param $variables
973
 *   An array containing an array at 'container'.
974
 *   A 'container' array may contain one or more of the following keys:
975
 *   array(
976
 *     'title' => 'the title',
977
 *     'body' => 'the body of the container, may also be an array of more
978
 *                containers or a renderable array.',
979
 *     'class' => array('the class of the container.'),
980
 *     'id' => 'the id of the container',
981
 *   );
982
 */
983
function theme_feeds_ui_container($variables) {
984
  $container = $variables['container'];
985

    
986
  $class = array_merge(array('feeds-container'), empty($container['class']) ? array('plain') : $container['class']);
987
  $id = empty($container['id']) ? '': ' id="' . $container['id'] . '"';
988
  $output = '<div class="' . implode(' ', $class) . '"' . $id . '>';
989

    
990
  if (isset($container['actions']) && count($container['actions'])) {
991
    $output .= '<ul class="container-actions">';
992
    foreach ($container['actions'] as $action) {
993
      $output .= '<li>' . $action . '</li>';
994
    }
995
    $output .= '</ul>';
996
  }
997

    
998
  if (!empty($container['title'])) {
999
    $output .= '<h4 class="feeds-container-title">';
1000
    $output .= $container['title'];
1001
    $output .= '</h4>';
1002
  }
1003

    
1004
  if (!empty($container['body'])) {
1005
    $output .= '<div class="feeds-container-body">';
1006
    if (is_array($container['body'])) {
1007
      if (isset($container['body']['#type'])) {
1008
        $output .= drupal_render($container['body']);
1009
      }
1010
      else {
1011
        foreach ($container['body'] as $c) {
1012
          $output .= theme('feeds_ui_container', array('container' => $c));
1013
        }
1014
      }
1015
    }
1016
    else {
1017
      $output .= $container['body'];
1018
    }
1019
    $output .= '</div>';
1020
  }
1021

    
1022
  $output .= '</div>';
1023
  return $output;
1024
}
1025

    
1026
/**
1027
 * Theme function for feeds_ui_mapping_form().
1028
 */
1029
function theme_feeds_ui_mapping_form($variables) {
1030
  $form = $variables['form'];
1031

    
1032
  $targets = feeds_importer($form['#importer'])->processor->getMappingTargets();
1033
  $targets = _feeds_ui_format_options($targets, TRUE);
1034

    
1035
  $sources = feeds_importer($form['#importer'])->parser->getMappingSources();
1036
  // Some parsers do not define source options.
1037
  $sources = $sources ? _feeds_ui_format_options($sources, TRUE) : array();
1038

    
1039
  // Build the actual mapping table.
1040
  $header = array(
1041
    t('Source'),
1042
    t('Target'),
1043
    t('Target configuration'),
1044
    '&nbsp;',
1045
    t('Weight'),
1046
  );
1047
  $rows = array();
1048
  if (is_array($form['#mappings'])) {
1049
    foreach ($form['#mappings'] as $i => $mapping) {
1050
      $source = isset($sources[$mapping['source']]) ? check_plain($sources[$mapping['source']]) : check_plain($mapping['source']);
1051
      $target = isset($targets[$mapping['target']]) ? check_plain($targets[$mapping['target']]) : '<em>' . t('Missing') . '</em>';
1052
      // Add indicator to target if target configuration changed.
1053
      if (isset($form['#mapping_settings'][$i])) {
1054
        $target .= '<span class="warning">*</span>';
1055
      }
1056
      $rows[] = array(
1057
        'data' => array(
1058
          $source,
1059
          $target,
1060
          drupal_render($form['config'][$i]),
1061
          drupal_render($form['remove_flags'][$i]),
1062
          drupal_render($form['mapping_weight'][$i]),
1063
        ),
1064
        'class' => array('draggable', 'tabledrag-leaf'),
1065
      );
1066
    }
1067
  }
1068
  if (!count($rows)) {
1069
    $rows[] = array(
1070
      array(
1071
        'colspan' => 5,
1072
        'data' => t('No mappings defined.'),
1073
      ),
1074
    );
1075
  }
1076
  $rows[] = array(
1077
    drupal_render($form['source']),
1078
    drupal_render($form['target']),
1079
    '',
1080
    drupal_render($form['add']),
1081
    '',
1082
  );
1083
  $output = '';
1084
  if (!empty($form['changed'])) {
1085
    // This form element is only available if target configuration changed.
1086
    $output .= drupal_render($form['changed']);
1087
  }
1088
  $output .= '<div class="help feeds-admin-ui">' . drupal_render($form['help']) . '</div>';
1089
  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'feeds-ui-mapping-overview')));
1090

    
1091
  // Build the help table that explains available sources.
1092
  $legend = '';
1093
  $rows = array();
1094
  foreach (element_children($form['legendset']['legend']['sources']) as $k) {
1095
    $rows[] = array(
1096
      check_plain(drupal_render($form['legendset']['legend']['sources'][$k]['name'])),
1097
      check_plain(drupal_render($form['legendset']['legend']['sources'][$k]['description'])),
1098
    );
1099
  }
1100
  if (count($rows)) {
1101
    $legend .= '<h4>' . t('Sources') . '</h4>';
1102
    $legend .= theme('table', array('header' => array(t('Name'), t('Description')), 'rows' => $rows));
1103
  }
1104

    
1105
  // Build the help table that explains available targets.
1106
  $rows = array();
1107
  foreach (element_children($form['legendset']['legend']['targets']) as $k) {
1108
    $rows[] = array(
1109
      check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['name']) . ' (' . $k . ')'),
1110
      check_plain(drupal_render($form['legendset']['legend']['targets'][$k]['description'])),
1111
    );
1112
  }
1113
  $legend .= '<h4>' . t('Targets') . '</h4>';
1114
  $legend .= theme('table', array('header' => array(t('Name'), t('Description')), 'rows' => $rows));
1115

    
1116
  // Stick tables into collapsible fieldset.
1117
  $form['legendset']['legend'] = array(
1118
    '#markup' => '<div>' . $legend . '</div>',
1119
  );
1120

    
1121
  $output .= drupal_render($form['legendset']);
1122
  $output .= drupal_render_children($form);
1123

    
1124
  drupal_add_tabledrag('feeds-ui-mapping-overview', 'order', 'sibling', 'feeds-ui-mapping-weight');
1125
  return $output;
1126
}
1127

    
1128
/**
1129
 * Page callback to import a Feeds importer.
1130
 */
1131
function feeds_ui_importer_import($form, &$form_state) {
1132
  $form['id'] = array(
1133
    '#type' => 'textfield',
1134
    '#title' => t('Importer id'),
1135
    '#description' => t('Enter the id to use for this importer if it is different from the source importer. Leave blank to use the id of the importer.'),
1136
  );
1137
  $form['id_override'] = array(
1138
    '#type' => 'checkbox',
1139
    '#title' => t('Replace an existing importer if one exists with the same id.'),
1140
  );
1141
  $form['bypass_validation'] = array(
1142
    '#type' => 'checkbox',
1143
    '#title' => t('Bypass importer validation'),
1144
    '#description' => t('Bypass the validation of plugins when importing.'),
1145
  );
1146
  $form['importer'] = array(
1147
    '#type' => 'textarea',
1148
    '#rows' => 10,
1149
  );
1150
  $form['actions'] = array('#type' => 'actions');
1151
  $form['actions']['submit'] = array(
1152
    '#type' => 'submit',
1153
    '#value' => t('Import'),
1154
  );
1155
  return $form;
1156
}
1157

    
1158
/**
1159
 * Form validation handler for feeds_ui_importer_import().
1160
 *
1161
 * @see feeds_ui_importer_import_submit()
1162
 */
1163
function feeds_ui_importer_import_validate($form, &$form_state) {
1164
  $form_state['values']['importer'] = trim($form_state['values']['importer']);
1165
  $form_state['values']['id'] = trim($form_state['values']['id']);
1166

    
1167
  if (!empty($form_state['values']['id']) && preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['id'])) {
1168
    form_error($form['id'], t('Feeds importer id must be alphanumeric with underscores only.'));
1169
  }
1170

    
1171
  if (substr($form_state['values']['importer'], 0, 5) == '<?php') {
1172
    $form_state['values']['importer'] = substr($form_state['values']['importer'], 5);
1173
  }
1174

    
1175
  $feeds_importer = NULL;
1176
  ob_start();
1177
  eval($form_state['values']['importer']);
1178
  ob_end_clean();
1179

    
1180
  if (!is_object($feeds_importer)) {
1181
    return form_error($form['importer'], t('Unable to interpret Feeds importer code.'));
1182
  }
1183

    
1184
  if (empty($feeds_importer->api_version) || $feeds_importer->api_version < 1) {
1185
    form_error($form['importer'], t('The importer is not compatible with this version of Feeds.'));
1186
  }
1187
  elseif (version_compare($feeds_importer->api_version, feeds_api_version(), '>')) {
1188
    form_error($form['importer'], t('That importer is created for the version %import_version of Feeds, but you only have version %api_version.', array(
1189
      '%import_version' => $feeds_importer->api_version,
1190
      '%api_version' => feeds_api_version())));
1191
  }
1192

    
1193
  // Change to user-supplied id.
1194
  if ($form_state['values']['id']) {
1195
    $feeds_importer->id = $form_state['values']['id'];
1196
  }
1197

    
1198
  $exists = feeds_ui_importer_machine_name_exists($feeds_importer->id);
1199

    
1200
  if ($exists && !$form_state['values']['id_override']) {
1201
    if (feeds_importer($feeds_importer->id)->export_type != EXPORT_IN_CODE) {
1202
      return form_error($form['id'], t('Feeds importer already exists with that id.'));
1203
    }
1204
  }
1205

    
1206
  if (!$form_state['values']['bypass_validation']) {
1207
    foreach (array('fetcher', 'parser', 'processor') as $type) {
1208
      $plugin = feeds_plugin($feeds_importer->config[$type]['plugin_key'], $feeds_importer->id);
1209
      if (get_class($plugin) == 'FeedsMissingPlugin') {
1210
        form_error($form['importer'], t('The plugin %plugin is unavailable.', array('%plugin' => $feeds_importer->config[$type]['plugin_key'])));
1211
      }
1212
    }
1213
  }
1214

    
1215
  $form_state['importer'] = $feeds_importer;
1216
}
1217

    
1218
/**
1219
 * Form submission handler for feeds_ui_importer_import().
1220
 *
1221
 * @see feeds_ui_importer_import_validate()
1222
 */
1223
function feeds_ui_importer_import_submit($form, &$form_state) {
1224
  $importer = $form_state['importer'];
1225

    
1226
  // Create a copy of the importer to preserve config.
1227
  $save = feeds_importer($importer->id);
1228
  $save->setConfig($importer->config);
1229
  foreach (array('fetcher', 'parser', 'processor') as $type) {
1230
    $save->setPlugin($importer->config[$type]['plugin_key']);
1231
    $save->$type->setConfig($importer->config[$type]['config']);
1232
  }
1233
  $save->save();
1234

    
1235
  drupal_set_message(t('Successfully imported the %id feeds importer.', array('%id' => $importer->id)));
1236
  $form_state['redirect'] = 'admin/structure/feeds';
1237
}