Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/util/PhabricatorSlug.php
12241 views
1
<?php
2
3
final class PhabricatorSlug extends Phobject {
4
5
public static function normalizeProjectSlug($slug) {
6
$slug = str_replace('/', ' ', $slug);
7
$slug = self::normalize($slug, $hashtag = true);
8
return rtrim($slug, '/');
9
}
10
11
public static function isValidProjectSlug($slug) {
12
$slug = self::normalizeProjectSlug($slug);
13
return ($slug != '_');
14
}
15
16
public static function normalize($slug, $hashtag = false) {
17
$slug = preg_replace('@/+@', '/', $slug);
18
$slug = trim($slug, '/');
19
$slug = phutil_utf8_strtolower($slug);
20
21
$ban =
22
// Ban control characters since users can't type them and they create
23
// various other problems with parsing and rendering.
24
"\\x00-\\x19".
25
26
// Ban characters with special meanings in URIs (and spaces), since we
27
// want slugs to produce nice URIs.
28
"#%&+=?".
29
" ".
30
31
// Ban backslashes and various brackets for parsing and URI quality.
32
"\\\\".
33
"<>{}\\[\\]".
34
35
// Ban single and double quotes since they can mess up URIs.
36
"'".
37
'"';
38
39
// In hashtag mode (used for Project hashtags), ban additional characters
40
// which cause parsing problems.
41
if ($hashtag) {
42
$ban .= '`~!@$^*,:;(|)';
43
}
44
45
$slug = preg_replace('(['.$ban.']+)', '_', $slug);
46
$slug = preg_replace('@_+@', '_', $slug);
47
48
$parts = explode('/', $slug);
49
50
// Remove leading and trailing underscores from each component, if the
51
// component has not been reduced to a single underscore. For example, "a?"
52
// converts to "a", but "??" converts to "_".
53
foreach ($parts as $key => $part) {
54
if ($part != '_') {
55
$parts[$key] = trim($part, '_');
56
}
57
}
58
$slug = implode('/', $parts);
59
60
// Specifically rewrite these slugs. It's OK to have a slug like "a..b",
61
// but not a slug which is only "..".
62
63
// NOTE: These are explicitly not pht()'d, because they should be stable
64
// across languages.
65
66
$replace = array(
67
'.' => 'dot',
68
'..' => 'dotdot',
69
);
70
71
foreach ($replace as $pattern => $replacement) {
72
$pattern = preg_quote($pattern, '@');
73
$slug = preg_replace(
74
'@(^|/)'.$pattern.'(\z|/)@',
75
'\1'.$replacement.'\2', $slug);
76
}
77
78
return $slug.'/';
79
}
80
81
public static function getDefaultTitle($slug) {
82
$parts = explode('/', trim($slug, '/'));
83
$default_title = end($parts);
84
$default_title = str_replace('_', ' ', $default_title);
85
$default_title = phutil_utf8_ucwords($default_title);
86
$default_title = nonempty($default_title, pht('Untitled Document'));
87
return $default_title;
88
}
89
90
public static function getAncestry($slug) {
91
$slug = self::normalize($slug);
92
93
if ($slug == '/') {
94
return array();
95
}
96
97
$ancestors = array(
98
'/',
99
);
100
101
$slug = explode('/', $slug);
102
array_pop($slug);
103
array_pop($slug);
104
105
$accumulate = '';
106
foreach ($slug as $part) {
107
$accumulate .= $part.'/';
108
$ancestors[] = $accumulate;
109
}
110
111
return $ancestors;
112
}
113
114
public static function getDepth($slug) {
115
$slug = self::normalize($slug);
116
if ($slug == '/') {
117
return 0;
118
} else {
119
return substr_count($slug, '/');
120
}
121
}
122
123
}
124
125