Page MenuHomePhorge

Deprecation notice at PhabricatorEditorURIEngine.php:283 when no RepositorySlug set
Open, NormalPublic

Description

Steps to reproduce
  1. Unclear, wait for deprecation warning for appear…?

See also Report Warnings.

/config/ Version table
BinaryVersionPath
php8.1.2-1ubuntu2.15apache2handler
...skipped, the log line 2 has arcanist/phorge version...

1preg_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated
2arcanist(head=stable, ref.master=5bc53cfe53d0, ref.stable=ca72430916af), phorge(head=stable, ref.master=1b49165ddd16, ref.stable=d519f75dfdee, custom=1)
3#0 preg_replace(string, string, NULL) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:283]
4#1 PhabricatorEditorURIEngine::escapeToken(NULL) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:144]
5#2 PhabricatorEditorURIEngine::newURITokensForRepository() called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:128]
6#3 PhabricatorEditorURIEngine::getURITokensForRepository(string) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:84]
7#4 PhabricatorEditorURIEngine::getURITokensForPath(string) called at [<phorge>/src/applications/differential/view/DifferentialChangesetDetailView.php:346]
8#5 DifferentialChangesetDetailView::getEditorURITemplate() called at [<phorge>/src/applications/differential/view/DifferentialChangesetDetailView.php:256]
9#6 DifferentialChangesetDetailView::render() called at [<phorge>/src/applications/differential/view/DifferentialChangesetListView.php:240]
10#7 DifferentialChangesetListView::render() called at [<phorge>/src/view/AphrontView.php:222]
11#8 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
12#9 phutil_escape_html(DifferentialChangesetListView) called at [<phorge>/src/infrastructure/markup/render.php:139]
13#10 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:139]
14#11 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:139]
15#12 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
16#13 phutil_tag(string, array, array) called at [<phorge>/src/view/phui/PHUITwoColumnView.php:242]
17#14 PHUITwoColumnView::buildFooter() called at [<phorge>/src/view/phui/PHUITwoColumnView.php:123]
18#15 PHUITwoColumnView::getTagContent() called at [<phorge>/src/view/AphrontTagView.php:161]
19#16 AphrontTagView::render() called at [<phorge>/src/view/AphrontView.php:222]
20#17 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
21#18 phutil_escape_html(PHUITwoColumnView) called at [<phorge>/src/infrastructure/markup/render.php:139]
22#19 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:139]
23#20 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
24#21 phutil_tag(string, array, array) called at [<phorge>/src/view/formation/PHUIFormationContentView.php:18]
25#22 PHUIFormationContentView::render() called at [<phorge>/src/view/AphrontView.php:222]
26#23 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
27#24 phutil_escape_html(PHUIFormationContentView) called at [<phorge>/src/infrastructure/markup/render.php:139]
28#25 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
29#26 phutil_tag(string, array, array) called at [<phorge>/src/view/formation/PHUIFormationView.php:67]
30#27 PHUIFormationView::render() called at [<phorge>/src/view/AphrontView.php:222]
31#28 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
32#29 phutil_escape_html(PHUIFormationView) called at [<phorge>/src/infrastructure/markup/render.php:171]
33#30 phutil_implode_html(string, array) called at [<phorge>/src/view/page/PhabricatorBarePageView.php:58]
34#31 PhabricatorBarePageView::willRenderPage() called at [<phorge>/src/view/page/PhabricatorStandardPageView.php:217]
35#32 PhabricatorStandardPageView::willRenderPage() called at [<phorge>/src/view/page/AphrontPageView.php:46]
36#33 AphrontPageView::render() called at [<phorge>/src/view/page/PhabricatorStandardPageView.php:919]
37#34 PhabricatorStandardPageView::produceAphrontResponse() called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:722]
38#35 AphrontApplicationConfiguration::produceResponse(AphrontRequest, PhabricatorStandardPageView) called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:299]
39#36 AphrontApplicationConfiguration::processRequest(AphrontRequest, PhutilDeferredLog, AphrontPHPHTTPSink, MultimeterControl) called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:204]
40#37 AphrontApplicationConfiguration::runHTTPRequest(AphrontPHPHTTPSink) called at [<phorge>/webroot/index.php:35]

