Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ce625dc

Browse filesBrowse files
committed
Parse and render anonymous classes correctly on php 8.
1 parent a14d8f9 commit ce625dc
Copy full SHA for ce625dc

23 files changed

+156
-51
lines changed

‎src/Symfony/Component/Console/Application.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Application.php
+8-5Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -863,17 +863,20 @@ private function doActuallyRenderThrowable(\Throwable $e, OutputInterface $outpu
863863
do {
864864
$message = trim($e->getMessage());
865865
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
866-
$class = \get_class($e);
867-
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
866+
$class = get_debug_type($e);
868867
$title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
869868
$len = Helper::strlen($title);
870869
} else {
871870
$len = 0;
872871
}
873872

874-
if (false !== strpos($message, "class@anonymous\0")) {
875-
$message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
876-
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
873+
if (str_contains($message, "@anonymous\0")) {
874+
$message = preg_replace_callback('/([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
875+
if ('class' === $m[1]) {
876+
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: 'class').'@anonymous' : $m[0];
877+
}
878+
879+
return $m[1].'@anonymous';
877880
}, $message);
878881
}
879882

‎src/Symfony/Component/Console/Tests/ApplicationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/ApplicationTest.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ public function testRenderAnonymousException()
912912
$tester = new ApplicationTester($application);
913913

914914
$tester->run(['command' => 'foo'], ['decorated' => false]);
915-
$this->assertStringContainsString('Dummy type "@anonymous" is invalid.', $tester->getDisplay(true));
915+
$this->assertStringContainsString('Dummy type "class@anonymous" is invalid.', $tester->getDisplay(true));
916916
}
917917

918918
public function testRenderExceptionStackTraceContainsRootException()
@@ -935,7 +935,7 @@ public function testRenderExceptionStackTraceContainsRootException()
935935
$tester = new ApplicationTester($application);
936936

937937
$tester->run(['command' => 'foo'], ['decorated' => false]);
938-
$this->assertStringContainsString('Dummy type "@anonymous" is invalid.', $tester->getDisplay(true));
938+
$this->assertStringContainsString('Dummy type "class@anonymous" is invalid.', $tester->getDisplay(true));
939939
}
940940

941941
public function testRun()

‎src/Symfony/Component/Console/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/composer.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"php": ">=7.1.3",
2020
"symfony/polyfill-mbstring": "~1.0",
2121
"symfony/polyfill-php73": "^1.8",
22+
"symfony/polyfill-php80": "^1.17",
2223
"symfony/service-contracts": "^1.1|^2"
2324
},
2425
"require-dev": {

‎src/Symfony/Component/Debug/ErrorHandler.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/ErrorHandler.php
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ public function handleError($type, $message, $file, $line)
415415
$context = $e;
416416
}
417417

