GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( ea93fb...6c9176 )
by Anton
02:17
created

functions.php ➔ commandSupportsOption()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 2
dl 0
loc 8
ccs 3
cts 4
cp 0.75
crap 2.0625
rs 10
c 0
b 0
f 0
1
<?php
2
/* (c) Anton Medvedev <[email protected]>
3
 *
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace Deployer;
9
10
use Deployer\Exception\Exception;
11
use Deployer\Exception\GracefulShutdownException;
12
use Deployer\Exception\RunException;
13
use Deployer\Host\FileLoader;
14
use Deployer\Host\Host;
15
use Deployer\Host\Localhost;
16
use Deployer\Host\Range;
17
use Deployer\Support\Proxy;
18
use Deployer\Task\Context;
19
use Deployer\Task\GroupTask;
20
use Deployer\Task\Task as T;
21
use Symfony\Component\Console\Exception\MissingInputException;
22
use Symfony\Component\Console\Helper\QuestionHelper;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\Console\Question\ChoiceQuestion;
27
use Symfony\Component\Console\Question\ConfirmationQuestion;
28
use Symfony\Component\Console\Question\Question;
29
use function Deployer\Support\array_to_string;
30
use function Deployer\Support\str_contains;
31
32
/**
33
 * @param string ...$hostname
34
 * @return Host|Host[]|Proxy
35
 */
36
function host(...$hostname)
37
{
38 1
    $deployer = Deployer::get();
39 1
    $aliases = Range::expand($hostname);
40
41 1
    foreach ($aliases as $alias) {
42 1
        if ($deployer->hosts->has($alias)) {
43
            $host = $deployer->hosts->get($alias);
44
            throw new \InvalidArgumentException(
45
                "Host \"{$host->tag()}\" already exists.\n" .
46
                "If you want to override configuration options, get host with <fg=yellow>getHost</> function.\n" .
47
                "\n" .
48
                "    <fg=yellow>getHost</>(<fg=green>'{$alias}'</>);" .
49
                "\n"
50
            );
51
        }
52
    }
53
54 1
    if (count($aliases) === 1) {
55 1
        $host = new Host($aliases[0]);
56 1
        $deployer->hosts->set($aliases[0], $host);
57 1
        return $host;
58
    } else {
59
        $hosts = array_map(function ($hostname) use ($deployer) {
60 1
            $host = new Host($hostname);
61 1
            $deployer->hosts->set($hostname, $host);
62 1
            return $host;
63 1
        }, $aliases);
64 1
        return new Proxy($hosts);
65
    }
66
}
67
68
/**
69
 * @param string ...$hostnames
70
 * @return Localhost|Localhost[]|Proxy
71
 */
72
function localhost(...$hostnames)
73
{
74 10
    $deployer = Deployer::get();
75 10
    $hostnames = Range::expand($hostnames);
76
77 10
    if (count($hostnames) <= 1) {
78 10
        $host = count($hostnames) === 1 ? new Localhost($hostnames[0]) : new Localhost();
79 10
        $deployer->hosts->set($host->alias(), $host);
80 10
        return $host;
81
    } else {
82
        $hosts = array_map(function ($hostname) use ($deployer) {
83
            $host = new Localhost($hostname);
84
            $deployer->hosts->set($host->alias(), $host);
85
            return $host;
86
        }, $hostnames);
87
        return new Proxy($hosts);
88
    }
89
}
90
91
/**
92
 * Get host by host alias.
93
 *
94
 * @param string $alias
95
 * @return Host
96
 */
97
function getHost(string $alias)
98
{
99 1
    return Deployer::get()->hosts->get($alias);
100
}
101
102
/**
103
 * Get current host.
104
 *
105
 * @return Host
106
 */
107
function currentHost()
108
{
109 6
    return Context::get()->getHost();
110
}
111
112
113
/**
114
 * Load list of hosts from file
115
 *
116
 * @param string $file
117
 * @return Proxy
118
 */
119
function inventory($file)
120
{
121
    $deployer = Deployer::get();
122
    $fileLoader = new FileLoader();
123
    $fileLoader->load($file);
124
125
    $hosts = $fileLoader->getHosts();
126
    foreach ($hosts as $host) {
127
        $deployer->hosts->set($host->alias(), $host);
128
    }
129
130
    return new Proxy($hosts);
131
}
132
133
/**
134
 * Set task description.
135
 *
136
 * @param string $title
137
 * @return string
138
 */
