Path.php 21.1 KB
Newer Older
1
2
3
4
5
<?php


namespace IMATHUZH\Qfq\Core\Helper;

6
/*
7
8
9
10
11
 * Glossar:
 * - App: directory in which the index.php file is located. All urls should be relative to this.
 * - Ext: directory in which the QFQ extension is loacted. e.g. the folder Classes is in there.
 * - API: api folder of qfq extension
 *
12
13
14
15
16
 * Conventions of Path class:
 * 1) naming conventions of of path constants/functions/variables:
 *   a) name a path by its origin and its destination separated by 'to'. E.g. APP_TO_SYSTEM_LOG, $appToProject.
 *   b) Or only by its destination and prefix "absolute" if the path is absolute e.g. absoluteApp().
 *   c) Or by its destination and prefix "url" if the path returns a fully qualified url using the base path e.g. urlExt().
17
 * 2) if the destination is a file, append "File". E.g. APP_TO_SYSTEM_QFQ_LOG_FILE.
18
19
20
21
22
 * 3) if a path has to be variable, create a setter and getter. E.g. self::setAbsoluteApp(), self::absoluteApp(), private static $absoluteApp.
 * 4) a path getter appends the given arguments to the requested path using self::join(..., func_get_args()). E.g. see absoluteApp().
 * 5) additional path getters may be defined which combine other getters. E.g. see absoluteProject().
 * 6) avoid manually defining new absolute paths. Define paths relative to App then create a getter which joins it with absoluteApp()
 * 7) avoid defining redundant paths in constants. E.g. create appToApi() by combining appToExt() and extToApi() instead of defining APP_TO_API.
23
24
 */

25
use IMATHUZH\Qfq\Core\Exception\Thrower;
26
27
use IMATHUZH\Qfq\Core\Store\Config;

28
29
class Path
{
30
    // App
31
32
    private static $absoluteApp = null; // This is manually set in self::setMainPaths()
    private static $urlApp = null;
33
34

    // Extension
35
    const APP_TO_EXT = 'typo3conf/ext/qfq';
36

37
    // API
38
    const EXT_TO_API = 'Classes/Api';
39

40
41
    // Report
    const EXT_TO_REPORT_SYSTEM = 'Resources/Private/Report';
42
    const EXT_TO_FORM_SYSTEM = 'Resources/Private/Form';
43

44
45
46
47
    // Javascript
    const EXT_TO_JAVASCRIPT = 'Resources/Public/JavaScript';
    const JAVASCRIPT_TO_EXT = '../../../';

48
    // Icons
49
    const EXT_TO_GFX_INFO_FILE = 'Resources/Public/icons/note.gif';
50
    const EXT_TO_PATH_ICONS = 'Resources/Public/icons';
51

52
    // Annotate
53
    const EXT_TO_HIGHLIGHT_JSON = 'Resources/Public/Json';
54
55
56
57
58
59

    // Twig
    const EXT_TO_TWIG_TEMPLATES = 'Resources/Public/twig_templates';

    // QFQ Project dir
    private static $appToProject = null;
60
61
62
    private static $projectToForm = null;
    private static $projectToReport = null;
    private const APP_TO_PROJECT_DEFAULT = '../'; // Don't use directly, use appToProject()
63
    private const APP_TO_FILEADMIN = 'fileadmin';
64
    private const APP_TO_PROJECT_IN_PROTECTED = 'fileadmin/protected/qfqProject'; // Don't use directly, use appToProject()
65
    const PROJECT_TO_CONF = 'conf';
66
67
    const PROJECT_TO_FORM_DEFAULT = 'form'; // Don't use directly, use projectToForm()
    const PROJECT_TO_FORM_PHPUNIT = 'form_phpunit'; // Don't use directly, use projectToForm()
68
    const FORM_TO_FORM_BACKUP = '.backup';
69
70
    const PROJECT_TO_REPORT_DEFAULT = 'report'; // Don't use directly, use projectToReport()
    const PROJECT_TO_REPORT_PHPUNIT = 'report_phpunit'; // Don't use directly, use projectToReport()
71
    const REPORT_FILE_TO_BACKUP = '.backup'; // The path from a directory containing a report file to the directory containing backups of that report file
72
73