418-
if (false !== strpos($message, "class@anonymous\0")) {
418+
if (false !== strpos($message, "@anonymous\0")) {
419419
$logMessage = $this->levels[$type].': '.(new FlattenException())->setMessage($message)->getMessage();
420420
} else {
421421
$logMessage = $this->levels[$type].': '.$message;
@@ -540,7 +540,7 @@ public function handleException($exception, array $error = null)
540540
$handlerException = null;
541541

542542
if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
543-
if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
543+
if (false !== strpos($message = $exception->getMessage(), "@anonymous\0")) {
544544
$message = (new FlattenException())->setMessage($message)->getMessage();
545545
}
546546
if ($exception instanceof FatalErrorException) {

‎src/Symfony/Component/Debug/Exception/FatalThrowableError.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/Exception/FatalThrowableError.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class FatalThrowableError extends FatalErrorException
2626

2727
public function __construct(\Throwable $e)
2828
{
29-
$this->originalClassName = \get_class($e);
29+
$this->originalClassName = get_debug_type($e);
3030

3131
if ($e instanceof \ParseError) {
3232
$severity = E_PARSE;

‎src/Symfony/Component/Debug/Exception/FlattenException.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/Exception/FlattenException.php
+13-5Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public static function createFromThrowable(\Throwable $exception, int $statusCod
6767
$e->setStatusCode($statusCode);
6868
$e->setHeaders($headers);
6969
$e->setTraceFromThrowable($exception);
70-
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
70+
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : get_debug_type($exception));
7171
$e->setFile($exception->getFile());
7272
$e->setLine($exception->getLine());
7373

@@ -134,7 +134,11 @@ public function getClass()
134134
*/
135135
public function setClass($class)
136136
{
137-
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
137+
if (preg_match('/^([\\\\\w]+)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', $class, $matches)) {
138+
$this->class = ('class' === $matches[1] ? get_parent_class($matches[0]) : $matches[1]).'@anonymous';
139+
} else {
140+
$this->class = $class;
141+
}
138142

139143
return $this;
140144
}
@@ -179,9 +183,13 @@ public function getMessage()
179183
*/
180184
public function setMessage($message)
181185
{
182-
if (false !== strpos($message, "class@anonymous\0")) {
183-
$message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
184-
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
186+
if (str_contains($message, "@anonymous\0")) {
187+
$message = preg_replace_callback('/([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
188+
if ('class' === $m[1]) {
189+
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: 'class').'@anonymous' : $m[0];
190+
}
191+
192+
return $m[1].'@anonymous';
185193
}, $message);
186194
}
187195

‎src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,24 @@ public function testHandleUserError()
325325
}
326326
}
327327

328+
public function testHandleErrorWithAnonymousClass(): void
329+
{
330+
$handler = ErrorHandler::register();
331+
$handler->throwAt(E_USER_WARNING, true);
332+
try {
333+
$handler->handleError(E_USER_WARNING, 'foo '.\get_class(new class() extends \stdClass {
334+
}).' bar', 'foo.php', 12);
335+
$this->fail('Exception expected.');
336+
} catch (\ErrorException $e) {
337+
$this->assertSame('User Warning: foo stdClass@anonymous bar', $e->getMessage());
338+
$this->assertSame(E_USER_WARNING, $e->getSeverity());
339+
$this->assertSame('foo.php', $e->getFile());
340+
$this->assertSame(12, $e->getLine());
341+
} finally {
342+
restore_error_handler();
343+
}
344+
}
345+
328346
public function testHandleDeprecation()
329347
{
330348
$logArgCheck = function ($level, $message, $context) {

‎src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,11 @@ public function testAnonymousClass()
355355

356356
$this->assertSame('RuntimeException@anonymous', $flattened->getClass());
357357

358+
$flattened->setClass(\get_class(new class('Oops') extends NotFoundHttpException {
359+
}));
360+
361+
$this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass());
362+
358363
$flattened = FlattenException::create(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException {
359364
}))));
360365

‎src/Symfony/Component/Debug/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/composer.json
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
],
1818
"require": {
1919
"php": ">=7.1.3",
20-
"psr/log": "~1.0"
20+
"psr/log": "~1.0",
21+
"symfony/polyfill-php80": "~1.17"
2122
},
2223
"conflict": {
2324
"symfony/http-kernel": "<3.4"

‎src/Symfony/Component/ErrorHandler/DebugClassLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ErrorHandler/DebugClassLoader.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
407407
}
408408
$deprecations = [];
409409

410-
$className = isset($class[15]) && "\0" === $class[15] && 0 === strpos($class, "class@anonymous\x00") ? get_parent_class($class).'@anonymous' : $class;
410+
$className = $refl->isAnonymous() ? (($parentRefl = $refl->getParentClass()) ? $parentRefl->name : 'class').'@anonymous' : $class;
411411

412412
// Don't trigger deprecations for classes in the same vendor
413413
if ($class !== $className) {

‎src/Symfony/Component/ErrorHandler/ErrorHandler.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ErrorHandler/ErrorHandler.php
+8-4Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ public function handleError(int $type, string $message, string $file, int $line)
435435
$context = $e;
436436
}
437437