139
function desc($title = null)
140
{
141 19
    static $store = null;
142
143 19
    if ($title === null) {
144 19
        return $store;
145
    } else {
146 9
        return $store = $title;
147
    }
148
}
149
150
/**
151
 * Define a new task and save to tasks list.
152
 *
153
 * Alternatively get a defined task.
154
 *
155
 * @param string $name Name of current task.
156
 * @param callable|array|string|null $body Callable task, array of other tasks names or nothing to get a defined tasks
157
 * @return Task\Task
158
 */
159
function task($name, $body = null)
160
{
161 19
    $deployer = Deployer::get();
162
163 19
    if (empty($body)) {
164 3
        return $deployer->tasks->get($name);
165
    }
166
167 19
    if (is_callable($body)) {
168 19
        $task = new T($name, $body);
169 10
    } elseif (is_array($body)) {
170 10
        $task = new GroupTask($name, $body);
171
    } else {
172
        throw new \InvalidArgumentException('Task should be a closure or array of other tasks.');
173
    }
174
175 19
    $task->saveSourceLocation();
176 19
    $deployer->tasks->set($name, $task);
177
178 19
    if (!empty(desc())) {
179 9
        $task->desc(desc());
180 9
        desc(''); // Clear title.
181
    }
182
183 19
    return $task;
184
}
185
186
/**
187
 * Call that task before specified task runs.
188
 *
189
 * @param string $task The task before $that should be run.
190
 * @param string|callable $todo The task to be run.
191
 * @return T|void
192
 */
193
function before($task, $todo)
194
{
195 1
    if (is_callable($todo)) {
196 1
        $newTask = task("$task:before", $todo);
197 1
        before($task, "$task:before");
198 1
        return $newTask;
199
    }
200 1
    task($task)->addBefore($todo);
201 1
}
202
203
/**
204
 * Call that task after specified task runs.
205
 *
206
 * @param string $task The task after $that should be run.
207
 * @param string|callable $todo The task to be run.
208
 * @return T|void
209
 */
210
function after($task, $todo)
211
{
212 1 View Code Duplication
    if (is_callable($todo)) {
213 1
        $newTask = task("$task:after", $todo);
214 1
        after($task, "$task:after");
215 1
        return $newTask;
216
    }
217 1
    task($task)->addAfter($todo);
218 1
}
219
220
/**
221
 * Setup which task run on failure of first.
222
 *
223
 * @param string $task The task which need to fail so $that should be run.
224
 * @param string $todo The task to be run.
225
 * @return T|void
226
 */
227
function fail($task, $todo)
228
{
229 9 View Code Duplication
    if (is_callable($todo)) {
230
        $newTask = task("$task:fail", $todo);
231
        fail($task, "$task:fail");
232
        return $newTask;
233
    }
234 9
    $deployer = Deployer::get();
235 9
    $deployer->fail->set($task, $todo);
236 9
}
237
238
/**
239
 * Add users options.
240
 *
241
 * @param string $name The option name
242
 * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
243
 * @param int|null $mode The option mode: One of the VALUE_* constants
244
 * @param string $description A description text
245
 * @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
246
 */
247
function option($name, $shortcut = null, $mode = null, $description = '', $default = null)
248
{
249 9
    Deployer::get()->inputDefinition->addOption(
250 9
        new InputOption($name, $shortcut, $mode, $description, $default)
251
    );
252 9
}
253
254
/**
255
 * Change the current working directory.
256
 *
257
 * @param string $path
258
 */
259
function cd($path)
260
{
261 6
    set('working_path', parse($path));
262 6
}
263
264
/**
265
 * Execute a callback within a specific directory and revert back to the initial working directory.
266
 *
267
 * @param string $path
268
 * @param callable $callback
269
 */
270
function within($path, $callback)
271
{
272
    $lastWorkingPath = get('working_path', '');
273
    try {
274
        set('working_path', parse($path));
275
        $callback();
276
    } finally {
277
        set('working_path', $lastWorkingPath);
278
    }
279
}
280
281
/**
282
 * Run command.
283
 *
284
 * @param string $command
285
 * @param array $options
286
 * @return string
287
 */
