--- /dev/null
+<?php
+namespace Consolidation\OutputFormatters\Transformations;
+
+use Symfony\Component\Finder\Glob;
+use Consolidation\OutputFormatters\Exception\UnknownFieldException;
+
+/**
+ * Reorder the field labels based on the user-selected fields
+ * to display.
+ */
+class ReorderFields
+{
+ /**
+ * Given a simple list of user-supplied field keys or field labels,
+ * return a reordered version of the field labels matching the
+ * user selection.
+ *
+ * @param string|array $fields The user-selected fields
+ * @param array $fieldLabels An associative array mapping the field
+ * key to the field label
+ * @param array $data The data that will be rendered.
+ *
+ * @return array
+ */
+ public function reorder($fields, $fieldLabels, $data)
+ {
+ $firstRow = reset($data);
+ if (!$firstRow) {
+ $firstRow = $fieldLabels;
+ }
+ if (empty($fieldLabels) && !empty($data)) {
+ $fieldLabels = array_combine(array_keys($firstRow), array_map('ucfirst', array_keys($firstRow)));
+ }
+ $fields = $this->getSelectedFieldKeys($fields, $fieldLabels);
+ if (empty($fields)) {
+ return array_intersect_key($fieldLabels, $firstRow);
+ }
+ return $this->reorderFieldLabels($fields, $fieldLabels, $data);
+ }
+
+ protected function reorderFieldLabels($fields, $fieldLabels, $data)
+ {
+ $result = [];
+ $firstRow = reset($data);
+ if (!$firstRow) {
+ $firstRow = $fieldLabels;
+ }
+ foreach ($fields as $field) {
+ if (array_key_exists($field, $firstRow)) {
+ if (array_key_exists($field, $fieldLabels)) {
+ $result[$field] = $fieldLabels[$field];
+ }
+ }
+ }
+ return $result;
+ }
+
+ protected function getSelectedFieldKeys($fields, $fieldLabels)
+ {
+ if (is_string($fields)) {
+ $fields = explode(',', $fields);
+ }
+ $selectedFields = [];
+ foreach ($fields as $field) {
+ $matchedFields = $this->matchFieldInLabelMap($field, $fieldLabels);
+ if (empty($matchedFields)) {
+ throw new UnknownFieldException($field);
+ }
+ $selectedFields = array_merge($selectedFields, $matchedFields);
+ }
+ return $selectedFields;
+ }
+
+ protected function matchFieldInLabelMap($field, $fieldLabels)
+ {
+ $fieldRegex = $this->convertToRegex($field);
+ return
+ array_filter(
+ array_keys($fieldLabels),
+ function ($key) use ($fieldRegex, $fieldLabels) {
+ $value = $fieldLabels[$key];
+ return preg_match($fieldRegex, $value) || preg_match($fieldRegex, $key);
+ }
+ );
+ }
+
+ /**
+ * Convert the provided string into a regex suitable for use in
+ * preg_match.
+ *
+ * Matching occurs in the same way as the Symfony Finder component:
+ * http://symfony.com/doc/current/components/finder.html#file-name
+ */
+ protected function convertToRegex($str)
+ {
+ return $this->isRegex($str) ? $str : Glob::toRegex($str);
+ }
+
+ /**
+ * Checks whether the string is a regex. This function is copied from
+ * MultiplePcreFilterIterator in the Symfony Finder component.
+ *
+ * @param string $str
+ *
+ * @return bool Whether the given string is a regex
+ */
+ protected function isRegex($str)
+ {
+ if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
+ $start = substr($m[1], 0, 1);
+ $end = substr($m[1], -1);
+
+ if ($start === $end) {
+ return !preg_match('/[*?[:alnum:] \\\\]/', $start);
+ }
+
+ foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) {
+ if ($start === $delimiters[0] && $end === $delimiters[1]) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}