diff --git a/core/includes/config.inc b/core/includes/config.inc index 3e22534..c1b7723 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -4,6 +4,7 @@ use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\NullStorage; use Drupal\Core\Config\StorageInterface; +use Symfony\Component\Yaml\Dumper; /** * @file @@ -295,3 +296,45 @@ function config_get_module_config_entities($module) { return ($entity_info['module'] == $module) && is_subclass_of($entity_info['class'], 'Drupal\Core\Config\Entity\ConfigEntityInterface'); }); } + +/** + * Return a formatted diff of a named config between two storages. + * + * @param Drupal\Core\Config\StorageInterface $source_storage + * The storage to diff configuration from. + * @param Drupal\Core\Config\StorageInterface $target_storage + * The storage to diff configuration to. + * @param string $name + * The name of the configuration object to diff. + * + * @return string + * A formatted string showing the difference between the two storages. + * + * @todo Make renderer injectable + */ +function config_diff(StorageInterface $source_storage, StorageInterface $target_storage, $name) { + require_once DRUPAL_ROOT . '/core/lib/Drupal/Component/Diff/DiffEngine.php'; + + // The output should show configuration object differences formatted as YAML. + // But the configuration is not necessarily stored in files. Therefore, they + // need to be read and parsed, and lastly, dumped into YAML strings. + $dumper = new Dumper(); + $dumper->setIndentation(2); + + $source_data = explode("\n", $dumper->dump($source_storage->read($name), PHP_INT_MAX)); + $target_data = explode("\n", $dumper->dump($target_storage->read($name), PHP_INT_MAX)); + + $diff = new Diff($source_data, $target_data); + $formatter = new DrupalDiffFormatter(); + $formatter->show_header = FALSE; + + $variables = array( + 'header' => array( + array('data' => t('Old'), 'colspan' => '2'), + array('data' => t('New'), 'colspan' => '2'), + ), + 'rows' => $formatter->format($diff), + ); + + return theme('table', $variables); +} diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 3ce399b..e4d3f48 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -3186,5 +3186,36 @@ function drupal_common_theme() { 'container' => array( 'render element' => 'element', ), + 'diff_header_line' => array( + 'variables' => array('lineno' => NULL), + ), + 'diff_content_line' => array( + 'variables' => array('line' => NULL), + ), + 'diff_empty_line' => array( + 'variables' => array('line' => NULL), + ), + ); } + +/** + * Theme function for a header line in the diff. + */ +function theme_diff_header_line($vars) { + return '' . t('Line %lineno', array('%lineno' => $vars['lineno'])) . ''; +} + +/** + * Theme function for a content line in the diff. + */ +function theme_diff_content_line($vars) { + return '
' . $vars['line'] . '
'; +} + +/** + * Theme function for an empty line in the diff. + */ +function theme_diff_empty_line($vars) { + return $vars['line']; +} diff --git a/core/lib/Drupal/Component/Diff/DiffEngine.php b/core/lib/Drupal/Component/Diff/DiffEngine.php index 1236610..f426b96 100644 --- a/core/lib/Drupal/Component/Diff/DiffEngine.php +++ b/core/lib/Drupal/Component/Diff/DiffEngine.php @@ -1111,11 +1111,11 @@ function _end_diff() { function _block_header($xbeg, $xlen, $ybeg, $ylen) { return array( array( - 'data' => theme('diff_header_line', array('lineno' => $xbeg + $this->line_stats['offset']['x'])), + 'data' => $xbeg + $this->line_stats['offset']['x'], 'colspan' => 2, ), array( - 'data' => theme('diff_header_line', array('lineno' => $ybeg + $this->line_stats['offset']['y'])), + 'data' => $ybeg + $this->line_stats['offset']['y'], 'colspan' => 2, ) ); @@ -1143,7 +1143,7 @@ function addedLine($line) { 'class' => 'diff-marker', ), array( - 'data' => theme('diff_content_line', array('line' => $line)), + 'data' => $line, 'class' => 'diff-context diff-addedline', ) ); @@ -1159,7 +1159,7 @@ function deletedLine($line) { 'class' => 'diff-marker', ), array( - 'data' => theme('diff_content_line', array('line' => $line)), + 'data' => $line, 'class' => 'diff-context diff-deletedline', ) ); @@ -1172,7 +1172,7 @@ function contextLine($line) { return array( ' ', array( - 'data' => theme('diff_content_line', array('line' => $line)), + 'data' => $line, 'class' => 'diff-context', ) ); @@ -1181,7 +1181,7 @@ function contextLine($line) { function emptyLine() { return array( ' ', - theme('diff_empty_line', array('line' => ' ')), + ' ', ); } diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc index c15e02a..8f88ae4 100644 --- a/core/modules/config/config.admin.inc +++ b/core/modules/config/config.admin.inc @@ -40,6 +40,10 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa if (empty($config_files)) { continue; } + + // Add the CSS for the inline diff. + $form['#attached']['css'][] = drupal_get_path('module', 'system') . '/system.diff.css'; + // @todo A table caption would be more appropriate, but does not have the // visual importance of a heading. $form[$config_change_type]['heading'] = array( @@ -63,8 +67,37 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa '#theme' => 'table', '#header' => array('Name'), ); + foreach ($config_files as $config_file) { $form[$config_change_type]['list']['#rows'][] = array($config_file); + + switch ($config_change_type) { + case 'change': + $fieldset_title = t('View changes of @config_file', array('@config_file' => $config_file)); + break; + + case 'create': + $fieldset_title = t('View added file @config_file', array('@config_file' => $config_file)); + break; + + case 'delete': + $fieldset_title = t('View removed file @config_file', array('@config_file' => $config_file)); + break; + } + + // Show the additions/changes/deletions in a nice collapsible fieldset. + $fieldset = array( + '#type' => 'fieldset', + '#title' => $fieldset_title, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $fieldset['diff'] = array( + '#markup' => config_diff($target_storage, $source_storage, $config_file), + ); + $form[$config_change_type]['list']['#rows'][] = array( + array('data' => $fieldset), + ); } } } diff --git a/core/modules/system/system.diff.css b/core/modules/system/system.diff.css new file mode 100644 index 0000000..8187561 --- /dev/null +++ b/core/modules/system/system.diff.css @@ -0,0 +1,86 @@ + +html.js .diff-js-hidden { display: none; } + +/** + * Inline diff metadata + */ +.diff-inline-metadata { + padding:4px; + border:1px solid #ddd; + background:#fff; + margin:0px 0px 10px; +} + +.diff-inline-legend { font-size:11px; } + +.diff-inline-legend span, +.diff-inline-legend label { margin-right:5px; } + +/** + * Inline diff markup + */ +span.diff-deleted { color:#ccc; } +span.diff-deleted img { border: solid 2px #ccc; } +span.diff-changed { background:#ffb; } +span.diff-changed img { border:solid 2px #ffb; } +span.diff-added { background:#cfc; } +span.diff-added img { border: solid 2px #cfc; } + +/** + * Traditional split diff theming + */ +table.diff { + border-spacing: 4px; + margin-bottom: 20px; + table-layout: fixed; + width: 100%; +} +table.diff tr.even, table.diff tr.odd { + background-color: inherit; + border: none; +} +td.diff-prevlink { + text-align: left; +} +td.diff-nextlink { + text-align: right; +} +td.diff-section-title, div.diff-section-title { + background-color: #f0f0ff; + font-size: 0.83em; + font-weight: bold; + padding: 0.1em 1em; +} +td.diff-deletedline { + background-color: #ffa; + width: 50%; +} +td.diff-addedline { + background-color: #afa; + width: 50%; +} +td.diff-context { + background-color: #fafafa; +} +span.diffchange { + color: #f00; + font-weight: bold; +} + +table.diff col.diff-marker { + width: 1.4em; +} +table.diff col.diff-content { + width: 50%; +} +table.diff th { + padding-right: inherit; +} +table.diff td div { + overflow: auto; + padding: 0.1ex 0.5em; + word-wrap: break-word; +} +table.diff td { + padding: 0.1ex 0.4em; +}