288
function run($command, $options = [])
289
{
290
    $run = function ($command, $options) {
291 7
        $host = Context::get()->getHost();
292
293 7
        $command = parse($command);
294 7
        $workingPath = get('working_path', '');
295
296 7
        if (!empty($workingPath)) {
297 6
            $command = "cd $workingPath && ($command)";
298
        }
299
300 7
        $env = get('env', []) + ($options['env'] ?? []);
301 7
        if (!empty($env)) {
302
            $env = array_to_string($env);
303
            $command = "export $env; $command";
304
        }
305
306 7
        if ($host instanceof Localhost) {
307 7
            $process = Deployer::get()->processRunner;
308 7
            $output = $process->run($host, $command, $options);
309
        } else {
310
            $client = Deployer::get()->sshClient;
311
            $output = $client->run($host, $command, $options);
312
        }
313
314 7
        return rtrim($output);
315 7
    };
316
317 7
    if (preg_match('/^sudo\b/', $command)) {
318
        try {
319
            return $run($command, $options);
320
        } catch (RunException $exception) {
321
            $askpass = get('sudo_askpass', '/tmp/dep_sudo_pass');
322
            $password = get('sudo_pass', false);
323
            if ($password === false) {
324
                writeln("<fg=green;options=bold>run</> $command");
325
                $password = askHiddenResponse('Password:');
326
            }
327
            $run("echo -e '#!/bin/sh\necho \"%secret%\"' > $askpass", array_merge($options, ['secret' => $password]));
328
            $run("chmod a+x $askpass", $options);
329
            $run(sprintf('export SUDO_ASKPASS=%s; %s', $askpass, preg_replace('/^sudo\b/', 'sudo -A', $command)), $options);
330
            $run("rm $askpass", $options);
331
        }
332
    } else {
333 7
        return $run($command, $options);
334
    }
335
}
336
337
338
/**
339
 * Execute commands on local machine
340
 *
341
 * @param string $command Command to run locally.
342
 * @param array $options
343
 * @return string Output of command.
344
 */
345
function runLocally($command, $options = [])
346
{
347 5
    $process = Deployer::get()->processRunner;
348 5
    $command = parse($command);
349
350 5
    $env = get('env', []) + ($options['env'] ?? []);
351 5
    if (!empty($env)) {
352
        $env = array_to_string($env);
353
        $command = "export $env; $command";
354
    }
355
356 5
    $output = $process->run(new Localhost(), $command, $options);
357
358 5
    return rtrim($output);
359
}
360
361
/**
362
 * Run test command.
363
 * Example:
364
 *
365
 *     test('[ -d {{release_path}} ]')
366
 *
367
 * @param string $command
368
 * @return bool
369
 */
370
function test($command)
371
{
372 6
    return run("if $command; then echo 'true'; fi") === 'true';
373
}
374
375
/**
376
 * Run test command locally.
377
 * Example:
378
 *
379
 *     testLocally('[ -d {{local_release_path}} ]')
380
 *
381
 * @param string $command
382
 * @return bool
383
 */
384
function testLocally($command)
385
{
386
    return runLocally("if $command; then echo 'true'; fi") === 'true';
387
}
388
389
/**
390
 * Iterate other hosts, allowing to call run func in callback.
391
 *
392
 * @experimental
393
 * @param Host|Host[] $hosts
394
 * @param callable $callback
395
 */
396
function on($hosts, callable $callback)
397
{
398
    $deployer = Deployer::get();
399
400
    if (!is_array($hosts) && !($hosts instanceof \Traversable)) {
401
        $hosts = [$hosts];
402
    }
403
404
    foreach ($hosts as $host) {
405
        if ($host instanceof Host) {
406
            $host->getConfig()->load();
407
            Context::push(new Context($host, input(), output()));
408
            try {
409
                $callback($host);
410
                $host->getConfig()->save();
411
            } catch (GracefulShutdownException $e) {
412
                $deployer->messenger->renderException($e, $host);
413
            } finally {
414
                Context::pop();
415
            }
416
        } else {
417
            throw new \InvalidArgumentException("Function on can iterate only on Host instances.");
418
        }
419
    }
420
}
421
422
/**
423
 * Run task
424
 *
425
 * @experimental
426
 * @param string $task
427
 */
428
function invoke($task)
429
{
430
    $hosts = [Context::get()->getHost()];
431
    $tasks = Deployer::get()->scriptManager->getTasks($task, $hosts);
0 ignored issues
show
Unused Code introduced by
The call to ScriptManager::getTasks() has too many arguments starting with $hosts.

This check compares calls to functions or methods with their respective definitions. If the call has more 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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
432
433
    $executor = Deployer::get()->seriesExecutor;
0 ignored issues
show
Bug introduced by
The property seriesExecutor does not seem to exist. Did you mean executor?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
434
    $executor->run($tasks, $hosts);
435
}
436
437
/*
438
 * Upload file or directory to host.
439
 */