Related Objects

View Standalone Graph
This task is connected to more than 200 other tasks. Only direct parents and subtasks are shown here. Use View Standalone Graph to show more of the graph.

Event Timeline

revi triaged this task as Normal priority.
revi created this object in space S1 Public.

@revi: Could you please double-check and confirm that the line 144 in <phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php is
'n' => $this->escapeToken($repository->getRepositorySlug()),
in your installation? Thanks in advance!

1<?php
2
3final class PhabricatorEditorURIEngine
4 extends Phobject {
5
6 private $viewer;
7 private $repository;
8 private $pattern;
9 private $rawTokens;
10 private $repositoryTokens;
11
12 public static function newForViewer(PhabricatorUser $viewer) {
13 if (!$viewer->isLoggedIn()) {
14 return null;
15 }
16
17 $pattern = $viewer->getUserSetting(PhabricatorEditorSetting::SETTINGKEY);
18
19 if ($pattern === null || trim($pattern) === '') {
20 return null;
21 }
22
23 $engine = id(new self())
24 ->setViewer($viewer)
25 ->setPattern($pattern);
26
27 // If there's a problem with the pattern,
28
29 try {
30 $engine->validatePattern();
31 } catch (PhabricatorEditorURIParserException $ex) {
32 return null;
33 }
34
35 return $engine;
36 }
37
38 public function setViewer(PhabricatorUser $viewer) {
39 $this->viewer = $viewer;
40 return $this;
41 }
42
43 public function getViewer() {
44 return $this->viewer;
45 }
46
47 public function setRepository(PhabricatorRepository $repository) {
48 $this->repository = $repository;
49 return $this;
50 }
51
52 public function getRepository() {
53 return $this->repository;
54 }
55
56 public function setPattern($pattern) {
57 $this->pattern = $pattern;
58 return $this;
59 }
60
61 public function getPattern() {
62 return $this->pattern;
63 }
64
65 public function validatePattern() {
66 $this->getRawURITokens();
67 return true;
68 }
69
70 public function getURIForPath($path, $line) {
71 $tokens = $this->getURITokensForRepository($path);
72
73 $variables = array(
74 'f' => $this->escapeToken($path),
75 'l' => $this->escapeToken($line),
76 );
77
78 $tokens = $this->newTokensWithVariables($tokens, $variables);
79
80 return $this->newStringFromTokens($tokens);
81 }
82
83 public function getURITokensForPath($path) {
84 $tokens = $this->getURITokensForRepository($path);
85
86 $variables = array(
87 'f' => $this->escapeToken($path),
88 );
89
90 return $this->newTokensWithVariables($tokens, $variables);
91 }
92
93 public static function getVariableDefinitions() {
94 return array(
95 'f' => array(
96 'name' => pht('File Name'),
97 'example' => pht('path/to/source.c'),
98 ),
99 'l' => array(
100 'name' => pht('Line Number'),
101 'example' => '777',
102 ),
103 'n' => array(
104 'name' => pht('Repository Short Name'),
105 'example' => 'arcanist',
106 ),
107 'd' => array(
108 'name' => pht('Repository ID'),
109 'example' => '42',
110 ),
111 'p' => array(
112 'name' => pht('Repository PHID'),
113 'example' => 'PHID-REPO-abcdefghijklmnopqrst',
114 ),
115 'r' => array(
116 'name' => pht('Repository Callsign'),
117 'example' => 'XYZ',
118 ),
119 '%' => array(
120 'name' => pht('Literal Percent Symbol'),
121 'example' => '%',
122 ),
123 );
124 }
125
126 private function getURITokensForRepository() {
127 if (!$this->repositoryTokens) {
128 $this->repositoryTokens = $this->newURITokensForRepository();
129 }
130
131 return $this->repositoryTokens;
132 }
133
134 private function newURITokensForRepository() {
135 $tokens = $this->getRawURITokens();
136
137 $repository = $this->getRepository();
138 if (!$repository) {
139 throw new PhutilInvalidStateException('setRepository');
140 }
141
142 $variables = array(
143 'r' => $this->escapeToken($repository->getCallsign()),
144 'n' => $this->escapeToken($repository->getRepositorySlug()),
145 'd' => $this->escapeToken($repository->getID()),
146 'p' => $this->escapeToken($repository->getPHID()),
147 );
148
149 return $this->newTokensWithVariables($tokens, $variables);
150 }
151
152 private function getRawURITokens() {
153 if (!$this->rawTokens) {
154 $this->rawTokens = $this->newRawURITokens();
155 }
156 return $this->rawTokens;
157 }
158
159 private function newRawURITokens() {
160 $raw_pattern = $this->getPattern();
161 $raw_tokens = self::newPatternTokens($raw_pattern);
162
163 $variable_definitions = self::getVariableDefinitions();
164
165 foreach ($raw_tokens as $token) {
166 if ($token['type'] !== 'variable') {
167 continue;
168 }
169
170 $value = $token['value'];
171
172 if (isset($variable_definitions[$value])) {
173 continue;
174 }
175
176 throw new PhabricatorEditorURIParserException(
177 pht(
178 'Editor pattern "%s" is invalid: the pattern contains an '.
179 'unrecognized variable ("%s"). Use "%%%%" to encode a literal '.
180 'percent symbol.',
181 $raw_pattern,
182 '%'.$value));
183 }
184
185 $variables = array(
186 '%' => '%',
187 );
188
189 $tokens = $this->newTokensWithVariables($raw_tokens, $variables);
190
191 $first_literal = null;
192 if ($tokens) {
193 foreach ($tokens as $token) {
194 if ($token['type'] === 'literal') {
195 $first_literal = $token['value'];
196 }
197 break;
198 }
199
200 if ($first_literal === null) {
201 throw new PhabricatorEditorURIParserException(
202 pht(
203 'Editor pattern "%s" is invalid: the pattern must begin with '.
204 'a valid editor protocol, but begins with a variable. This is '.
205 'very sneaky and also very forbidden.',
206 $raw_pattern));
207 }
208 }
209
210 $uri = new PhutilURI($first_literal);
211 $editor_protocol = $uri->getProtocol();
212
213 if (!$editor_protocol) {
214 throw new PhabricatorEditorURIParserException(
215 pht(
216 'Editor pattern "%s" is invalid: the pattern must begin with '.
217 'a valid editor protocol, but does not begin with a recognized '.
218 'protocol string.',
219 $raw_pattern));
220 }
221
222 $allowed_key = 'uri.allowed-editor-protocols';
223 $allowed_protocols = PhabricatorEnv::getEnvConfig($allowed_key);
224 if (empty($allowed_protocols[$editor_protocol])) {
225 throw new PhabricatorEditorURIParserException(
226 pht(
227 'Editor pattern "%s" is invalid: the pattern must begin with '.
228 'a valid editor protocol, but the protocol "%s://" is not allowed.',
229 $raw_pattern,
230 $editor_protocol));
231 }
232
233 return $tokens;
234 }
235
236 private function newTokensWithVariables(array $tokens, array $variables) {
237 // Replace all "variable" tokens that we have replacements for with
238 // the literal value.
239 foreach ($tokens as $key => $token) {
240 $type = $token['type'];
241
242 if ($type == 'variable') {
243 $variable = $token['value'];
244 if (isset($variables[$variable])) {
245 $tokens[$key] = array(
246 'type' => 'literal',
247 'value' => $variables[$variable],
248 );
249 }
250 }
251 }
252
253 // Now, merge sequences of adjacent "literal" tokens into a single token.
254 $last_literal = null;
255 foreach ($tokens as $key => $token) {
256 $is_literal = ($token['type'] === 'literal');
257
258 if (!$is_literal) {
259 $last_literal = null;
260 continue;
261 }
262
263 if ($last_literal !== null) {
264 $tokens[$key]['value'] =
265 $tokens[$last_literal]['value'].$token['value'];
266 unset($tokens[$last_literal]);
267 }
268
269 $last_literal = $key;
270 }
271
272 $tokens = array_values($tokens);
273
274 return $tokens;
275 }
276
277 private function escapeToken($token) {
278 // Paths are user controlled, so a clever user could potentially make
279 // editor links do surprising things with paths containing "/../".
280
281 // Find anything that looks like "/../" and mangle it.
282
283 $token = preg_replace('((^|/)\.\.(/|\z))', '\1dot-dot\2', $token);
284
285 return phutil_escape_uri($token);
286 }
287
288 private function newStringFromTokens(array $tokens) {
289 $result = array();
290
291 foreach ($tokens as $token) {
292 $token_type = $token['type'];
293 $token_value = $token['value'];
294
295 $is_literal = ($token_type === 'literal');
296 if (!$is_literal) {
297 throw new Exception(
298 pht(
299 'Editor pattern token list can not be converted into a string: '.
300 'it still contains a non-literal token ("%s", of type "%s").',
301 $token_value,
302 $token_type));
303 }
304
305 $result[] = $token_value;
306 }
307
308 $result = implode('', $result);
309
310 return $result;
311 }
312
313 public static function newPatternTokens($raw_pattern) {
314 $token_positions = array();
315
316 $len = strlen($raw_pattern);
317
318 for ($ii = 0; $ii < $len; $ii++) {
319 $c = $raw_pattern[$ii];
320 if ($c === '%') {
321 if (!isset($raw_pattern[$ii + 1])) {
322 throw new PhabricatorEditorURIParserException(
323 pht(
324 'Editor pattern "%s" is invalid: the final character in a '.
325 'pattern may not be an unencoded percent symbol ("%%"). '.
326 'Use "%%%%" to encode a literal percent symbol.',
327 $raw_pattern));
328 }
329
330 $token_positions[] = $ii;
331 $ii++;
332 }
333 }
334
335 // Add a final marker past the end of the string, so we'll collect any
336 // trailing literal bytes.
337 $token_positions[] = $len;
338
339 $tokens = array();
340 $cursor = 0;
341 foreach ($token_positions as $pos) {
342 $token_len = ($pos - $cursor);
343
344 if ($token_len > 0) {
345 $tokens[] = array(
346 'type' => 'literal',
347 'value' => substr($raw_pattern, $cursor, $token_len),
348 );
349 }
350
351 $cursor = $pos;
352
353 if ($cursor < $len) {
354 $tokens[] = array(
355 'type' => 'variable',
356 'value' => substr($raw_pattern, $cursor + 1, 1),
357 );
358 }
359
360 $cursor = $pos + 2;
361 }
362
363 return $tokens;
364 }
365
366}

