Passed
Push — master ( 5cd64e...f25568 )
by MusikAnimal
10:13 queued 06:26
created

AppExtension::dateFormatStd()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 0
cts 4
cp 0
crap 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the AppExtension class.
4
 */
5
6
namespace AppBundle\Twig;
7
8
use Xtools\ProjectRepository;
9
use Xtools\User;
10
use NumberFormatter;
11
use IntlDateFormatter;
12
use DateTime;
13
14
/**
15
 * Twig functions and filters for XTools.
16
 */
17
class AppExtension extends Extension
18
{
19
    /** @var NumberFormatter Instance of NumberFormatter class, used in localizing numbers. */
20
    protected $numFormatter;
21
22
    /** @var IntlDateFormatter Instance of IntlDateFormatter class, used in localizing dates. */
23
    protected $dateFormatter;
24
25
    /** @var float Duration of the current HTTP request in seconds. */
26
    protected $requestTime;
27
28
    /**
29
     * Get the name of this extension.
30
     * @return string
31
     */
32 13
    public function getName()
33
    {
34 13
        return 'app_extension';
35
    }
36
37
    /*********************************** FUNCTIONS ***********************************/
38
39
    /**
40
     * Get all functions that this class provides.
41
     * @return array
42
     */
43 12
    public function getFunctions()
44
    {
45 12
        $options = ['is_safe' => ['html']];
46
        return [
47 12
            new \Twig_SimpleFunction('request_time', [ $this, 'requestTime' ], $options),
48 12
            new \Twig_SimpleFunction('memory_usage', [ $this, 'requestMemory' ], $options),
49 12
            new \Twig_SimpleFunction('year', [ $this, 'generateYear' ], $options),
50 12
            new \Twig_SimpleFunction('msgPrintExists', [ $this, 'intuitionMessagePrintExists' ], $options),
51 12
            new \Twig_SimpleFunction('msgExists', [ $this, 'intuitionMessageExists' ], $options),
52 12
            new \Twig_SimpleFunction('msg', [ $this, 'intuitionMessage' ], $options),
53 12
            new \Twig_SimpleFunction('lang', [ $this, 'getLang' ], $options),
54 12
            new \Twig_SimpleFunction('langName', [ $this, 'getLangName' ], $options),
55 12
            new \Twig_SimpleFunction('allLangs', [ $this, 'getAllLangs' ]),
56 12
            new \Twig_SimpleFunction('isRTL', [ $this, 'intuitionIsRTL' ]),
57 12
            new \Twig_SimpleFunction('isRTLLang', [ $this, 'intuitionIsRTLLang' ]),
58 12
            new \Twig_SimpleFunction('shortHash', [ $this, 'gitShortHash' ]),
59 12
            new \Twig_SimpleFunction('hash', [ $this, 'gitHash' ]),
60 12
            new \Twig_SimpleFunction('releaseDate', [ $this, 'gitDate' ]),
61 12
            new \Twig_SimpleFunction('enabled', [ $this, 'tabEnabled' ]),
62 12
            new \Twig_SimpleFunction('tools', [ $this, 'allTools' ]),
63 12
            new \Twig_SimpleFunction('color', [ $this, 'getColorList' ]),
64 12
            new \Twig_SimpleFunction('chartColor', [ $this, 'chartColor' ]),
65 12
            new \Twig_SimpleFunction('isSingleWiki', [ $this, 'isSingleWiki' ]),
66 12
            new \Twig_SimpleFunction('getReplagThreshold', [ $this, 'getReplagThreshold' ]),
67 12
            new \Twig_SimpleFunction('loadStylesheetsFromCDN', [ $this, 'loadStylesheetsFromCDN' ]),
68 12
            new \Twig_SimpleFunction('isWMFLabs', [ $this, 'isWMFLabs' ]),
69 12
            new \Twig_SimpleFunction('replag', [ $this, 'replag' ]),
70 12
            new \Twig_SimpleFunction('link', [ $this, 'link' ]),
71 12
            new \Twig_SimpleFunction('quote', [ $this, 'quote' ]),
72 12
            new \Twig_SimpleFunction('bugReportURL', [ $this, 'bugReportURL' ]),
73 12
            new \Twig_SimpleFunction('logged_in_user', [$this, 'functionLoggedInUser']),
74 12
            new \Twig_SimpleFunction('isUserAnon', [$this, 'isUserAnon']),
75 12
            new \Twig_SimpleFunction('nsName', [$this, 'nsName']),
76 12
            new \Twig_SimpleFunction('formatDuration', [$this, 'formatDuration']),
77 12
            new \Twig_SimpleFunction('numberFormat', [$this, 'numberFormat']),
78
        ];
79
    }
80
81
    /**
82
     * Get the duration of the current HTTP request in seconds.
83
     * @return double
84
     * Untestable since there is no request stack in the tests.
85
     * @codeCoverageIgnore
86
     */
87
    public function requestTime()
88
    {
89
        if (!isset($this->requestTime)) {
90
            $this->requestTime = microtime(true) - $this->getCurrentRequest()->server->get('REQUEST_TIME_FLOAT');
91
        }
92
93
        return $this->requestTime;
94
    }
95
96
    /**
97
     * Get the formatted real memory usage.
98
     * @return float
99
     */
100 12
    public function requestMemory()
101
    {
102 12
        $mem = memory_get_usage(false);
103 12
        $div = pow(1024, 2);
104 12
        return $mem / $div;
105
    }
106
107
    /**
108
     * Get the current year.
109
     * @return string
110
     */
111
    public function generateYear()
112
    {
113
        return date('Y');
114
    }
115
116
    /**
117
     * See if a given i18n message exists.
118
     * @TODO: refactor all intuition stuff so it can be used anywhere
119
     * @param string $message The message.
120
     * @param array $vars
121
     * @return bool
122
     */
123
    public function intuitionMessageExists($message = '', $vars = [])
124
    {
125
        return $this->getIntuition()->msgExists($message, array_merge(
126
            [
127
                'domain' => 'xtools'
128
            ],
129
            [
130
                'variables' => $vars
131
            ]
132
        ));
133
    }
134
135
    /**
136
     * Get an i18n message if it exists, otherwise just get the message key.
137
     * @param string $message
138
     * @param array $vars
139
     * @return mixed|null|string
140
     */
141
    public function intuitionMessagePrintExists($message = "", $vars = [])
142
    {
143
        if (is_array($message)) {
144
            $vars = $message;
145
            $message = $message[0];
146
            $vars = array_slice($vars, 1);
147
        }
148
        if ($this->intuitionMessageExists($message, $vars)) {
149
            return $this->intuitionMessage($message, $vars);
150
        } else {
151
            return $message;
152
        }
153
    }
154
155
    /**
156
     * Get an i18n message.
157
     * @param string $message
158
     * @param array $vars
159
     * @return mixed|null|string
160
     */
161 11
    public function intuitionMessage($message = "", $vars = [])
162
    {
163 11
        return $this->getIntuition()->msg($message, [ "domain" => "xtools", "variables" => $vars ]);
164
    }
165
166
    /**
167
     * Get the current language code.
168
     * @return string
169
     */
170 12
    public function getLang()
171
    {
172 12
        return $this->getIntuition()->getLang();
173
    }
174
175
    /**
176
     * Get the current language name (defaults to 'English').
177
     * @return string
178
     */
179 12
    public function getLangName()
180
    {
181 12
        return in_array(ucfirst($this->getIntuition()->getLangName()), $this->getAllLangs())
182 12
            ? $this->getIntuition()->getLangName()
183 12
            : 'English';
184
    }
185
186
    /**
187
     * Get all available languages in the i18n directory
188
     * @return array Associative array of langKey => langName
189
     */
190 12
    public function getAllLangs()
191
    {
192 12
        $messageFiles = glob($this->container->getParameter("kernel.root_dir") . '/../i18n/*.json');
193
194 12
        $languages = array_values(array_unique(array_map(
195 12
            function ($filename) {
196 12
                return basename($filename, '.json');
197 12
            },
198 12
            $messageFiles
199
        )));
200
201 12
        $availableLanguages = [];
202
203 12
        foreach ($languages as $lang) {
204 12
            $availableLanguages[$lang] = ucfirst($this->getIntuition()->getLangName($lang));
205
        }
206 12
        asort($availableLanguages);
207
208 12
        return $availableLanguages;
209
    }
210
211
    /**
212
     * Whether the current language is right-to-left.
213
     * @return bool
214
     */
215 11
    public function intuitionIsRTL()
216
    {
217 11
        return $this->getIntuition()->isRTL($this->getIntuition()->getLang());
218
    }
219
220
    /**
221
     * Whether the given language is right-to-left.
222
     * @param string $lang The language code.
223
     * @return bool
224
     */
225 1
    public function intuitionIsRTLLang($lang)
226
    {
227 1
        return $this->getIntuition()->isRTL($lang);
228
    }
229
230
    /**
231
     * Get the short hash of the currently checked-out Git commit.
232
     * @return string
233
     */
234 11
    public function gitShortHash()
235
    {
236 11
        return exec("git rev-parse --short HEAD");
237
    }
238
239
    /**
240
     * Get the full hash of the currently checkout-out Git commit.
241
     * @return string
242
     */
243 12
    public function gitHash()
244
    {
245 12
        return exec("git rev-parse HEAD");
246
    }
247
248
    /**
249
     * Get the date of the HEAD commit.
250
     * @return string
251
     */
252 2
    public function gitDate()
253
    {
254 2
        $date = new DateTime(exec('git show -s --format=%ci'));
255 2
        return $date->format('Y-m-d');
256
    }
257
258
    /**
259
     * Check whether a given tool is enabled.
260
     * @param string $tool The short name of the tool.
261
     * @return bool
262
     */
263 11
    public function tabEnabled($tool = "index")
264
    {
265 11
        $param = false;
266 11
        if ($this->container->hasParameter("enable.$tool")) {
267 11
            $param = boolval($this->container->getParameter("enable.$tool"));
268
        }
269 11
        return $param;
270
    }
271
272
    /**
273
     * Get a list of the short names of all tools.
274
     * @return string[]
275
     */
276 11
    public function allTools()
277
    {
278 11
        $retVal = [];
279 11
        if ($this->container->hasParameter("tools")) {
280 11
            $retVal = $this->container->getParameter("tools");
281
        }
282 11
        return $retVal;
283
    }
284
285
    /**
286
     * Get a list of namespace colours (one or all).
287
     * @param bool $num The NS ID to get.
288
     * @return string[]|string Indexed by namespace ID.
289
     */
290
    public static function getColorList($num = false)
291
    {
292
        $colors = [
293
            0 => '#FF5555',
294
            1 => '#55FF55',
295
            2 => '#FFEE22',
296
            3 => '#FF55FF',
297
            4 => '#5555FF',
298
            5 => '#55FFFF',
299
            6 => '#C00000',
300
            7 => '#0000C0',
301
            8 => '#008800',
302
            9 => '#00C0C0',
303
            10 => '#FFAFAF',
304
            11 => '#808080',
305
            12 => '#00C000',
306
            13 => '#404040',
307
            14 => '#C0C000',
308
            15 => '#C000C0',
309
            90 => '#991100',
310
            91 => '#99FF00',
311
            92 => '#000000',
312
            93 => '#777777',
313
            100 => '#75A3D1',
314
            101 => '#A679D2',
315
            102 => '#660000',
316
            103 => '#000066',
317
            104 => '#FAFFAF',
318
            105 => '#408345',
319
            106 => '#5c8d20',
320
            107 => '#e1711d',
321
            108 => '#94ef2b',
322
            109 => '#756a4a',
323
            110 => '#6f1dab',
324
            111 => '#301e30',
325
            112 => '#5c9d96',
326
            113 => '#a8cd8c',
327
            114 => '#f2b3f1',
328
            115 => '#9b5828',
329
            116 => '#002288',
330
            117 => '#0000CC',
331
            118 => '#99FFFF',
332
            119 => '#99BBFF',
333
            120 => '#FF99FF',
334
            121 => '#CCFFFF',
335
            122 => '#CCFF00',
336
            123 => '#CCFFCC',
337
            200 => '#33FF00',
338
            201 => '#669900',
339
            202 => '#666666',
340
            203 => '#999999',
341
            204 => '#FFFFCC',
342
            205 => '#FF00CC',
343
            206 => '#FFFF00',
344
            207 => '#FFCC00',
345
            208 => '#FF0000',
346
            209 => '#FF6600',
347
            250 => '#6633CC',
348
            251 => '#6611AA',
349
            252 => '#66FF99',
350
            253 => '#66FF66',
351
            446 => '#06DCFB',
352
            447 => '#892EE4',
353
            460 => '#99FF66',
354
            461 => '#99CC66',
355
            470 => '#CCCC33',
356
            471 => '#CCFF33',
357
            480 => '#6699FF',
358
            481 => '#66FFFF',
359
            484 => '#07C8D6',
360
            485 => '#2AF1FF',
361
            486 => '#79CB21',
362
            487 => '#80D822',
363
            490 => '#995500',
364
            491 => '#998800',
365
            710 => '#FFCECE',
366
            711 => '#FFC8F2',
367
            828 => '#F7DE00',
368
            829 => '#BABA21',
369
            866 => '#FFFFFF',
370
            867 => '#FFCCFF',
371
            1198 => '#FF34B3',
372
            1199 => '#8B1C62',
373
            2300 => '#A900B8',
374
            2301 => '#C93ED6',
375
            2302 => '#8A09C1',
376
            2303 => '#974AB8',
377
            2600 => '#000000',
378
        ];
379
380
        if ($num === false) {
381
            return $colors;
382
        } elseif (isset($colors[$num])) {
383
            return $colors[$num];
384
        } else {
385
            // Default to grey.
386
            return '#CCC';
387
        }
388
    }
389
390
    /**
391
     * Get color-blind friendly colors for use in charts
392
     * @param  Integer $num Index of color
393
     * @return String RGBA color (so you can more easily adjust the opacity)
394
     */
395
    public function chartColor($num)
396
    {
397
        $colors = [
398
            'rgba(171, 212, 235, 1)',
399
            'rgba(178, 223, 138, 1)',
400
            'rgba(251, 154, 153, 1)',
401
            'rgba(253, 191, 111, 1)',
402
            'rgba(202, 178, 214, 1)',
403
            'rgba(207, 182, 128, 1)',
404
            'rgba(141, 211, 199, 1)',
405
            'rgba(252, 205, 229, 1)',
406
            'rgba(255, 247, 161, 1)',
407
            'rgba(217, 217, 217, 1)',
408
        ];
409
410
        return $colors[$num % count($colors)];
411
    }
412
413
    /**
414
     * Whether XTools is running in single-project mode.
415
     * @return bool
416
     */
417 8
    public function isSingleWiki()
418
    {
419 8
        $param = true;
420 8
        if ($this->container->hasParameter('app.single_wiki')) {
421 8
            $param = boolval($this->container->getParameter('app.single_wiki'));
422
        }
423 8
        return $param;
424
    }
425
426
    /**
427
     * Get the database replication-lag threshold.
428
     * @return int
429
     */
430
    public function getReplagThreshold()
431
    {
432
        $param = 30;
433
        if ($this->container->hasParameter('app.replag_threshold')) {
434
            $param = $this->container->getParameter('app.replag_threshold');
435
        };
436
        return $param;
437
    }
438
439
    /**
440
     * Whether we should load stylesheets from external CDNs or not.
441
     * @return bool
442
     */
443 11
    public function loadStylesheetsFromCDN()
444
    {
445 11
        $param = false;
446 11
        if ($this->container->hasParameter('app.load_stylesheets_from_cdn')) {
447 11
            $param = boolval($this->container->getParameter('app.load_stylesheets_from_cdn'));
448
        }
449 11
        return $param;
450
    }
451
452
    /**
453
     * Whether XTools is running in WMF Labs mode.
454
     * @return bool
455
     */
456 11
    public function isWMFLabs()
457
    {
458 11
        $param = false;
459 11
        if ($this->container->hasParameter('app.is_labs')) {
460 11
            $param = boolval($this->container->getParameter('app.is_labs'));
461
        }
462 11
        return $param;
463
    }
464
465
    /**
466
     * The current replication lag.
467
     * @return int
468
     * @codeCoverageIgnore
469
     */
470
    public function replag()
471
    {
472
        $retVal = 0;
473
474
        if ($this->isWMFLabs()) {
475
            $project = $this->getCurrentRequest()->get('project');
476
477
            if (!isset($project)) {
478
                $project = 'enwiki';
479
            }
480
481
            $dbName = ProjectRepository::getProject($project, $this->container)
482
                ->getDatabaseName();
483
484
            $stmt = "SELECT lag FROM `heartbeat_p`.`heartbeat` h
485
            RIGHT JOIN `meta_p`.`wiki` w ON concat(h.shard, \".labsdb\")=w.slice
486
            WHERE dbname LIKE :project LIMIT 1";
487
488
            $conn = $this->container->get('doctrine')->getManager('replicas')->getConnection();
489
490
            // Prepare the query and execute
491
            $resultQuery = $conn->prepare($stmt);
492
            $resultQuery->bindParam('project', $dbName);
493
            $resultQuery->execute();
494
495
            if ($resultQuery->errorCode() == 0) {
496
                $results = $resultQuery->fetchAll();
497
498
                if (isset($results[0]['lag'])) {
499
                    $retVal = $results[0]['lag'];
500
                }
501
            }
502
        }
503
504
        return $retVal;
505
    }
506
507
    /**
508
     * Get a random quote for the footer
509
     * @return string
510
     */
511 11
    public function quote()
512
    {
513
        // Don't show if bash is turned off, but always show for Labs
514
        // (so quote is in footer but not in nav).
515 11
        $isLabs = $this->container->getParameter('app.is_labs');
516 11
        if (!$isLabs && !$this->container->getParameter('enable.bash')) {
517 11
            return '';
518
        }
519
        $quotes = $this->container->getParameter('quotes');
520
        $id = array_rand($quotes);
521
        return $quotes[$id];
522
    }
523
524
    /**
525
     * Get the currently logged in user's details.
526
     * @return string[]
527
     */
528 11
    public function functionLoggedInUser()
529
    {
530 11
        return $this->container->get('session')->get('logged_in_user');
531
    }
532
533
534
    /*********************************** FILTERS ***********************************/
535
536
    /**
537
     * Get all filters for this extension.
538
     * @return array
539
     */
540 12
    public function getFilters()
541
    {
542
        return [
543 12
            new \Twig_SimpleFilter('capitalize_first', [ $this, 'capitalizeFirst' ]),
544 12
            new \Twig_SimpleFilter('percent_format', [ $this, 'percentFormat' ]),
545 12
            new \Twig_SimpleFilter('diff_format', [ $this, 'diffFormat' ], [ 'is_safe' => [ 'html' ] ]),
546 12
            new \Twig_SimpleFilter('num_format', [$this, 'numberFormat']),
547 12
            new \Twig_SimpleFilter('date_format', [$this, 'dateFormatStd']),
548 12
            new \Twig_SimpleFilter('date_localize', [$this, 'dateFormat']),
549
        ];
550
    }
551
552
    /**
553
     * Format a number based on language settings.
554
     * @param  int|float $number
555
     * @param  int $decimals Number of decimals to format to.
556
     * @return string
557
     */
558 15
    public function numberFormat($number, $decimals = 0)
559
    {
560 15
        if (!isset($this->numFormatter)) {
561 15
            $lang = $this->getIntuition()->getLang();
562 15
            $this->numFormatter = new NumberFormatter($lang, NumberFormatter::DECIMAL);
0 ignored issues
show
Bug introduced by
The call to NumberFormatter::__construct() has too few arguments starting with pattern. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

562
            $this->numFormatter = /** @scrutinizer ignore-call */ new NumberFormatter($lang, NumberFormatter::DECIMAL);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
563
        }
564
565
        // Get separator symbols.
566 15
        $decimal = $this->numFormatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
567 15
        $thousands = $this->numFormatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
568
569 15
        $formatted = number_format($number, $decimals, $decimal, $thousands);
570
571
        // Remove trailing .0's (e.g. 40.00 -> 40).
572 15
        return preg_replace("/\\".$decimal."0+$/", '', $formatted);
573
    }
574
575
    /**
576
     * Localize the given date based on language settings.
577
     * @param  string|DateTime $datetime
578
     * @return string
579
     */
580 1
    public function dateFormat($datetime)
581
    {
582 1
        if (!isset($this->dateFormatter)) {
583 1
            $this->dateFormatter = new IntlDateFormatter(
0 ignored issues
show
Bug introduced by
The call to IntlDateFormatter::__construct() has too few arguments starting with timezone. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

583
            $this->dateFormatter = /** @scrutinizer ignore-call */ new IntlDateFormatter(

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
584 1
                $this->getIntuition()->getLang(),
585 1
                IntlDateFormatter::SHORT,
586 1
                IntlDateFormatter::SHORT
587
            );
588
        }
589
590 1
        if (is_string($datetime) || is_int($datetime)) {
591 1
            $datetime = new DateTime($datetime);
592
        }
593
594 1
        return $this->dateFormatter->format($datetime);
595
    }
596
597
    /**
598
     * Format the given date to ISO 8601.
599
     * @param  string|DateTime $datetime
600
     * @return string
601
     */
602
    public function dateFormatStd($datetime)
603
    {
604
        if (is_string($datetime) || is_int($datetime)) {
605
            $datetime = new DateTime($datetime);
606
        }
607
608
        return $datetime->format('Y-m-d H:i');
609
    }
610
611
    /**
612
     * Mysteriously missing Twig helper to capitalize only the first character.
613
     * E.g. used for table headings for translated messages
614
     * @param  string $str The string
615
     * @return string      The string, capitalized
616
     */
617 5
    public function capitalizeFirst($str)
618
    {
619 5
        return ucfirst($str);
620
    }
621
622
    /**
623
     * Format a given number or fraction as a percentage.
624
     * @param  number  $numerator   Numerator or single fraction if denominator is ommitted.
625
     * @param  number  $denominator Denominator.
626
     * @param  integer $precision   Number of decimal places to show.
627
     * @return string               Formatted percentage.
628
     */
629 1
    public function percentFormat($numerator, $denominator = null, $precision = 1)
630
    {
631 1
        if (!$denominator) {
632 1
            $quotient = $numerator;
633
        } else {
634 1
            $quotient = ( $numerator / $denominator ) * 100;
635
        }
636
637 1
        return $this->numberFormat($quotient, $precision) . '%';
638
    }
639
640
    /**
641
     * Helper to return whether the given user is an anonymous (logged out) user.
642
     * @param  User|string $user User object or username as a string.
643
     * @return bool
644
     */
645 1
    public function isUserAnon($user)
646
    {
647 1
        if ($user instanceof User) {
648 1
            $username = $user->getUsername();
649
        } else {
650 1
            $username = $user;
651
        }
652
653 1
        return (bool)filter_var($username, FILTER_VALIDATE_IP);
654
    }
655
656
    /**
657
     * Helper to properly translate a namespace name
658
     * @param  int|string $namespace Namespace key as a string or ID
659
     * @param  array      $namespaces List of available namespaces
660
     *                                as retrieved from Project::getNamespaces
661
     * @return string Namespace name
662
     */
663
    public function nsName($namespace, $namespaces)
664
    {
665
        if ($namespace === 'all') {
666
            return $this->getIntuition()->msg('all');
667
        } elseif ($namespace === '0' || $namespace === 0 || $namespace === 'Main') {
668
            return $this->getIntuition()->msg('mainspace');
669
        } else {
670
            return $namespaces[$namespace];
671
        }
672
    }
673
674
    /**
675
     * Format a given number as a diff, colouring it green if it's postive, red if negative, gary if zero
676
     * @param  number $size Diff size
677
     * @return string       Markup with formatted number
678
     */
679 1
    public function diffFormat($size)
680
    {
681 1
        if ($size < 0) {
682 1
            $class = 'diff-neg';
683 1
        } elseif ($size > 0) {
684 1
            $class = 'diff-pos';
685
        } else {
686 1
            $class = 'diff-zero';
687
        }
688
689 1
        $size = $this->numberFormat($size);
690
691 1
        return "<span class='$class'>$size</span>";
692
    }
693
694
    /**
695
     * Format a time duration as humanized string.
696
     * @param int $seconds Number of seconds.
697
     * @param bool $translate Used for unit testing. Set to false to return
698
     *   the value and i18n key, instead of the actual translation.
699
     * @return string|array Examples: '30 seconds', '2 minutes', '15 hours', '500 days',
700
     *   or [30, 'num-seconds'] (etc.) if $translate is false.
701
     */
702 1
    public function formatDuration($seconds, $translate = true)
703
    {
704 1
        list($val, $key) = $this->getDurationMessageKey($seconds);
705
706 1
        if ($translate) {
707
            return $this->numberFormat($val) . ' ' . $this->intuitionMessage("num-$key", [$val]);
708
        } else {
709 1
            return [$this->numberFormat($val), "num-$key"];
710
        }
711
    }
712
713
    /**
714
     * Given a time duration in seconds, generate a i18n message key and value.
715
     * @param  int $seconds Number of seconds.
716
     * @return array<integer|string> [int - message value, string - message key]
717
     */
718 1
    private function getDurationMessageKey($seconds)
719
    {
720
        /** @var int Value to show in message */
721 1
        $val = $seconds;
722
723
        /** @var string Unit of time, used in the key for the i18n message */
724 1
        $key = 'seconds';
725
726 1
        if ($seconds >= 86400) {
727
            // Over a day
728 1
            $val = (int) floor($seconds / 86400);
729 1
            $key = 'days';
730 1
        } elseif ($seconds >= 3600) {
731
            // Over an hour, less than a day
732 1
            $val = (int) floor($seconds / 3600);
733 1
            $key = 'hours';
734 1
        } elseif ($seconds >= 60) {
735
            // Over a minute, less than an hour
736 1
            $val = (int) floor($seconds / 60);
737 1
            $key = 'minutes';
738
        }
739
740 1
        return [$val, $key];
741
    }
742
}
743