    // Config
Marc Egger's avatar
Marc Egger committed
74
    const APP_TO_TYPO3_CONF = 'typo3conf';
75

76
    // Log files
77
    private static $absoluteLog = null;
78
79
80
    private static $overloadAbsoluteQfqLogFile = null;
    private static $overloadAbsoluteMailLogFile = null;
    private static $overloadAbsoluteSqlLogFile = null;
81
82
83
84
85
    private const LOG_TO_QFQ_LOG_FILE_DEFAULT = 'qfq.log'; // Don't use directly, use absoluteQfqLogFile()
    private const LOG_TO_MAIL_LOG_FILE_DEFAULT = 'mail.log'; // Don't use directly, use absoluteMailLogFile()
    private const LOG_TO_SQL_LOG_FILE_DEFAULT = 'sql.log'; // Don't use directly, use absoluteSqlLogFile()
    private const PROJECT_TO_LOG_DEFAULT = 'log'; // Don't use directly, use absoluteLog()
    private const APP_TO_LOG_IN_PROTECTED = 'fileadmin/protected/log'; // Don't use directly, use absoluteLog()
86

87
    // Thumbnail
88
89
90
    const APP_TO_SYSTEM_THUMBNAIL_DIR_SECURE_DEFAULT = 'fileadmin/protected/qfqThumbnail';
    const APP_TO_SYSTEM_THUMBNAIL_DIR_PUBLIC_DEFAULT = 'typo3temp/qfqThumbnail';
    const APP_TO_THUMBNAIL_UNKNOWN_TYPE = 'typo3/sysext/frontend/Resources/Public/Icons/FileIcons/';
91

92
    // Send Email
Marc Egger's avatar
Marc Egger committed
93
    const EXT_TO_SEND_EMAIL_FILE = 'Classes/External/sendEmail';
94

95
    /**
96
     * Manually set the paths which are not constant nor can be inferred by other paths.
97
     * This function must be called at the beginning of every entry point, to tell the Path class, where things are.
98
     *
99
     * @param string|null $absoluteApp
100
101
102
     * @throws \CodeException
     * @throws \UserFormException
     */
103
    public static function setMainPaths(string $absoluteApp = null)
104
    {
105
106
107
108
        if (is_null($absoluteApp)) {
            $absoluteApp = self::findAbsoluteApp();
        }
        self::setAbsoluteApp($absoluteApp);
109
110

        // Only executed on first call:
111
112
        self::findAppToProject();
        self::findAbsoluteLog();
113
    }
114

115
    /**
116
     * @param array $pathPartsToAppend
117
118
119
     * @return string
     * @throws \UserFormException
     */
120
    public static function absoluteApp(...$pathPartsToAppend): string
121
    {
122
123
        self::enforcePathIsSet(self::$absoluteApp);
        return self::join(self::$absoluteApp, $pathPartsToAppend);
124
125
126
    }

    /**
127
     * @param array $pathPartsToAppend
128
129
130
     * @return string
     * @throws \UserFormException
     */
131
    public static function absoluteExt(...$pathPartsToAppend): string
132
    {
133
        return self::absoluteApp(self::appToExt(), $pathPartsToAppend);
134
135
    }

136
    /**
137
     * @param array $pathPartsToAppend
138
139
140
     * @return string
     * @throws \UserFormException
     */
141
    public static function absoluteLog(...$pathPartsToAppend): string
142
    {
143
144
145
        if (is_null(self::$absoluteLog)) {
            self::findAbsoluteLog();
        }
146
147
        self::enforcePathIsSet(self::$absoluteLog);
        return self::join(self::$absoluteLog, $pathPartsToAppend);
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
    }

    /**
     * @return string
     * @throws \UserFormException
     */
    public static function absoluteSqlLogFile(): string
    {
        if (is_null(self::$overloadAbsoluteSqlLogFile)) {
            return self::absoluteLog(self::LOG_TO_SQL_LOG_FILE_DEFAULT);
        }
        return self::$overloadAbsoluteSqlLogFile;
    }