440 View Code Duplication
function upload(string $source, string $destination, $config = [])
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
441
{
442
    $rsync = Deployer::get()->rsync;
443
    $host = currentHost();
444
    $source = parse($source);
445
    $destination = parse($destination);
446
447
    if ($host instanceof Localhost) {
448
        $rsync->call($host, $source, $destination, $config);
449
    } else {
450
        $rsync->call($host, $source, "{$host->hostname()}:$destination", $config);
451
    }
452
}
453
454
/*
455
 * Download file or directory from host
456
 */
457 View Code Duplication
function download(string $source, string $destination, $config = [])
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
458
{
459
    $rsync = Deployer::get()->rsync;
460
    $host = currentHost();
461
    $source = parse($source);
462
    $destination = parse($destination);
463
464
    if ($host instanceof Localhost) {
465
        $rsync->call($host, $source, $destination, $config);
466
    } else {
467
        $rsync->call($host, "{$host->hostname()}:$source", $destination, $config);
468
    }
469
}
470
471
/**
472
 * Writes an info message.
473
 * @param string $message
474
 */
475
function info($message)
476
{
477 5
    output()->writeln("<fg=green;options=bold>info</> " . parse($message));
478 5
}
479
480
/**
481
 * Writes an warning message.
482
 * @param string $message
483
 */
484
function warning($message)
485
{
486
    writeln("<fg=yellow;options=bold>warning</> <comment>" . parse($message) . "</comment>");
487
}
488
489
/**
490
 * Writes a message to the output and adds a newline at the end.
491
 * @param string|array $message
492
 * @param int $options
493
 */
494
function writeln($message, $options = 0)
495
{
496 1
    $host = currentHost();
497 1
    output()->writeln("[{$host->tag()}] " . parse($message), $options);
498 1
}
499
500
/**
501
 * Writes a message to the output.
502
 * @param string $message
503
 * @param int $options
504
 */
505
function write($message, $options = 0)
506
{
507
    output()->write(parse($message), $options);
508
}
509
510
/**
511
 * Setup configuration option.
512
 *
513
 * @param string $name
514
 * @param mixed $value
515
 */
516 View Code Duplication
function set($name, $value)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
517
{
518 9
    if (!Context::has()) {
519 9
        Deployer::get()->config->set($name, $value);
520
    } else {
521 6
        Context::get()->getConfig()->set($name, $value);
522
    }
523 9
}
524
525
/**
526
 * Merge new config params to existing config array.
527
 *
528
 * @param string $name
529
 * @param array $array
530
 */
531 View Code Duplication
function add($name, $array)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
532
{
533
    if (!Context::has()) {
534
        Deployer::get()->config->add($name, $array);
535
    } else {
536
        Context::get()->getConfig()->add($name, $array);
537
    }
538
}
539
540
/**
541
 * Get configuration value.
542
 *
543
 * @param string $name
544
 * @param mixed|null $default
545
 * @return mixed
546
 */
547 View Code Duplication
function get($name, $default = null)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
548
{
549 8
    if (!Context::has()) {
550
        return Deployer::get()->config->get($name, $default);
551
    } else {
552 8
        return Context::get()->getConfig()->get($name, $default);
553
    }
554
}
555
556
/**
557
 * Check if there is such configuration option.
558
 *
559
 * @param string $name
560
 * @return boolean
561
 */
562 View Code Duplication
function has($name)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
563
{
564 4
    if (!Context::has()) {
565
        return Deployer::get()->config->has($name);
566
    } else {
567 4
        return Context::get()->getConfig()->has($name);
568
    }
569
}
570
571
/**
572
 * @param string $message
573
 * @param string|null $default
574
 * @param string[]|null $autocomplete
575
 * @return string
576
 */
577
function ask($message, $default = null, $autocomplete = null)
578
{
579
    Context::required(__FUNCTION__);
580
581
    if (output()->isQuiet()) {
582
        return $default;
583
    }
584
585
    /** @var QuestionHelper $helper */
586
    $helper = Deployer::get()->getHelper('question');
587
588
    $tag = currentHost()->tag();
589
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
590
591
    $question = new Question($message, $default);
592
    if (!empty($autocomplete)) {
593
        $question->setAutocompleterValues($autocomplete);
594
    }
595
596
    try {
597
        return $helper->ask(input(), output(), $question);
598
    } catch (MissingInputException $exception) {
599
        throw new Exception("Failed to read input from stdin.\nMake sure what you are asking for input not from parallel task.", $exception->getCode(), $exception);
600
    }
601
}
602
603
/**
604
 * @param string $message
605
 * @param string[] $availableChoices
606
 * @param string|null $default
607
 * @param bool|false $multiselect
608
 * @return string|string[]
609
 */