438-
if (false !== strpos($message, "class@anonymous\0")) {
438+
if (false !== strpos($message, "@anonymous\0")) {
439439
$logMessage = $this->parseAnonymousClass($message);
440440
} else {
441441
$logMessage = $this->levels[$type].': '.$message;
@@ -558,7 +558,7 @@ public function handleException(\Throwable $exception)
558558
}
559559

560560
if ($this->loggedErrors & $type) {
561-
if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) {
561+
if (false !== strpos($message = $exception->getMessage(), "@anonymous\0")) {
562562
$message = $this->parseAnonymousClass($message);
563563
}
564564

@@ -768,8 +768,12 @@ private function cleanTrace(array $backtrace, int $type, string $file, int $line
768768
*/
769769
private function parseAnonymousClass(string $message): string
770770
{
771-
return preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
772-
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
771+
return preg_replace_callback('/([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) {
772+
if ('class' === $m[1]) {
773+
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: 'class').'@anonymous' : $m[0];
774+
}
775+
776+
return $m[1].'@anonymous';
773777
}, $message);
774778
}
775779
}

‎src/Symfony/Component/ErrorHandler/Exception/FlattenException.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ErrorHandler/Exception/FlattenException.php
+13-5Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public static function createFromThrowable(\Throwable $exception, int $statusCod
7171
$e->setStatusCode($statusCode);
7272
$e->setHeaders($headers);
7373
$e->setTraceFromThrowable($exception);
74-
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : \get_class($exception));
74+
$e->setClass($exception instanceof FatalThrowableError ? $exception->getOriginalClassName() : get_debug_type($exception));
7575
$e->setFile($exception->getFile());
7676
$e->setLine($exception->getLine());
7777

@@ -138,7 +138,11 @@ public function getClass(): string
138138
*/
139139
public function setClass($class): self
140140
{
141-
$this->class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
141+
if (preg_match('/^([\\\\\w]+)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', $class, $matches)) {
142+
$this->class = ('class' === $matches[1] ? get_parent_class($matches[0]) : $matches[1]).'@anonymous';
143+
} else {
144+
$this->class = $class;
145+
}
142146

143147
return $this;
144148
}
@@ -195,9 +199,13 @@ public function getMessage(): string
195199
*/
196200
public function setMessage($message): self
197201
{
198-
if (false !== strpos($message, "class@anonymous\0")) {
199-
$message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
200-
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
202+
if (str_contains($message, "@anonymous\0")) {
203+
$message = preg_replace_callback('/([\\\\\w]+)@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
204+
if ('class' === $m[1]) {
205+
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
206+
}
207+
208+
return $m[1].'@anonymous';
201209
}, $message);
202210
}
203211

‎src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,24 @@ public function testHandleUserError()
365365
}
366366
}
367367