    /**
     * @return string
     * @throws \UserFormException
     */
    public static function absoluteQfqLogFile(): string
    {
        if (is_null(self::$overloadAbsoluteQfqLogFile)) {
            return self::absoluteLog(self::LOG_TO_QFQ_LOG_FILE_DEFAULT);
        }
        return self::$overloadAbsoluteQfqLogFile;
    }

    /**
     * @return string
     * @throws \UserFormException
     */
    public static function absoluteMailLogFile(): string
    {
        if (is_null(self::$overloadAbsoluteMailLogFile)) {
            return self::absoluteLog(self::LOG_TO_MAIL_LOG_FILE_DEFAULT);
        }
        return self::$overloadAbsoluteMailLogFile;
    }

186
    /**
187
     * @param array $pathPartsToAppend
188
189
190
     * @return string
     * @throws \UserFormException
     */
191
    public static function appToProject(...$pathPartsToAppend): string
192
    {
193
        self::enforcePathIsSet(self::$appToProject);
194
        return self::join(self::$appToProject, $pathPartsToAppend);
195
196
    }

197
    /**
198
     * @param array $pathPartsToAppend
199
200
201
     * @return string
     * @throws \UserFormException
     */
202
    public static function absoluteProject(...$pathPartsToAppend): string
203
    {
204
        return self::absoluteApp(self::appToProject($pathPartsToAppend));
205
206
    }

207
208
209
210
211
    /**
     * @param array $pathPartsToAppend
     * @return string
     * @throws \UserFormException
     */
212
    public static function absoluteConf(...$pathPartsToAppend): string
213
    {
214
        return self::absoluteProject(self::PROJECT_TO_CONF, $pathPartsToAppend);
215
216
    }

217
    /**
218
     * @param array $pathPartsToAppend
219
220
221
     * @return string
     * @throws \UserFormException
     */
222
    public static function appToExt(...$pathPartsToAppend): string
223
    {
224
        return self::join(self::APP_TO_EXT, $pathPartsToAppend);
225
226
    }

227
228
229
    /**
     * @param mixed ...$pathPartsToAppend
     * @return string
230
     * @throws \UserFormException
231
232
233
     */
    public static function urlApp(...$pathPartsToAppend): string
    {
234
235
236
237
238
        // ensure base url is configured
        if (is_null(self::$urlApp) || self::$urlApp === '') {
            Thrower::userFormException('Base url not configured.', 'Go to QFQ extension configuration in the Typo3 backend and fill in a value for config.baseUrl');
        }
        return self::join(self::$urlApp, $pathPartsToAppend);
239
240
    }

241
    /**
242
     * @param array $pathPartsToAppend
243
244
245
     * @return string
     * @throws \UserFormException
     */
246
    public static function urlExt(...$pathPartsToAppend): string
247
    {
248
        return self::urlApp(self::appToExt($pathPartsToAppend));
249
250
251
    }

    /**
252
     * @param array $pathPartsToAppend
253
254
255
     * @return string
     * @throws \UserFormException
     */
256
    public static function appToApi(...$pathPartsToAppend): string
257
    {
258
        return self::join(self::APP_TO_EXT, self::EXT_TO_API, $pathPartsToAppend);
259
260
    }

261
262
263
264
265
266
267
268
269
270
    /**
     * @param array $pathPartsToAppend
     * @return string
     * @throws \UserFormException
     */
    public static function urlApi(...$pathPartsToAppend): string
    {
        return self::urlApp(self::appToApi($pathPartsToAppend));
    }

271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
    /**
     * @param mixed ...$pathPartsToAppend
     * @return string
     */
    public static function projectToForm(...$pathPartsToAppend) : string
    {
        $projectToForm = is_null(self::$projectToForm) ? self::PROJECT_TO_FORM_DEFAULT : self::$projectToForm;
        return self::join($projectToForm, $pathPartsToAppend);
    }

    /**
     * @param mixed ...$pathPartsToAppend
     * @return string
     */
    public static function projectToReport(...$pathPartsToAppend) : string
    {
        $projectToReport = is_null(self::$projectToReport) ? self::PROJECT_TO_REPORT_DEFAULT : self::$projectToReport;
        return self::join($projectToReport, $pathPartsToAppend);
    }