$ git show
commit d519f75dfdee61bb109468aa708c47f53f3e5128 (HEAD -> stable, origin/stable)
Author: Mark Jervelund <EMAIL-REDACTED>
Date:   Mon Feb 12 11:51:24 2024 +0100

(Commit message skipped)
aklapper renamed this task from Deprecation notice at PhabricatorEditorURIEngine.php:283 to Deprecation notice at PhabricatorEditorURIEngine.php:283 when no RepositorySlug set.Apr 22 2025, 08:10

Copying from Q182:

PhabricatorEditorURIEngine.php:283, occurrences: 4
preg_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated
arcanist(head=stable, ref.master=989690868513, ref.stable=05abd055019c), libphremoteuser(head=clarion, ref.master=944307decd9c, ref.clarion=d0da6c048bd5), phorge(head=stable, custom=4), translations(head=wmf/stable, ref.wmf/stable=76bb64361cec)
#0 preg_replace(string, string, NULL) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:283]
#1 PhabricatorEditorURIEngine::escapeToken(NULL) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:144]
#2 PhabricatorEditorURIEngine::newURITokensForRepository() called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:128]
#3 PhabricatorEditorURIEngine::getURITokensForRepository(string) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:84]
#4 PhabricatorEditorURIEngine::getURITokensForPath(string) called at [<phorge>/src/applications/differential/view/DifferentialChangesetDetailView.php:371]
#5 DifferentialChangesetDetailView::getEditorURITemplate() called at [<phorge>/src/applications/differential/view/DifferentialChangesetDetailView.php:272]
#6 DifferentialChangesetDetailView::render() called at [<phorge>/src/applications/differential/view/DifferentialChangesetListView.php:240]
#7 DifferentialChangesetListView::render() called at [<phorge>/src/view/AphrontView.php:222]
#8 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
#9 phutil_escape_html(DifferentialChangesetListView) called at [<phorge>/src/infrastructure/markup/render.php:139]
#10 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:139]
#11 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
#12 phutil_tag(string, array, array) called at [<phorge>/src/view/phui/PHUITwoColumnView.php:236]
#13 PHUITwoColumnView::buildFooter() called at [<phorge>/src/view/phui/PHUITwoColumnView.php:123]
#14 PHUITwoColumnView::getTagContent() called at [<phorge>/src/view/AphrontTagView.php:161]
#15 AphrontTagView::render() called at [<phorge>/src/view/AphrontView.php:222]
#16 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
#17 phutil_escape_html(PHUITwoColumnView) called at [<phorge>/src/infrastructure/markup/render.php:139]
#18 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:139]
#19 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
#20 phutil_tag(string, array, array) called at [<phorge>/src/view/formation/PHUIFormationContentView.php:13]
#21 PHUIFormationContentView::render() called at [<phorge>/src/view/AphrontView.php:222]
#22 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
#23 phutil_escape_html(PHUIFormationContentView) called at [<phorge>/src/infrastructure/markup/render.php:139]
#24 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
#25 phutil_tag(string, array, array) called at [<phorge>/src/view/formation/PHUIFormationView.php:58]
#26 PHUIFormationView::render() called at [<phorge>/src/view/AphrontView.php:222]
#27 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
#28 phutil_escape_html(PHUIFormationView) called at [<phorge>/src/infrastructure/markup/render.php:171]
#29 phutil_implode_html(string, array) called at [<phorge>/src/view/page/PhabricatorBarePageView.php:58]
#30 PhabricatorBarePageView::willRenderPage() called at [<phorge>/src/view/page/PhabricatorStandardPageView.php:217]
#31 PhabricatorStandardPageView::willRenderPage() called at [<phorge>/src/view/page/AphrontPageView.php:46]
#32 AphrontPageView::render() called at [<phorge>/src/view/page/PhabricatorStandardPageView.php:918]
#33 PhabricatorStandardPageView::produceAphrontResponse() called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:724]
#34 AphrontApplicationConfiguration::produceResponse(AphrontRequest, PhabricatorStandardPageView) called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:299]
#35 AphrontApplicationConfiguration::processRequest(AphrontRequest, PhutilDeferredLog, AphrontPHPHTTPSink, MultimeterControl) called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:203]
#36 AphrontApplicationConfiguration::runHTTPRequest(AphrontPHPHTTPSink) called at [<phorge>/webroot/index.php:35]

Looking at src/applications/repository/storage/PhabricatorRepository.php I see stuff like $has_shortname = ($this->getRepositorySlug() !== null); so it seems that a repository "slug" is what the UI calls a "short name". That's also seconded by code in DiffusionRepositoryEditEngine.

Going to some local repo like http://phorge.localhost/diffusion/CALL/manage/basics/ I see Short Name: No Short Name.
Going to its http://phorge.localhost/diffusion/edit/1/page/basics/ I see that the Short Name field is empty.

Other code also checks for its emptiness, e.g. in src/applications/diffusion/typeahead/DiffusionRepositoryDatasource.php:

$slug = $repository->getRepositorySlug();
if (phutil_nonempty_string($slug)) {

or in src/applications/repository/query/PhabricatorRepositoryQuery.php:

$slug = $repository->getRepositorySlug();
if ($slug === null) {

So I believe that the slug can be null and that we should add a check for that.