2019-10-13 05:59:52 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2017-10-26 16:43:43 +00:00
|
|
|
|
2020-02-06 21:48:18 +00:00
|
|
|
namespace Rector\Core\PhpParser\Node;
|
2017-10-26 16:43:43 +00:00
|
|
|
|
|
|
|
use PhpParser\Node;
|
2020-07-25 20:02:44 +00:00
|
|
|
use PhpParser\Node\Expr;
|
|
|
|
use PhpParser\Node\Expr\Assign;
|
2020-07-22 16:43:23 +00:00
|
|
|
use PhpParser\Node\Expr\Variable;
|
2019-01-14 18:21:23 +00:00
|
|
|
use PhpParser\Node\Stmt;
|
2019-05-23 17:32:46 +00:00
|
|
|
use PhpParser\Node\Stmt\Class_;
|
|
|
|
use PhpParser\Node\Stmt\ClassLike;
|
2018-10-09 10:18:42 +00:00
|
|
|
use PhpParser\Node\Stmt\Expression;
|
2017-10-26 16:43:43 +00:00
|
|
|
use PhpParser\NodeFinder;
|
2021-02-19 12:01:23 +00:00
|
|
|
use Rector\Core\PhpParser\Comparing\NodeComparator;
|
2021-01-15 20:27:36 +00:00
|
|
|
use Rector\Core\Util\StaticInstanceOf;
|
2020-07-19 11:11:20 +00:00
|
|
|
use Rector\NodeNameResolver\NodeNameResolver;
|
2019-04-13 09:20:27 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2021-01-19 19:45:30 +00:00
|
|
|
use Symplify\PackageBuilder\Php\TypeChecker;
|
2020-10-31 12:59:40 +00:00
|
|
|
use Webmozart\Assert\Assert;
|
2017-10-26 16:43:43 +00:00
|
|
|
|
2019-09-03 09:11:45 +00:00
|
|
|
/**
|
2021-01-08 22:30:33 +00:00
|
|
|
* @template T of Node
|
2020-02-06 21:48:18 +00:00
|
|
|
* @see \Rector\Core\Tests\PhpParser\Node\BetterNodeFinder\BetterNodeFinderTest
|
2019-09-03 09:11:45 +00:00
|
|
|
*/
|
2017-10-26 16:43:43 +00:00
|
|
|
final class BetterNodeFinder
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var NodeFinder
|
|
|
|
*/
|
|
|
|
private $nodeFinder;
|
|
|
|
|
2020-07-19 11:11:20 +00:00
|
|
|
/**
|
|
|
|
* @var NodeNameResolver
|
|
|
|
*/
|
|
|
|
private $nodeNameResolver;
|
|
|
|
|
2020-07-25 20:02:44 +00:00
|
|
|
/**
|
2021-02-19 12:01:23 +00:00
|
|
|
* @var TypeChecker
|
2020-07-25 20:02:44 +00:00
|
|
|
*/
|
2021-02-19 12:01:23 +00:00
|
|
|
private $typeChecker;
|
2020-07-25 20:02:44 +00:00
|
|
|
|
2021-01-19 19:45:30 +00:00
|
|
|
/**
|
2021-02-19 12:01:23 +00:00
|
|
|
* @var NodeComparator
|
2021-01-19 19:45:30 +00:00
|
|
|
*/
|
2021-02-19 12:01:23 +00:00
|
|
|
private $nodeComparator;
|
2021-01-19 19:45:30 +00:00
|
|
|
|
2020-07-25 20:02:44 +00:00
|
|
|
public function __construct(
|
|
|
|
NodeFinder $nodeFinder,
|
2021-01-19 19:45:30 +00:00
|
|
|
NodeNameResolver $nodeNameResolver,
|
2021-02-19 12:01:23 +00:00
|
|
|
TypeChecker $typeChecker,
|
|
|
|
NodeComparator $nodeComparator
|
2020-07-25 20:02:44 +00:00
|
|
|
) {
|
2017-10-26 16:43:43 +00:00
|
|
|
$this->nodeFinder = $nodeFinder;
|
2020-07-19 11:11:20 +00:00
|
|
|
$this->nodeNameResolver = $nodeNameResolver;
|
2021-01-19 19:45:30 +00:00
|
|
|
$this->typeChecker = $typeChecker;
|
2021-02-19 12:01:23 +00:00
|
|
|
$this->nodeComparator = $nodeComparator;
|
2019-03-31 18:13:46 +00:00
|
|
|
}
|
|
|
|
|
2019-03-31 19:30:26 +00:00
|
|
|
/**
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param class-string<T> $type
|
|
|
|
* @return T|null
|
2019-03-31 19:30:26 +00:00
|
|
|
*/
|
2021-01-08 22:30:33 +00:00
|
|
|
public function findParentType(Node $node, string $type): ?Node
|
|
|
|
{
|
|
|
|
Assert::isAOf($type, Node::class);
|
|
|
|
|
|
|
|
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
|
2021-01-19 11:24:48 +00:00
|
|
|
if (! $parent instanceof Node) {
|
2021-01-08 22:30:33 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
if (is_a($parent, $type, true)) {
|
|
|
|
return $parent;
|
|
|
|
}
|
|
|
|
|
2021-02-08 01:04:48 +00:00
|
|
|
if (! $parent instanceof Node) {
|
2021-01-08 22:30:33 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} while ($parent = $parent->getAttribute(AttributeKey::PARENT_NODE));
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-17 22:30:39 +00:00
|
|
|
* @param array<class-string<T>> $types
|
2021-01-08 22:30:33 +00:00
|
|
|
* @return T|null
|
|
|
|
*/
|
|
|
|
public function findParentTypes(Node $node, array $types): ?Node
|
2019-03-31 18:13:46 +00:00
|
|
|
{
|
2020-10-31 12:59:40 +00:00
|
|
|
Assert::allIsAOf($types, Node::class);
|
2019-03-31 19:30:26 +00:00
|
|
|
|
2021-01-08 22:30:33 +00:00
|
|
|
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
|
2021-01-19 11:24:48 +00:00
|
|
|
if (! $parent instanceof Node) {
|
2019-03-31 19:30:26 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-03-31 18:13:46 +00:00
|
|
|
do {
|
2021-01-15 20:27:36 +00:00
|
|
|
if (StaticInstanceOf::isOneOf($parent, $types)) {
|
2021-01-08 22:30:33 +00:00
|
|
|
return $parent;
|
2019-03-31 18:13:46 +00:00
|
|
|
}
|
2019-03-31 19:30:26 +00:00
|
|
|
|
2021-01-08 22:30:33 +00:00
|
|
|
if ($parent === null) {
|
2019-03-31 18:13:46 +00:00
|
|
|
return null;
|
|
|
|
}
|
2021-01-08 22:30:33 +00:00
|
|
|
} while ($parent = $parent->getAttribute(AttributeKey::PARENT_NODE));
|
2019-03-31 18:13:46 +00:00
|
|
|
|
|
|
|
return null;
|
2017-10-26 16:43:43 +00:00
|
|
|
}
|
|
|
|
|
2019-07-10 07:23:37 +00:00
|
|
|
/**
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param array<class-string<T>> $types
|
|
|
|
* @return T|null
|
2019-07-10 07:23:37 +00:00
|
|
|
*/
|
|
|
|
public function findFirstAncestorInstancesOf(Node $node, array $types): ?Node
|
|
|
|
{
|
|
|
|
$currentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
|
2021-02-08 01:04:48 +00:00
|
|
|
|
|
|
|
while ($currentNode instanceof Node) {
|
2019-07-10 07:23:37 +00:00
|
|
|
foreach ($types as $type) {
|
|
|
|
if (is_a($currentNode, $type, true)) {
|
|
|
|
return $currentNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$currentNode = $currentNode->getAttribute(AttributeKey::PARENT_NODE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-10-26 16:43:43 +00:00
|
|
|
/**
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param class-string<T> $type
|
2019-01-14 18:21:23 +00:00
|
|
|
* @param Node|Node[]|Stmt[] $nodes
|
2021-01-08 22:30:33 +00:00
|
|
|
* @return T[]
|
2017-10-26 16:43:43 +00:00
|
|
|
*/
|
|
|
|
public function findInstanceOf($nodes, string $type): array
|
|
|
|
{
|
2020-10-31 12:59:40 +00:00
|
|
|
Assert::isAOf($type, Node::class);
|
2020-09-18 09:50:55 +00:00
|
|
|
|
2017-10-26 16:43:43 +00:00
|
|
|
return $this->nodeFinder->findInstanceOf($nodes, $type);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param class-string<T> $type
|
2017-10-26 16:43:43 +00:00
|
|
|
* @param Node|Node[] $nodes
|
2021-01-08 22:30:33 +00:00
|
|
|
* @return T|null
|
2017-10-26 16:43:43 +00:00
|
|
|
*/
|
|
|
|
public function findFirstInstanceOf($nodes, string $type): ?Node
|
|
|
|
{
|
2020-10-31 12:59:40 +00:00
|
|
|
Assert::isAOf($type, Node::class);
|
2020-09-18 09:50:55 +00:00
|
|
|
|
2017-10-26 16:43:43 +00:00
|
|
|
return $this->nodeFinder->findFirstInstanceOf($nodes, $type);
|
|
|
|
}
|
2017-10-26 19:20:05 +00:00
|
|
|
|
2020-07-19 11:11:20 +00:00
|
|
|
/**
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param class-string<T> $type
|
2020-07-19 11:11:20 +00:00
|
|
|
* @param Node|Node[] $nodes
|
|
|
|
*/
|
|
|
|
public function hasInstanceOfName($nodes, string $type, string $name): bool
|
2020-07-19 13:39:13 +00:00
|
|
|
{
|
2020-10-31 12:59:40 +00:00
|
|
|
Assert::isAOf($type, Node::class);
|
2020-09-18 09:50:55 +00:00
|
|
|
|
2020-07-19 13:39:13 +00:00
|
|
|
return (bool) $this->findInstanceOfName($nodes, $type, $name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Node|Node[] $nodes
|
|
|
|
*/
|
2020-07-19 15:20:00 +00:00
|
|
|
public function hasVariableOfName($nodes, string $name): bool
|
2020-07-19 13:39:13 +00:00
|
|
|
{
|
2020-07-19 15:20:00 +00:00
|
|
|
return (bool) $this->findVariableOfName($nodes, $name);
|
2020-07-19 13:39:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Node|Node[] $nodes
|
2021-01-08 22:30:33 +00:00
|
|
|
* @return Variable|null
|
2020-07-19 13:39:13 +00:00
|
|
|
*/
|
2020-07-19 15:20:00 +00:00
|
|
|
public function findVariableOfName($nodes, string $name): ?Node
|
2020-07-19 11:11:20 +00:00
|
|
|
{
|
2020-07-19 15:20:00 +00:00
|
|
|
return $this->findInstanceOfName($nodes, Variable::class, $name);
|
2020-07-19 11:11:20 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 19:08:12 +00:00
|
|
|
/**
|
|
|
|
* @param Node|Node[] $nodes
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param class-string<T>[] $types
|
2020-03-30 19:08:12 +00:00
|
|
|
*/
|
|
|
|
public function hasInstancesOf($nodes, array $types): bool
|
|
|
|
{
|
2020-10-31 12:59:40 +00:00
|
|
|
Assert::allIsAOf($types, Node::class);
|
|
|
|
|
2020-03-30 19:08:12 +00:00
|
|
|
foreach ($types as $type) {
|
2020-10-26 14:03:26 +00:00
|
|
|
$nodeFinderFindFirstInstanceOf = $this->nodeFinder->findFirstInstanceOf($nodes, $type);
|
2020-09-18 09:50:55 +00:00
|
|
|
|
2021-01-20 11:41:35 +00:00
|
|
|
if (! $nodeFinderFindFirstInstanceOf instanceof Node) {
|
2020-03-30 19:08:12 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-15 22:14:44 +00:00
|
|
|
/**
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param class-string<T> $type
|
2018-01-15 22:14:44 +00:00
|
|
|
* @param Node|Node[] $nodes
|
2021-01-08 22:30:33 +00:00
|
|
|
* @return T|null
|
2018-01-15 22:14:44 +00:00
|
|
|
*/
|
|
|
|
public function findLastInstanceOf($nodes, string $type): ?Node
|
|
|
|
{
|
2020-10-31 12:59:40 +00:00
|
|
|
Assert::isAOf($type, Node::class);
|
2020-09-18 09:50:55 +00:00
|
|
|
|
2018-01-15 22:14:44 +00:00
|
|
|
$foundInstances = $this->nodeFinder->findInstanceOf($nodes, $type);
|
2019-02-17 14:12:47 +00:00
|
|
|
if ($foundInstances === []) {
|
2018-01-15 22:14:44 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-01-08 22:30:33 +00:00
|
|
|
$lastItemKey = array_key_last($foundInstances);
|
|
|
|
return $foundInstances[$lastItemKey];
|
2018-01-15 22:14:44 +00:00
|
|
|
}
|
|
|
|
|
2017-10-26 19:20:05 +00:00
|
|
|
/**
|
|
|
|
* @param Node|Node[] $nodes
|
|
|
|
* @return Node[]
|
|
|
|
*/
|
|
|
|
public function find($nodes, callable $filter): array
|
|
|
|
{
|
|
|
|
return $this->nodeFinder->find($nodes, $filter);
|
|
|
|
}
|
2017-11-01 21:37:57 +00:00
|
|
|
|
2019-05-23 17:32:46 +00:00
|
|
|
/**
|
|
|
|
* Excludes anonymous classes!
|
|
|
|
*
|
2020-10-31 12:59:40 +00:00
|
|
|
* @param Node[]|Node $nodes
|
2019-05-23 17:32:46 +00:00
|
|
|
* @return ClassLike[]
|
|
|
|
*/
|
2020-10-31 12:59:40 +00:00
|
|
|
public function findClassLikes($nodes): array
|
2019-05-23 17:32:46 +00:00
|
|
|
{
|
|
|
|
return $this->find($nodes, function (Node $node): bool {
|
|
|
|
if (! $node instanceof ClassLike) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-08-18 12:15:39 +00:00
|
|
|
// skip anonymous classes
|
2019-10-30 14:38:30 +00:00
|
|
|
return ! ($node instanceof Class_ && $node->isAnonymous());
|
2019-05-23 17:32:46 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-05 11:07:20 +00:00
|
|
|
/**
|
|
|
|
* @param Node[] $nodes
|
2021-01-08 22:30:33 +00:00
|
|
|
* @return ClassLike|null
|
2020-07-05 11:07:20 +00:00
|
|
|
*/
|
2020-09-01 17:56:30 +00:00
|
|
|
public function findFirstNonAnonymousClass(array $nodes): ?Node
|
2020-07-05 11:07:20 +00:00
|
|
|
{
|
|
|
|
return $this->findFirst($nodes, function (Node $node): bool {
|
|
|
|
if (! $node instanceof ClassLike) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip anonymous classes
|
|
|
|
return ! ($node instanceof Class_ && $node->isAnonymous());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-11-01 21:37:57 +00:00
|
|
|
/**
|
|
|
|
* @param Node|Node[] $nodes
|
|
|
|
*/
|
|
|
|
public function findFirst($nodes, callable $filter): ?Node
|
|
|
|
{
|
|
|
|
return $this->nodeFinder->findFirst($nodes, $filter);
|
|
|
|
}
|
2018-10-09 10:18:42 +00:00
|
|
|
|
2021-01-08 22:30:33 +00:00
|
|
|
/**
|
|
|
|
* @return Assign|null
|
|
|
|
*/
|
2020-09-01 17:56:30 +00:00
|
|
|
public function findPreviousAssignToExpr(Expr $expr): ?Node
|
2020-07-25 20:02:44 +00:00
|
|
|
{
|
2020-09-01 17:56:30 +00:00
|
|
|
return $this->findFirstPrevious($expr, function (Node $node) use ($expr): bool {
|
2020-07-25 20:02:44 +00:00
|
|
|
if (! $node instanceof Assign) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-02-19 12:01:23 +00:00
|
|
|
return $this->nodeComparator->areNodesEqual($node->var, $expr);
|
2020-07-25 20:02:44 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-14 09:53:04 +00:00
|
|
|
public function findFirstPreviousOfNode(Node $node, callable $filter): ?Node
|
|
|
|
{
|
|
|
|
// move to previous expression
|
|
|
|
$previousStatement = $node->getAttribute(AttributeKey::PREVIOUS_NODE);
|
|
|
|
if ($previousStatement !== null) {
|
|
|
|
$foundNode = $this->findFirst([$previousStatement], $filter);
|
|
|
|
// we found what we need
|
|
|
|
if ($foundNode !== null) {
|
|
|
|
return $foundNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->findFirstPreviousOfNode($previousStatement, $filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
|
|
|
|
if ($parent instanceof Node) {
|
|
|
|
return $this->findFirstPreviousOfNode($parent, $filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2018-10-09 10:18:42 +00:00
|
|
|
public function findFirstPrevious(Node $node, callable $filter): ?Node
|
|
|
|
{
|
2019-10-27 16:50:26 +00:00
|
|
|
$node = $node instanceof Expression ? $node : $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
|
2019-01-25 00:49:26 +00:00
|
|
|
if ($node === null) {
|
|
|
|
return null;
|
|
|
|
}
|
2018-10-09 10:18:42 +00:00
|
|
|
|
|
|
|
$foundNode = $this->findFirst([$node], $filter);
|
|
|
|
// we found what we need
|
2019-02-17 14:12:47 +00:00
|
|
|
if ($foundNode !== null) {
|
2018-10-09 10:18:42 +00:00
|
|
|
return $foundNode;
|
|
|
|
}
|
|
|
|
|
2020-12-23 14:33:14 +00:00
|
|
|
// move to previous expression
|
2019-10-27 17:40:59 +00:00
|
|
|
$previousStatement = $node->getAttribute(AttributeKey::PREVIOUS_STATEMENT);
|
2020-12-23 14:33:14 +00:00
|
|
|
if ($previousStatement !== null) {
|
|
|
|
return $this->findFirstPrevious($previousStatement, $filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
|
|
|
|
if ($parent === null) {
|
2018-10-09 10:18:42 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-12-23 14:33:14 +00:00
|
|
|
return $this->findFirstPrevious($parent, $filter);
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|
2019-03-31 18:13:46 +00:00
|
|
|
|
2020-03-23 16:13:04 +00:00
|
|
|
/**
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param class-string<T>[] $types
|
|
|
|
* @return T|null
|
2020-03-23 16:13:04 +00:00
|
|
|
*/
|
|
|
|
public function findFirstPreviousOfTypes(Node $mainNode, array $types): ?Node
|
|
|
|
{
|
2020-09-01 17:56:30 +00:00
|
|
|
return $this->findFirstPrevious($mainNode, function (Node $node) use ($types): bool {
|
2021-01-19 19:45:30 +00:00
|
|
|
return $this->typeChecker->isInstanceOf($node, $types);
|
2020-03-23 16:13:04 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-05 14:15:51 +00:00
|
|
|
public function findFirstNext(Node $node, callable $filter): ?Node
|
|
|
|
{
|
|
|
|
$next = $node->getAttribute(AttributeKey::NEXT_NODE);
|
|
|
|
if ($next instanceof Node) {
|
|
|
|
$found = $this->findFirst($next, $filter);
|
|
|
|
if ($found instanceof Node) {
|
|
|
|
return $found;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->findFirstNext($next, $filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
$parent = $node->getAttribute(AttributeKey::PARENT_NODE);
|
|
|
|
if ($parent instanceof Node) {
|
|
|
|
return $this->findFirstNext($parent, $filter);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-03-31 19:30:26 +00:00
|
|
|
/**
|
2020-08-05 20:45:36 +00:00
|
|
|
* @param Node|Node[] $nodes
|
2021-01-08 22:30:33 +00:00
|
|
|
* @param class-string<T> $type
|
|
|
|
* @return T|null
|
2019-03-31 19:30:26 +00:00
|
|
|
*/
|
2020-08-05 20:45:36 +00:00
|
|
|
private function findInstanceOfName($nodes, string $type, string $name): ?Node
|
2019-03-31 19:30:26 +00:00
|
|
|
{
|
2020-10-31 12:59:40 +00:00
|
|
|
Assert::isAOf($type, Node::class);
|
2020-09-18 09:50:55 +00:00
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
$foundInstances = $this->nodeFinder->findInstanceOf($nodes, $type);
|
|
|
|
foreach ($foundInstances as $foundInstance) {
|
2021-01-08 22:30:33 +00:00
|
|
|
if (! $this->nodeNameResolver->isName($foundInstance, $name)) {
|
|
|
|
continue;
|
2019-03-31 19:30:26 +00:00
|
|
|
}
|
2021-01-08 22:30:33 +00:00
|
|
|
|
|
|
|
return $foundInstance;
|
2019-03-31 19:30:26 +00:00
|
|
|
}
|
|
|
|
|
2020-08-05 20:45:36 +00:00
|
|
|
return null;
|
2019-03-31 19:30:26 +00:00
|
|
|
}
|
2017-10-26 16:43:43 +00:00
|
|
|
}
|