    /**
     * @param string $newPath
     */
    public static function setProjectToForm(string $newPath)
    {
        self::$projectToForm = $newPath;
    }

    /**
     * @param string $newPath
     */
    public static function setProjectToReport(string $newPath)
    {
        self::$projectToReport = $newPath;
    }

307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    /**
     * @param string $newPath
     * @throws \UserFormException
     */
    public static function setAbsoluteSqlLogFile(string $newPath)
    {
        self::enforcePathIsAbsolute($newPath);
        self::$overloadAbsoluteSqlLogFile = $newPath;
    }

    /**
     * @param string $newPath
     * @throws \UserFormException
     */
    public static function setAbsoluteQfqLogFile(string $newPath)
    {
        self::enforcePathIsAbsolute($newPath);
        self::$overloadAbsoluteQfqLogFile = $newPath;
    }

    /**
     * @param string $newPath
     * @throws \UserFormException
     */
    public static function setAbsoluteMailLogFile(string $newPath)
    {
        self::enforcePathIsAbsolute($newPath);
        self::$overloadAbsoluteMailLogFile = $newPath;
    }

337
338
339
340
341
342
343
344
    /**
     * @param $urlApp
     */
    public static function setUrlApp($urlApp)
    {
        self::$urlApp = $urlApp;
    }

345
346
347
    /**
     * Override the paths of sql.log, qfq.log, mail.log using the values from the config file or Typo3.
     *
348
     * @throws \CodeException
349
     * @throws \UserFormException
350
     * @throws \UserReportException
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
     */
    public static function overrideLogPathsFromConfig()
    {
        // QFQ log
        $absoluteQfqLogFile = Config::get(SYSTEM_QFQ_LOG_PATHFILENAME);
        if (!empty($absoluteQfqLogFile)) {
            self::setAbsoluteQfqLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteQfqLogFile));
        }

        // Mail log
        $absoluteMailLogFile = Config::get(SYSTEM_MAIL_LOG_PATHFILENAME);
        if (!empty($absoluteMailLogFile)) {
            self::setAbsoluteMailLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteMailLogFile));
        }

        // SQL log
        $absoluteSqlLogFile = Config::get(SYSTEM_SQL_LOG_PATHFILENAME);
        if (!empty($absoluteSqlLogFile)) {
            self::setAbsoluteSqlLogFile(self::joinIfNotAbsolute(self::absoluteApp(), $absoluteSqlLogFile));
        }
    }

    /**
     * Return the second path if it is absolute. Otherwise concatenate and return the two paths.
     *
     * @param string $path1
     * @param string $path2
     * @return string
     * @throws \UserFormException
     */
    public static function joinIfNotAbsolute (string $path1, string $path2): string
    {
        if ($path2 !== '' && $path2[0] === '/') {
            return $path2;
        }
        return self::join($path1, $path2);
    }

389
    /**
390
391
     * Join the arguments together as a path. Array arguments are flattened!
     * Trailing path parts may not start with '/'.
392
     *
393
394
395
     * join(['this', 'is'], 'my/wonderful', [['path/of', 'nice'], 'things']) === 'this/is/my/wonderful/path/of/nice/things'
     *
     * @param array $pathPartsToAppend
396
397
     * @return string
     */
398
    public static function join(...$pathPartsToAppend): string
399
    {
400
        $parts = $pathPartsToAppend;
401
402
403
404

        // concatenate all parts (arrays are flattened)
        $path = '';
        array_walk_recursive($parts, function ($part) use (&$path) {
405

406
            // filter out empty string and null arguments
407
408
            if (is_null($part) || $part === '') {
                return;
409
410
            }

411
            // first part added without '/'
412
413
414
415
            if ($path === '') {
                $path .= $part;
            } else {

416
                // trailing path parts may not be absolute
417
418
419
420
421
422
423
424
425
426
                if ($part[0] === '/') {
                    throw new \UserFormException(json_encode([
                        ERROR_MESSAGE_TO_USER => 'Failed: Join path.',
                        ERROR_MESSAGE_TO_DEVELOPER => "Trailing path parts may not start with '/'. Trailing part: '$part'."]),
                        ERROR_PATH_INVALID);
                }

                $path .= '/' . $part;
            }
        });
427

428
429
430
431
432
433
434
435
436
437
        // remove multiple occurrences of '/' (but keep http://)
        if (preg_match('/^\w*:\/\//', $path, $match) ) {
            $protocol = $match[0];
            $path = substr($path, strlen($protocol));
        } else {
            $protocol = '';
        }
        $path = preg_replace('/\/{2,}/','/', $path);

        return $protocol . $path;
438
    }