368+
public function testHandleErrorWithAnonymousClass(): void
369+
{
370+
$handler = ErrorHandler::register();
371+
$handler->throwAt(3, true);
372+
try {
373+
$handler->handleError(3, 'foo '.\get_class(new class() extends \stdClass {
374+
}).' bar', 'foo.php', 12);
375+
$this->fail('Exception expected.');
376+
} catch (\ErrorException $e) {
377+
$this->assertSame('foo stdClass@anonymous bar', $e->getMessage());
378+
$this->assertSame(3, $e->getSeverity());
379+
$this->assertSame('foo.php', $e->getFile());
380+
$this->assertSame(12, $e->getLine());
381+
} finally {
382+
restore_error_handler();
383+
}
384+
}
385+
368386
public function testHandleDeprecation()
369387
{
370388
$logArgCheck = function ($level, $message, $context) {
@@ -433,6 +451,10 @@ public function handleExceptionProvider(): array
433451
{
434452
return [
435453
['Uncaught Exception: foo', new \Exception('foo')],
454+
['Uncaught Exception: foo', new class('foo') extends \RuntimeException {
455+
}],
456+
['Uncaught Exception: foo stdClass@anonymous bar', new \RuntimeException('foo '.\get_class(new class() extends \stdClass {
457+
}).' bar')],
436458
['Uncaught Error: bar', new \Error('bar')],
437459
['Uncaught ccc', new \ErrorException('ccc')],
438460
];

‎src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,11 @@ public function testAnonymousClass()
373373

374374
$this->assertSame('RuntimeException@anonymous', $flattened->getClass());
375375

376+
$flattened->setClass(\get_class(new class('Oops') extends NotFoundHttpException {
377+
}));
378+
379+
$this->assertSame('Symfony\Component\HttpKernel\Exception\NotFoundHttpException@anonymous', $flattened->getClass());
380+
376381
$flattened = FlattenException::createFromThrowable(new \Exception(sprintf('Class "%s" blah.', \get_class(new class() extends \RuntimeException {
377382
}))));
378383

‎src/Symfony/Component/ErrorHandler/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/ErrorHandler/composer.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"php": ">=7.1.3",
2020
"psr/log": "~1.0",
2121
"symfony/debug": "^4.4.5",
22+
"symfony/polyfill-php80": "^1.17",
2223
"symfony/var-dumper": "^4.4|^5.0"
2324
},
2425
"require-dev": {

‎src/Symfony/Component/HttpKernel/Kernel.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Kernel.php
+3-6Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,7 @@ public function getBundles()
228228
public function getBundle($name)
229229
{
230230
if (!isset($this->bundles[$name])) {
231-
$class = static::class;
232-
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
233-
234-
throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, $class));
231+
throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, get_debug_type($this)));
235232
}
236233

237234
return $this->bundles[$name];
@@ -473,8 +470,8 @@ protected function build(ContainerBuilder $container)
473470
*/
474471
protected function getContainerClass()
475472
{
476-
$class = static::class;
477-
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class;
473+
$class = get_debug_type($this);
474+
$class = str_ends_with($class, '@anonymous') ? get_parent_class($this).str_replace('.', '_', ContainerBuilder::hash($class)) : $class;
478475
$class = $this->name.str_replace('\\', '_', $class).ucfirst($this->environment).($this->debug ? 'Debug' : '').'Container';
479476

480477
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) {

‎src/Symfony/Component/HttpKernel/Tests/KernelTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/KernelTest.php
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,27 @@ public function testKernelStartTimeIsResetWhileBootingAlreadyBootedKernel()
640640
$this->assertGreaterThan($preReBoot, $kernel->getStartTime());
641641
}
642642

643+
public function testAnonymousKernelGeneratesValidContainerClass(): void
644+
{
645+
$kernel = new class('test', true) extends Kernel {
646+
public function registerBundles(): iterable
647+
{
648+
return [];
649+
}
650+
651+
public function registerContainerConfiguration(LoaderInterface $loader): void
652+
{
653+
}
654+
655+
public function getContainerClass(): string
656+
{
657+
return parent::getContainerClass();
658+
}
659+
};
660+
661+
$this->assertRegExp('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*TestDebugContainer$/', $kernel->getContainerClass());
662+
}
663+
643664
/**
644665
* Returns a mock for the BundleInterface.
645666
*/

‎src/Symfony/Component/HttpKernel/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/composer.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"symfony/http-foundation": "^4.4|^5.0",
2323
"symfony/polyfill-ctype": "^1.8",
2424
"symfony/polyfill-php73": "^1.9",
25+
"symfony/polyfill-php80": "^1.17",
2526
"psr/log": "~1.0"
2627
},
2728
"require-dev": {

‎src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Messenger/Middleware/TraceableMiddleware.php
+1-2Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,7 @@ public function next(): MiddlewareInterface
7878
if ($this->stack === $nextMiddleware = $this->stack->next()) {
7979
$this->currentEvent = 'Tail';
8080
} else {
81-
$class = \get_class($nextMiddleware);
82-
$this->currentEvent = sprintf('"%s"', 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class);
81+
$this->currentEvent = sprintf('"%s"', get_debug_type($nextMiddleware));
8382
}
8483
$this->currentEvent .= sprintf(' on "%s"', $this->busName);
8584

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.