610
function askChoice($message, array $availableChoices, $default = null, $multiselect = false)
611
{
612
    Context::required(__FUNCTION__);
613
614
    if (empty($availableChoices)) {
615
        throw new \InvalidArgumentException('Available choices should not be empty');
616
    }
617
618
    if ($default !== null && !array_key_exists($default, $availableChoices)) {
619
        throw new \InvalidArgumentException('Default choice is not available');
620
    }
621
622
    if (output()->isQuiet()) {
623
        if ($default === null) {
624
            $default = key($availableChoices);
625
        }
626
        return [$default => $availableChoices[$default]];
627
    }
628
629
    $helper = Deployer::get()->getHelper('question');
630
631
    $tag = currentHost()->tag();
632
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
633
634
    $question = new ChoiceQuestion($message, $availableChoices, $default);
635
    $question->setMultiselect($multiselect);
636
637
    return $helper->ask(input(), output(), $question);
638
}
639
640
/**
641
 * @param string $message
642
 * @param bool $default
643
 * @return bool
644
 */
645
function askConfirmation($message, $default = false)
646
{
647
    Context::required(__FUNCTION__);
648
649
    if (output()->isQuiet()) {
650
        return $default;
651
    }
652
653
    $helper = Deployer::get()->getHelper('question');
654
655
    $yesOrNo = $default ? 'Y/n' : 'y/N';
656
    $tag = currentHost()->tag();
657
    $message = "[$tag] <question>$message</question> [$yesOrNo] ";
658
659
    $question = new ConfirmationQuestion($message, $default);
660
661
    return $helper->ask(input(), output(), $question);
662
}
663
664
/**
665
 * @param string $message
666
 * @return string
667
 */
668
function askHiddenResponse($message)
669
{
670
    Context::required(__FUNCTION__);
671
672
    if (output()->isQuiet()) {
673
        return '';
674
    }
675
676
    $helper = Deployer::get()->getHelper('question');
677
678
    $tag = currentHost()->tag();
679
    $message = "[$tag] <question>$message</question> ";
680
681
    $question = new Question($message);
682
    $question->setHidden(true);
683
    $question->setHiddenFallback(false);
684
685
    return $helper->ask(input(), output(), $question);
686
}
687
688
/**
689
 * @return InputInterface
690
 */
691
function input()
692
{
693 4
    return Context::get()->getInput();
694
}
695
696
697
/**
698
 * @return OutputInterface
699
 */
700
function output()
701
{
702 6
    return Context::get()->getOutput();
703
}
704
705
/**
706
 * Check if command exists
707
 *
708
 * @param string $command
709
 * @return bool
710
 */
711
function commandExist($command)
712
{
713 3
    return test("hash $command 2>/dev/null");
714
}
715
716
function commandSupportsOption($command, $option)
717
{
718 5
    $man = run("(man $command 2>&1 || $command -h 2>&1 || $command --help 2>&1) | grep -- $option || true");
719 5
    if (empty($man)) {
720
        return false;
721
    }
722 5
    return str_contains($man, $option);
723
}
724
725
/**
726
 * Parse set values.
727
 *
728
 * @param string $value
729
 * @return string
730
 */
731
function parse($value)
732
{
733 9
    return Context::get()->getConfig()->parse($value);
734
}
735
736
function locateBinaryPath($name)
737
{
738 4
    $nameEscaped = escapeshellarg($name);
739
740
    // Try `command`, should cover all Bourne-like shells
741
    // Try `which`, should cover most other cases
742
    // Fallback to `type` command, if the rest fails
743 4
    $path = run("command -v $nameEscaped || which $nameEscaped || type -p $nameEscaped");
744 4
    if (!$path) {
745
        throw new \RuntimeException("Can't locate [$nameEscaped] - neither of [command|which|type] commands are available");
746
    }
747
748
    // Deal with issue when `type -p` outputs something like `type -ap` in some implementations
749 4
    return trim(str_replace("$name is", "", $path));
750
751
}
752