439
440
441

    ///////////////////////////////////////////////////  Private  //////////////////////////////////////////////////////

442
    /**
443
444
     * @param string $newPath
     */
445
    private static function setAbsoluteApp(string $newPath)
446
    {
447
        self::$absoluteApp = $newPath;
448
449
450
451
452
453
454
455
456
457
458
459
460
    }

    /**
     * @param string $newPath
     */
    private static function setAppToProject(string $newPath)
    {
        self::$appToProject = $newPath;
    }

    /**
     * @param string $newPath
     */
461
    private static function setAbsoluteLog(string $newPath)
462
    {
463
        self::$absoluteLog = $newPath;
464
465
466
    }

    /**
467
468
469
470
471
     * Searches these places for log directory:
     *   1) project-directory/log
     *   2) fileadmin/protected/log
     * If not found create log dir in: project-directory/log
     *
472
473
     * @throws \UserFormException
     */
474
    private static function findAbsoluteLog()
475
    {
476
477
478
479
480
        if (!is_null(self::$absoluteLog)) {
            // only execute once
            return;
        }

Marc Egger's avatar
Marc Egger committed
481
        // search log dir qfqProject/log
482
483
484
        $absoluteLog = self::absoluteApp(self::appToProject(self::PROJECT_TO_LOG_DEFAULT));
        if (file_exists($absoluteLog)) {
            self::setAbsoluteLog($absoluteLog);
485

Marc Egger's avatar
Marc Egger committed
486
        // search log dir fileadmin/protected/log
487
488
        } elseif (file_exists(self::absoluteApp(self::APP_TO_LOG_IN_PROTECTED))) {
            self::setAbsoluteLog(self::absoluteApp(self::APP_TO_LOG_IN_PROTECTED));
489

Marc Egger's avatar
Marc Egger committed
490
        // create default log dir qfqProject/log
491
        } else {
492
493
            HelperFile::createPathRecursive($absoluteLog);
            self::setAbsoluteLog($absoluteLog);
494
495
496
        }
    }

497
498
499
500
501
502
    /**
     * Read the project location from qfq.project.path.php or create the file with default path.
     *
     * @throws \CodeException
     * @throws \UserFormException
     */
503
    private static function findAppToProject()
504
    {
505
506
507
508
509
        if (!is_null(self::$appToProject)) {
            // only execute once
            return;
        }

510
        // does qfq.project.path.php exist? => read path
511
512
513
        $absoluteProjectPathFile = self::absoluteApp(PROJECT_PATH_PHP_FILE);
        if (HelperFile::isReadableException($absoluteProjectPathFile)) {
            self::setAppToProject(HelperFile::include($absoluteProjectPathFile));
514

515
        // does the deprecated config.qfq.php exist? => fileadmin/protected/qfqProject & migrate to qfq.json
516
517
        } elseif (HelperFile::isReadableException(self::absoluteApp(self::APP_TO_TYPO3_CONF, CONFIG_QFQ_PHP))) {
            HelperFile::createPathRecursive(self::absoluteApp(self::APP_TO_PROJECT_IN_PROTECTED));
Marc Egger's avatar
Marc Egger committed
518
            self::setAppToProject(self::APP_TO_PROJECT_IN_PROTECTED);
519
            Config::migrateConfigPhpToJson();
520
            self::writeProjectPathPhp();
521

522
        // does fileadmin exist? => fileadmin/protected/qfqProject
523
524
        } elseif (file_exists(self::absoluteApp(self::APP_TO_FILEADMIN))) {
            HelperFile::createPathRecursive(self::absoluteApp(self::APP_TO_PROJECT_IN_PROTECTED));
525
            self::setAppToProject(self::APP_TO_PROJECT_IN_PROTECTED);
Marc Egger's avatar
Marc Egger committed
526
527
            self::writeProjectPathPhp();

528
        // else => folder above APP
529
530
531
532
533
        } else {
            self::setAppToProject(self::APP_TO_PROJECT_DEFAULT);
            self::writeProjectPathPhp();
        }
    }
