Completed
Push — master ( 08cee9...1ce617 )
by Alexander
01:22
created

RevisionPrinter::_generateSummaryColumn()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 0
cts 14
cp 0
rs 8.8497
c 0
b 0
f 0
cc 6
nc 16
nop 1
crap 42
1
<?php
2
/**
3
 * This file is part of the SVN-Buddy library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/console-helpers/svn-buddy
9
 */
10
11
namespace ConsoleHelpers\SVNBuddy\Repository\RevisionLog;
12
13
14
use ConsoleHelpers\SVNBuddy\Helper\DateHelper;
15
use ConsoleHelpers\SVNBuddy\Helper\OutputHelper;
16
use Symfony\Component\Console\Helper\Table;
17
use Symfony\Component\Console\Helper\TableCell;
18
use Symfony\Component\Console\Helper\TableSeparator;
19
use Symfony\Component\Console\Output\OutputInterface;
20
21
class RevisionPrinter
22
{
23
24
	const COLUMN_FULL_MESSAGE = 1;
25
26
	const COLUMN_DETAILS = 2;
27
28
	const COLUMN_SUMMARY = 3;
29
30
	const COLUMN_REFS = 4;
31
32
	const COLUMN_MERGE_ORACLE = 5;
33
34
	const COLUMN_MERGE_STATUS = 6;
35
36
	/**
37
	 * Date helper.
38
	 *
39
	 * @var DateHelper
40
	 */
41
	private $_dateHelper;
42
43
	/**
44
	 * Output helper.
45
	 *
46
	 * @var OutputHelper
47
	 */
48
	private $_outputHelper;
49
50
	/**
51
	 * Columns.
52
	 *
53
	 * @var array
54
	 */
55
	private $_columns = array();
56
57
	/**
58
	 * Aggregate by bug.
59
	 *
60
	 * @var boolean
61
	 */
62
	private $_aggregateByBug = false;
63
64
	/**
65
	 * Merge conflict regexps.
66
	 *
67
	 * @var array
68
	 */
69
	private $_mergeConflictRegExps = array();
70
71
	/**
72
	 * Log message limit.
73
	 *
74
	 * @var integer
75
	 */
76
	private $_logMessageLimit = 68;
77
78
	/**
79
	 * Current revision (e.g. in a working copy).
80
	 *
81
	 * @var integer|null
82
	 */
83
	private $_currentRevision;
84
85
	/**
86
	 * Creates instance of revision printer.
87
	 *
88
	 * @param DateHelper   $date_helper   Date helper.
89
	 * @param OutputHelper $output_helper Output helper.
90
	 */
91 1
	public function __construct(DateHelper $date_helper, OutputHelper $output_helper)
92
	{
93 1
		$this->_dateHelper = $date_helper;
94 1
		$this->_outputHelper = $output_helper;
95
96 1
		$this->_resetState();
97 1
	}
98
99
	/**
100
	 * Resets state.
101
	 *
102
	 * @return void
103
	 */
104 1
	private function _resetState()
105
	{
106 1
		$this->_columns = array();
107 1
		$this->_mergeConflictRegExps = array();
108 1
		$this->_logMessageLimit = 68;
109 1
		$this->_aggregateByBug = false;
110 1
	}
111
112
	/**
113
	 * Adds column to the output.
114
	 *
115
	 * @param integer $column Column.
116
	 *
117
	 * @return self
118
	 */
119
	public function withColumn($column)
120
	{
121
		$this->_columns[] = $column;
122
123
		return $this;
124
	}
125
126
	/**
127
	 * Sets aggregate by bug.
128
	 *
129
	 * @param boolean $aggregate_by_bug Aggregate by bug.
130
	 *
131
	 * @return void
132
	 */
133
	public function setAggregateByBug($aggregate_by_bug)
134
	{
135
		$this->_aggregateByBug = $aggregate_by_bug;
136
	}
137
138
	/**
139
	 * Sets merge conflict regexps.
140
	 *
141
	 * @param array $merge_conflict_regexps Merge conflict regexps.
142
	 *
143
	 * @return void
144
	 */
145
	public function setMergeConflictRegExps(array $merge_conflict_regexps)
146
	{
147
		$this->_mergeConflictRegExps = $merge_conflict_regexps;
148
	}
149
150
	/**
151
	 * Sets log message limit.
152
	 *
153
	 * @param integer $log_message_limit Log message limit.
154
	 *
155
	 * @return void
156
	 */
157
	public function setLogMessageLimit($log_message_limit)
158
	{
159
		$this->_logMessageLimit = $log_message_limit;
160
	}
161
162
	/**
163
	 * Sets current revision.
164
	 *
165
	 * @param integer $revision Revision.
166
	 *
167
	 * @return void
168
	 */
169
	public function setCurrentRevision($revision)
170
	{
171
		$this->_currentRevision = $revision;
172
	}
173
174
	/**
175
	 * Prints revisions.
176
	 *
177
	 * @param RevisionLog     $revision_log Revision log.
178
	 * @param array           $revisions    Revisions.
179
	 * @param OutputInterface $output       Output.
180
	 *
181
	 * @return void
182
	 */
183
	public function printRevisions(RevisionLog $revision_log, array $revisions, OutputInterface $output)
184
	{
185
		$table = new Table($output);
186
		$headers = array('Revision', 'Author', 'Date', 'Bug-ID', 'Log Message');
187
188
		$with_full_message = in_array(self::COLUMN_FULL_MESSAGE, $this->_columns);
189
		$with_details = in_array(self::COLUMN_DETAILS, $this->_columns);
190
191
		// Add "Summary" header.
192
		$with_summary = in_array(self::COLUMN_SUMMARY, $this->_columns);
193
194
		if ( $with_summary ) {
195
			$headers[] = 'Summary';
196
		}
197
198
		// Add "Refs" header.
199
		$with_refs = in_array(self::COLUMN_REFS, $this->_columns);
200
201
		if ( $with_refs ) {
202
			$headers[] = 'Refs';
203
		}
204
205
		$with_merge_oracle = in_array(self::COLUMN_MERGE_ORACLE, $this->_columns);
206
207
		// Add "M.O." header.
208
		if ( $with_merge_oracle ) {
209
			$headers[] = 'M.O.';
210
		}
211
212
		// Add "Merged Via" header.
213
		$with_merge_status = in_array(self::COLUMN_MERGE_STATUS, $this->_columns);
214
215
		if ( $with_merge_status ) {
216
			$headers[] = 'Merged Via';
217
		}
218
219
		$table->setHeaders($headers);
220
221
		$prev_bugs = null;
222
		$last_color = 'yellow';
223
224
		if ( $this->_aggregateByBug ) {
225
			$aggregated_revisions = $this->aggregateRevisionsByBug($revisions, $revision_log);
226
			$revisions = array_keys($aggregated_revisions);
227
		}
228
229
		$last_revision = end($revisions);
230
231
		$project_path = $revision_log->getProjectPath();
232
233
		$bugs_per_row = $with_details ? 1 : 3;
234
235
		$revisions_data = $revision_log->getRevisionsData('summary', $revisions);
236
		$revisions_paths = $revision_log->getRevisionsData('paths', $revisions);
237
		$revisions_bugs = $revision_log->getRevisionsData('bugs', $revisions);
238
		$revisions_refs = $revision_log->getRevisionsData('refs', $revisions);
239
240
		if ( $with_merge_status ) {
241
			$revisions_merged_via = $revision_log->getRevisionsData('merges', $revisions);
242
			$revisions_merged_via_refs = $revision_log->getRevisionsData(
243
				'refs',
244
				call_user_func_array('array_merge', $revisions_merged_via)
245
			);
246
		}
247
248
		$first_revision = reset($revisions);
249
250
		foreach ( $revisions as $revision ) {
251
			$revision_data = $revisions_data[$revision];
252
253
			$new_bugs = $revisions_bugs[$revision];
254
255
			if ( isset($prev_bugs) && $new_bugs !== $prev_bugs ) {
256
				$last_color = $last_color === 'yellow' ? 'magenta' : 'yellow';
257
			}
258
259
			$row = array(
260
				$this->_aggregateByBug ? $aggregated_revisions[$revision] . ' cmts' : $revision,
0 ignored issues
show
Bug introduced by
The variable $aggregated_revisions does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
261
				$revision_data['author'],
262
				$this->_dateHelper->getAgoTime($revision_data['date']),
263
				$this->_outputHelper->formatArray($new_bugs, $bugs_per_row, $last_color),
264
				$this->_generateLogMessageColumn($with_full_message || $with_details, $revision_data),
265
			);
266
267
			$revision_paths = $revisions_paths[$revision];
268
269
			// Add "Summary" column.
270
			if ( $with_summary ) {
271
				$row[] = $this->_generateSummaryColumn($revision_paths);
272
			}
273
274
			// Add "Refs" column.
275
			if ( $with_refs ) {
276
				$row[] = $this->_outputHelper->formatArray(
277
					$revisions_refs[$revision],
278
					1
279
				);
280
			}
281
282
			// Add "M.O." column.
283
			if ( $with_merge_oracle ) {
284
				$merge_conflict_prediction = $this->_getMergeConflictPrediction($revision_paths);
285
				$row[] = $merge_conflict_prediction ? '<error>' . count($merge_conflict_prediction) . '</error>' : '';
286
			}
287
			else {
288
				$merge_conflict_prediction = array();
289
			}
290
291
			// Add "Merged Via" column.
292
			if ( $with_merge_status ) {
293
				$row[] = $this->_generateMergedViaColumn($revisions_merged_via[$revision], $revisions_merged_via_refs);
0 ignored issues
show
Bug introduced by
The variable $revisions_merged_via does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $revisions_merged_via_refs does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
294
			}
295
296
			if ( $revision === $this->_currentRevision ) {
297
				foreach ( $row as $index => $cell ) {
298
					$row[$index] = $this->applyStyle($cell, 'fg=white;options=bold');
299
				}
300
			}
301
302
			if ( $with_full_message && $revision !== $first_revision ) {
303
				$table->addRow(new TableSeparator());
304
			}
305
306
			$table->addRow($row);
307
308
			if ( $with_details ) {
309
				$details = $this->_generateDetailsRowContent(
310
					$revision,
311
					$revisions_refs,
312
					$revision_paths,
313
					$merge_conflict_prediction,
314
					$project_path
315
				);
316
317
				$table->addRow(new TableSeparator());
318
				$table->addRow(array(
319
					new TableCell($details, array('colspan' => count($headers))),
320
				));
321
322
				if ( $revision != $last_revision ) {
323
					$table->addRow(new TableSeparator());
324
				}
325
			}
326
327
			$prev_bugs = $new_bugs;
328
		}
329
330
		$table->render();
331
332
		$this->_resetState();
333
	}
334
335
	/**
336
	 * Aggregates revisions by bugs.
337
	 *
338
	 * @param array       $revisions    Revisions.
339
	 * @param RevisionLog $revision_log Revision Log.
340
	 *
341
	 * @return array
342
	 */
343
	protected function aggregateRevisionsByBug(array $revisions, RevisionLog $revision_log)
344
	{
345
		$bugs_revisions = array(
346
			'unknown' => array(),
347
		);
348
349
		$revisions_bugs = $revision_log->getRevisionsData('bugs', $revisions);
350
351
		foreach ( \array_reverse($revisions) as $revision ) {
352
			$revision_bugs = $revisions_bugs[$revision];
353
354
			if ( !$revision_bugs ) {
355
				$bugs_revisions['unknown'][] = $revision;
356
				continue;
357
			}
358
359
			foreach ( $revision_bugs as $revision_bug ) {
360
				if ( !isset($bugs_revisions[$revision_bug]) ) {
361
					$bugs_revisions[$revision_bug] = array();
362
				}
363
364
				$bugs_revisions[$revision_bug][] = $revision;
365
			}
366
		}
367
368
		$bugs_revisions = \array_reverse($bugs_revisions, true);
369
370
		$ret = array();
371
372
		foreach ( $bugs_revisions as $bug => $bug_revisions ) {
373
			$ret[reset($bug_revisions)] = count($bug_revisions);
374
		}
375
376
		return $ret;
377
	}
378
379
	/**
380
	 * Applies a style to the text.
381
	 *
382
	 * @param string $text  Text.
383
	 * @param string $style Style.
384
	 *
385
	 * @return string
386
	 */
387
	protected function applyStyle($text, $style)
388
	{
389
		if ( strpos($text, PHP_EOL) === false ) {
390
			return '<' . $style . '>' . $text . '</>';
391
		}
392
393
		$lines = explode(PHP_EOL, $text);
394
395
		return '<' . $style . '>' . implode('</>' . PHP_EOL . '<' . $style . '>', $lines) . '</>';
396
	}
397
398
	/**
399
	 * Returns log message.
400
	 *
401
	 * @param boolean $with_full_message Show commit message without truncation.
402
	 * @param array   $revision_data     Revision data.
403
	 *
404
	 * @return string
405
	 */
406
	private function _generateLogMessageColumn($with_full_message, array $revision_data)
407
	{
408
		$commit_message = trim($revision_data['msg']);
409
410
		if ( $with_full_message ) {
411
			// When details requested don't transform commit message except for word wrapping.
412
			// FIXME: Not UTF-8 safe solution.
413
			$log_message = wordwrap($commit_message, $this->_logMessageLimit);
414
415
			return $log_message;
416
		}
417
		else {
418
			// When details not requested only operate on first line of commit message.
419
			list($log_message,) = explode(PHP_EOL, $commit_message);
420
			$log_message = preg_replace('/(^|\s+)\[fixes:.*?\]/s', "$1\xE2\x9C\x94", $log_message);
421
422
			if ( strpos($commit_message, PHP_EOL) !== false
423
				|| mb_strlen($log_message) > $this->_logMessageLimit
424
			) {
425
				$log_message = mb_substr($log_message, 0, $this->_logMessageLimit - 3) . '...';
426
427
				return $log_message;
428
			}
429
430
			return $log_message;
431
		}
432
	}
433
434
	/**
435
	 * Generates change summary for a revision.
436
	 *
437
	 * @param array $revision_paths Revision paths.
438
	 *
439
	 * @return string
440
	 */
441
	private function _generateSummaryColumn(array $revision_paths)
442
	{
443
		$summary = array('added' => 0, 'changed' => 0, 'removed' => 0);
444
445
		foreach ( $revision_paths as $path_data ) {
446
			$path_action = $path_data['action'];
447
448
			if ( $path_action === 'A' ) {
449
				$summary['added']++;
450
			}
451
			elseif ( $path_action === 'D' ) {
452
				$summary['removed']++;
453
			}
454
			else {
455
				$summary['changed']++;
456
			}
457
		}
458
459
		if ( $summary['added'] ) {
460
			$summary['added'] = '<fg=green>+' . $summary['added'] . '</>';
461
		}
462
463
		if ( $summary['removed'] ) {
464
			$summary['removed'] = '<fg=red>-' . $summary['removed'] . '</>';
465
		}
466
467
		return implode(' ', array_filter($summary));
468
	}
469
470
	/**
471
	 * Returns merge conflict path predictions.
472
	 *
473
	 * @param array $revision_paths Revision paths.
474
	 *
475
	 * @return array
476
	 */
477
	private function _getMergeConflictPrediction(array $revision_paths)
478
	{
479
		if ( !$this->_mergeConflictRegExps ) {
480
			return array();
481
		}
482
483
		$conflict_paths = array();
484
485
		foreach ( $revision_paths as $revision_path ) {
486
			foreach ( $this->_mergeConflictRegExps as $merge_conflict_regexp ) {
487
				if ( preg_match($merge_conflict_regexp, $revision_path['path']) ) {
488
					$conflict_paths[] = $revision_path['path'];
489
				}
490
			}
491
		}
492
493
		return $conflict_paths;
494
	}
495
496
	/**
497
	 * Generates content for "Merged Via" cell content.
498
	 *
499
	 * @param array $merged_via                Merged Via.
500
	 * @param array $revisions_merged_via_refs Merged Via Refs.
501
	 *
502
	 * @return string
503
	 */
504
	private function _generateMergedViaColumn(array $merged_via, array $revisions_merged_via_refs)
505
	{
506
		if ( !$merged_via ) {
507
			return '';
508
		}
509
510
		$merged_via_enhanced = array();
511
512
		foreach ( $merged_via as $merged_via_revision ) {
513
			$merged_via_revision_refs = $revisions_merged_via_refs[$merged_via_revision];
514
515
			if ( $merged_via_revision_refs ) {
516
				$merged_via_enhanced[] = $merged_via_revision . ' (' . implode(',', $merged_via_revision_refs) . ')';
517
			}
518
			else {
519
				$merged_via_enhanced[] = $merged_via_revision;
520
			}
521
		}
522
523
		return $this->_outputHelper->formatArray($merged_via_enhanced, 1);
524
	}
525
526
	/**
527
	 * Generates details row content.
528
	 *
529
	 * @param integer $revision                  Revision.
530
	 * @param array   $revisions_refs            Refs.
531
	 * @param array   $revision_paths            Revision paths.
532
	 * @param array   $merge_conflict_prediction Merge conflict prediction.
533
	 * @param string  $project_path              Project path.
534
	 *
535
	 * @return string
536
	 */
537
	private function _generateDetailsRowContent(
538
		$revision,
539
		array $revisions_refs,
540
		array $revision_paths,
541
		array $merge_conflict_prediction,
542
		$project_path
543
	) {
544
		$details = '<fg=white;options=bold>Changed Paths:</>';
545
		$path_cut_off_regexp = $this->getPathCutOffRegExp($project_path, $revisions_refs[$revision]);
546
547
		foreach ( $revision_paths as $path_data ) {
548
			$path_action = $path_data['action'];
549
			$relative_path = $this->_getRelativeLogPath($path_data, 'path', $path_cut_off_regexp);
550
551
			$details .= PHP_EOL . ' * ';
552
553
			if ( $path_action === 'A' ) {
554
				$color_format = 'fg=green';
555
			}
556
			elseif ( $path_action === 'D' ) {
557
				$color_format = 'fg=red';
558
			}
559
			else {
560
				$color_format = in_array($path_data['path'], $merge_conflict_prediction) ? 'error' : '';
561
			}
562
563
			$to_colorize = array($path_action . '    ' . $relative_path);
564
565
			if ( isset($path_data['copyfrom-path']) ) {
566
				// TODO: When copy happened from different ref/project, then relative path = absolute path.
567
				$copy_from_rev = $path_data['copyfrom-rev'];
568
				$copy_from_path = $this->_getRelativeLogPath($path_data, 'copyfrom-path', $path_cut_off_regexp);
569
				$to_colorize[] = '        (from ' . $copy_from_path . ':' . $copy_from_rev . ')';
570
			}
571
572
			if ( $color_format ) {
573
				$details .= '<' . $color_format . '>';
574
				$details .= implode('</>' . PHP_EOL . '<' . $color_format . '>', $to_colorize);
575
				$details .= '</>';
576
			}
577
			else {
578
				$details .= implode(PHP_EOL, $to_colorize);
579
			}
580
		}
581
582
		return $details;
583
	}
584
585
	/**
586
	 * Returns path cut off regexp.
587
	 *
588
	 * @param string $project_path Project path.
589
	 * @param array  $refs         Refs.
590
	 *
591
	 * @return string
592
	 */
593
	protected function getPathCutOffRegExp($project_path, array $refs)
0 ignored issues
show
Unused Code introduced by
The parameter $refs is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
594
	{
595
		$ret = array();
596
597
		// Remove ref from path only for single-ref revision.
598
		/*if ( count($refs) === 1 ) {
599
			$ret[] = $project_path . reset($refs) . '/';
600
		}*/
601
602
		// Always remove project path.
603
		$ret[] = $project_path;
604
605
		return '#^(' . implode('|', array_map('preg_quote', $ret)) . ')#';
606
	}
607
608
	/**
609
	 * Returns relative path to "svn log" returned path.
610
	 *
611
	 * @param array  $path_data           Path data.
612
	 * @param string $path_key            Path key.
613
	 * @param string $path_cut_off_regexp Path cut off regexp.
614
	 *
615
	 * @return string
616
	 */
617
	private function _getRelativeLogPath(array $path_data, $path_key, $path_cut_off_regexp)
618
	{
619
		$ret = preg_replace($path_cut_off_regexp, '', $path_data[$path_key], 1);
620
621
		if ( $ret === '' ) {
622
			$ret = '.';
623
		}
624
625
		return $ret;
626
	}
627
628
}
629