Marc Egger's avatar
Marc Egger committed
534

535
536
537
538
539
540
541
542
    /**
     * Find the absolute path of the App directory using the path of this file.
     * Fails if typo3conf is not found in that path.
     *
     * @throws \UserFormException
     */
    private static function findAbsoluteApp()
    {
543
        // look for typo3conf directory
544
545
546
547
548
549
550
551
552
553
        $absoluteApp = self::realpath(self::join(__DIR__, '../../../../../../'));
        if (!file_exists(self::join($absoluteApp, self::APP_TO_TYPO3_CONF)))
        {
            Thrower::userFormException('App path seems to be wrong: Directory "typo3conf" not found in app path.'
                , "Current app path: $absoluteApp");
        }

        return $absoluteApp;
    }

Marc Egger's avatar
Marc Egger committed
554
555
556
557
558
559
560
561
562
563
564
565
566
    /**
     * Write the project path configuration file to the project directory.
     *
     * @throws \UserFormException
     */
    private static function writeProjectPathPhp()
    {
        $appToProject = self::appToProject();
        $fileContent = <<<EOF
<?php

/**
QFQ project path configuration
Marc Egger's avatar
Marc Egger committed
567
!! ATTENTION !!: The files in the project directory should NOT be served by your http server! 
Marc Egger's avatar
Marc Egger committed
568
569
570
571
572
Only exception: The app directory inside the project directory may be served.
*/

return '$appToProject'; // path relative to app directory (i.e. location of this file).
EOF;
573
        HelperFile::file_put_contents(self::absoluteApp(PROJECT_PATH_PHP_FILE), $fileContent);
Marc Egger's avatar
Marc Egger committed
574
    }
575
576

    /**
577
578
     * Throw an exception if the given path is not set (i.e. === null).
     *
579
580
581
582
583
     * @param $path
     * @throws \UserFormException
     */
    private static function enforcePathIsSet($path) {
        if(is_null($path)) {
584
            Thrower::userFormException('Path accessed before set.', 'Make sure Path::setMainPaths(...) is called before this code is executed..');
585
586
        }
    }
587
588

    /**
589
590
     * Throw exception if path does not start with '/'
     *
591
592
593
     * @param $path
     * @throws \UserFormException
     */
594
595
596
597
598
599
600
601
    private static function enforcePathIsAbsolute(string $path) {
        if($path !== '' && $path[0] === '/') {
            return;
        }
        Thrower::userFormException('Path is not absolute', "Path does not start with '/' : $path");
    }

    /**
602
     * PHP System function: realpath() with QFQ exception.
603
     * The first argument must be a path to something that exists. Otherwise an exception is thrown!
604
     *
605
     * @param string $pathToSomethingThatExists This file/dir MUST exist! Otherwise exception!
606
     * @param array $pathPartsToAppend The appended path doesnt have to exist.
607
608
609
     * @return string
     * @throws \UserFormException
     */
610
    private static function realpath(string $pathToSomethingThatExists, ...$pathPartsToAppend): string
611
    {
612
        $absolutePath = realpath($pathToSomethingThatExists);
613
        if ($absolutePath === false) {
614
            Thrower::userFormException('Path not found.', "Either path does not exist or access not permitted. Make sure the whole path is executable by the current user. Path: $pathToSomethingThatExists.");
615
        }
616
        return self::join($absolutePath, $pathPartsToAppend);
617
    }
618
619
620
621
622
623
624
625
626
627
628
629

    /**
     * Returns true if the given path contains the double dot operator '..'.
     * File/directory names which contain '..' are not counted.
     *
     * @param string $path
     * @return bool
     */
    private static function containsDoubleDot(string $path)
    {
        return $path === '..' || OnString::strStartsWith($path, '../') || OnString::strEndsWith($path, '/..' ) || OnString::strContains($path, '/../');
    }
630
}