| Server IP : 185.252.147.100 / Your IP : 216.73.217.33 Web Server : nginx/1.27.3 System : Linux mitrofanov.ru 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64 User : mitr ( 1000) PHP Version : 8.2.29 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /www/html/east-point.site/public/wp-content/plugins/staatic/src/Bridge/ |
Upload File : |
<?php
declare(strict_types=1);
namespace Staatic\WordPress\Bridge;
use DateTimeImmutable;
use Generator;
use Staatic\Vendor\GuzzleHttp\Psr7\Uri;
use Staatic\Vendor\Psr\Http\Message\UriInterface;
use Staatic\Vendor\Psr\Log\LoggerAwareInterface;
use Staatic\Vendor\Psr\Log\LoggerAwareTrait;
use Staatic\Vendor\Psr\Log\NullLogger;
use Staatic\Vendor\Ramsey\Uuid\Uuid;
use RuntimeException;
use Staatic\Framework\Result;
use Staatic\Framework\ResultRepository\ResultRepositoryInterface;
use wpdb;
final class ResultRepository implements ResultRepositoryInterface, LoggerAwareInterface
{
/**
* @var wpdb
*/
private $wpdb;
use LoggerAwareTrait;
/** @var int */
private const MAX_URL_LENGTH = 2083;
/**
* @var string
*/
private $tableName;
/**
* @var string
*/
private $deployTableName;
public function __construct(wpdb $wpdb, string $tableName = 'staatic_results', string $deployTableName = 'staatic_results_deployment')
{
$this->wpdb = $wpdb;
$this->logger = new NullLogger();
$this->tableName = $wpdb->prefix . $tableName;
$this->deployTableName = $wpdb->prefix . $deployTableName;
}
public function nextId(): string
{
return (string) Uuid::uuid4();
}
/**
* @param Result $result
*/
public function add($result): void
{
$this->logger->debug("Adding result #{$result->id()}", [
'resultId' => $result->id()
]);
$queryResult = $this->wpdb->insert($this->tableName, $this->getResultValues($result));
if ($queryResult === \false) {
throw new RuntimeException(
"Unable to add result '{$result->url()}' (#{$result->id()}): {$this->wpdb->last_error}"
);
}
}
/**
* @param Result $result
*/
public function update($result): void
{
$this->logger->debug("Updating result #{$result->id()}", [
'resultId' => $result->id()
]);
$queryResult = $this->wpdb->update($this->tableName, $this->getResultValues($result), [
'uuid' => $result->id()
]);
if ($queryResult === \false) {
throw new RuntimeException("Unable to update result #{$result->id()}}: {$this->wpdb->last_error}");
}
}
/**
* @param Result $result
*/
public function delete($result): void
{
$this->logger->debug("Deleting result #{$result->id()}", [
'resultId' => $result->id()
]);
$queryResult = $this->wpdb->delete($this->tableName, [
'uuid' => $result->id()
]);
if ($queryResult === \false) {
throw new RuntimeException("Unable to delete result #{$result->id()}: {$this->wpdb->last_error}");
}
}
/**
* @param string $sourceBuildId
* @param string $targetBuildId
*/
public function mergeBuildResults($sourceBuildId, $targetBuildId): void
{
$this->logger->debug("Merging build results from build #{$sourceBuildId} into build #{$targetBuildId}", [
'buildId' => $targetBuildId
]);
$statement = $this->wpdb->prepare(
"\n SELECT\n s.url, s.url_hash, s.status_code, s.md5, s.sha1, s.size, s.mime_type, s.charset,\n s.redirect_url, s.original_url, s.original_found_on_url, s.date_created\n FROM {$this->tableName} s\n LEFT JOIN {$this->tableName} t ON\n t.build_uuid = UNHEX(REPLACE(%s, '-', '')) AND\n t.url_hash = s.url_hash\n WHERE s.build_uuid = UNHEX(REPLACE(%s, '-', ''))\n AND t.uuid IS NULL",
$targetBuildId,
$sourceBuildId
);
$rows = $this->wpdb->get_results($statement, \ARRAY_A);
$insertValues = [];
foreach ($rows as $row) {
$insertValues[] = [
'uuid' => $this->nextId(),
'build_uuid' => $targetBuildId
] + $row;
if (count($insertValues) >= 50) {
$this->massInsert($this->tableName, $insertValues);
$insertValues = [];
}
}
if (count($insertValues)) {
$this->massInsert($this->tableName, $insertValues);
}
}
private function massInsert(string $tableName, array $insertValues): void
{
$columnNames = array_keys($insertValues[0]);
$rowPlaceholders = array_map(function () {
return "(UNHEX(REPLACE(%s, '-', '')), UNHEX(REPLACE(%s, '-', '')), %s, %s, %d, %s, %s, %d, %s, %s, %s, %s, %s, %s)";
}, $insertValues);
//!TODO: not really flexible, yet...
$nullPlaceholder = '<<NULL-' . bin2hex(random_bytes(16)) . '>>';
$values = [];
foreach ($insertValues as $row) {
foreach ($row as $value) {
$values[] = ($value === null) ? $nullPlaceholder : $value;
}
}
$statement = $this->wpdb->prepare(
"\n INSERT INTO {$tableName} (" . implode(
', ',
$columnNames
) . ")\n VALUES " . implode(
', ',
$rowPlaceholders
),
$values
);
$statement = str_replace('\'' . $nullPlaceholder . '\'', 'NULL', $statement);
$queryResult = $this->wpdb->query($statement);
if ($queryResult === \false) {
throw new RuntimeException("Unable to mass insert into {$tableName}: {$this->wpdb->last_error}");
}
}
/**
* @param string $buildId
* @param string $deploymentId
*/
public function scheduleForDeployment($buildId, $deploymentId): int
{
$this->logger->debug("Scheduling results in build #{$buildId} for deployment #{$deploymentId}", [
'buildId' => $buildId,
'deploymentId' => $deploymentId
]);
$statement = $this->wpdb->prepare(
"\n INSERT INTO {$this->deployTableName} (deployment_uuid, result_uuid, date_created)\n SELECT UNHEX(REPLACE(%s, '-', '')), r.uuid, %s\n FROM {$this->tableName} r\n WHERE r.build_uuid = UNHEX(REPLACE(%s, '-', ''))",
$deploymentId,
(new DateTimeImmutable())->format('Y-m-d H:i:s'),
$buildId
);
$queryResult = $this->wpdb->query($statement);
if ($queryResult === \false) {
throw new RuntimeException(
"Unable to schedule results in build #{$buildId} for deployment #{$deploymentId}: {$this->wpdb->last_error}"
);
}
return is_int($queryResult) ? $queryResult : 0;
}
/**
* @param Result $result
* @param string $deploymentId
*/
public function markDeployed($result, $deploymentId): void
{
$this->logger->debug("Marking result #{$result->id()} deployed for deployment #{$deploymentId}", [
'resultId' => $result->id(),
'deploymentId' => $deploymentId
]);
$statement = $this->wpdb->prepare(
"\n UPDATE {$this->deployTableName}\n SET date_deployed = %s\n WHERE deployment_uuid = UNHEX(REPLACE(%s, '-', ''))\n AND result_uuid = UNHEX(REPLACE(%s, '-', ''))",
(new DateTimeImmutable())->format('Y-m-d H:i:s'),
$deploymentId,
$result->id()
);
$rowsAffected = $this->wpdb->query($statement);
if ($rowsAffected !== 1) {
throw new RuntimeException(
"Unable to mark result #{$result->id()} deployed for deployment #{$deploymentId}"
);
}
}
/**
* @param string $deploymentId
* @param mixed[] $resultIds
*/
public function markManyDeployed($deploymentId, $resultIds): void
{
$numResults = count($resultIds);
$this->logger->debug("Marking {$numResults} results deployed for deployment #{$deploymentId}", [
'deploymentId' => $deploymentId
]);
$statement = $this->wpdb->prepare(
"\n UPDATE {$this->deployTableName}\n SET date_deployed = %s\n WHERE deployment_uuid = UNHEX(REPLACE(%s, '-', ''))\n AND result_uuid IN (UNHEX(REPLACE('" . implode(
"', '-', '')), UNHEX(REPLACE('",
$resultIds
) . "', '-', '')))",
(new DateTimeImmutable())->format('Y-m-d H:i:s'),
$deploymentId
);
$rowsAffected = $this->wpdb->query($statement);
if ($rowsAffected !== $numResults) {
throw new RuntimeException("Unable to mark results deployed for deployment #{$deploymentId}");
}
}
private function getResultValues(Result $result): array
{
return [
'uuid' => $result->id(),
'build_uuid' => $result->buildId(),
'url' => substr((string) $result->url(), 0, self::MAX_URL_LENGTH),
'url_hash' => $result->urlHash(),
'status_code' => $result->statusCode(),
'md5' => $result->md5(),
'sha1' => $result->sha1(),
'size' => $result->size(),
'mime_type' => $result->mimeType(),
'charset' => $result->charset(),
'redirect_url' => $result->redirectUrl() ? substr(
(string) $result->redirectUrl(),
0,
self::MAX_URL_LENGTH
) : null,
'original_url' => $result->originalUrl() ? substr(
(string) $result->originalUrl(),
0,
self::MAX_URL_LENGTH
) : null,
'original_found_on_url' => $result->originalFoundOnUrl() ? substr(
(string) $result->originalFoundOnUrl(),
0,
self::MAX_URL_LENGTH
) : null,
'date_created' => $result->dateCreated()->format('Y-m-d H:i:s')
];
}
/**
* @param string $resultId
*/
public function find($resultId): Result
{
$row = $this->wpdb->get_row(
$this->wpdb->prepare(
"SELECT * FROM {$this->tableName} WHERE uuid = UNHEX(REPLACE(%s, '-', ''))",
$resultId
),
\ARRAY_A
);
if (!is_array($row)) {
throw new RuntimeException("Unable to find result #{$resultId}");
}
return $this->rowToResult($row);
}
/**
* @return Result[]|Generator
*/
public function findAll(): Generator
{
$rows = $this->wpdb->get_results("SELECT * FROM {$this->tableName}", \ARRAY_A);
foreach ($rows as $row) {
yield $this->rowToResult($row);
}
}
/**
* @return Result[]|Generator
* @param string $buildId
*/
public function findByBuildId($buildId): Generator
{
$rows = $this->wpdb->get_results(
$this->wpdb->prepare(
"SELECT * FROM {$this->tableName} WHERE build_uuid = UNHEX(REPLACE(%s, '-', ''))",
$buildId
),
\ARRAY_A
);
foreach ($rows as $row) {
yield $this->rowToResult($row);
}
}
/**
* @return Result[]
* @param string $buildId
*/
public function findByBuildIdWithRedirectUrl($buildId): array
{
$rows = $this->wpdb->get_results(
$this->wpdb->prepare(
"\n SELECT *\n FROM {$this->tableName}\n WHERE build_uuid = UNHEX(REPLACE(%s, '-', ''))\n AND redirect_url IS NOT NULL",
$buildId
),
\ARRAY_A
);
return array_map(function ($row) {
return $this->rowToResult($row);
}, $rows);
}
/**
* @param string $buildId
* @param string $deploymentId
*/
public function findByBuildIdPendingDeployment($buildId, $deploymentId): Generator
{
$rows = $this->wpdb->get_results(
$this->wpdb->prepare(
"\n SELECT r.*\n FROM {$this->tableName} r\n LEFT JOIN {$this->deployTableName} d ON\n d.deployment_uuid = UNHEX(REPLACE(%s, '-', '')) AND\n d.result_uuid = r.uuid\n WHERE r.build_uuid = UNHEX(REPLACE(%s, '-', ''))\n AND d.date_deployed IS NULL",
$deploymentId,
$buildId
),
\ARRAY_A
);
foreach ($rows as $row) {
yield $this->rowToResult($row);
}
}
/**
* @param string $buildId
* @param UriInterface $url
*/
public function findOneByBuildIdAndUrl($buildId, $url): ?Result
{
$row = $this->wpdb->get_row(
$this->wpdb->prepare(
"SELECT * FROM {$this->tableName} WHERE build_uuid = UNHEX(REPLACE(%s, '-', '')) AND url = %s",
$buildId,
(string) $url
),
\ARRAY_A
);
return is_array($row) ? $this->rowToResult($row) : null;
}
/**
* @param string $buildId
* @param UriInterface $url
*/
public function findOneByBuildIdAndUrlResolved($buildId, $url): ?Result
{
$row = $this->wpdb->get_row(
$this->wpdb->prepare(
"SELECT * FROM {$this->tableName} WHERE build_uuid = UNHEX(REPLACE(%s, '-', '')) AND url = %s",
$buildId,
(string) $url
),
\ARRAY_A
);
if (!is_array($row)) {
return null;
}
$result = $this->rowToResult($row);
if ($result->statusCodeCategory() === 3) {
return $this->findOneByBuildIdAndUrlResolved($buildId, $result->redirectUrl());
} else {
return $result;
}
}
/**
* @param string $buildId
*/
public function countByBuildId($buildId): int
{
return (int) $this->wpdb->get_var(
$this->wpdb->prepare("SELECT COUNT(*) FROM {$this->tableName} WHERE build_uuid = UNHEX(REPLACE(%s, '-', ''))", $buildId)
);
}
/**
* @param string $buildId
* @param string $deploymentId
*/
public function countByBuildIdPendingDeployment($buildId, $deploymentId): int
{
return (int) $this->wpdb->get_var(
$this->wpdb->prepare("\n SELECT COUNT(*)\n FROM {$this->tableName} r\n LEFT JOIN {$this->deployTableName} d ON\n d.deployment_uuid = UNHEX(REPLACE(%s, '-', '')) AND\n d.result_uuid = r.uuid\n WHERE r.build_uuid = UNHEX(REPLACE(%s, '-', ''))\n AND d.date_deployed IS NULL", $deploymentId, $buildId)
);
}
private function rowToResult(array $row): Result
{
return new Result((string) Uuid::fromBytes($row['uuid']), (string) Uuid::fromBytes($row['build_uuid']), new Uri(
$row['url']
), $row['url_hash'], (int) $row['status_code'], $row['md5'], $row['sha1'], (int) $row['size'], $row['mime_type'], $row['charset'], $row['redirect_url'] ? new Uri(
$row['redirect_url']
) : null, $row['original_url'] ? new Uri(
$row['original_url']
) : null, $row['original_found_on_url'] ? new Uri(
$row['original_found_on_url']
) : null, new DateTimeImmutable(
$row['date_created']
));
}
// Plugin specific methods
/**
* @param string $buildId
*/
public function deleteByBuildId($buildId): void
{
$this->logger->debug("Deleting results for build #{$buildId}", [
'buildId' => $buildId
]);
$queryResult = $this->wpdb->delete($this->tableName, [
'build_uuid' => $buildId
]);
if ($queryResult === \false) {
throw new RuntimeException("Unable to delete results for build #{$buildId}: {$this->wpdb->last_error}");
}
}
/**
* @return Result[]
* @param string $buildId
* @param string|null $query
* @param int $limit
* @param int $offset
* @param string|null $orderBy
* @param string|null $direction
*/
public function findWhereMatching($buildId, $statusCategories, $query, $limit, $offset, $orderBy, $direction): array
{
$statusCategories = is_int($statusCategories) ? [$statusCategories] : $statusCategories;
$orderBy = $orderBy ?: null;
$direction = $direction ?: 'DESC';
$results = $this->wpdb->get_results(
"\n SELECT *\n FROM {$this->tableName}\n WHERE build_uuid = UNHEX(REPLACE('" . esc_sql(
$buildId
) . "', '-', ''))" . ((!empty($statusCategories)) ? "\n AND SUBSTR(status_code, 1, 1) IN (" . implode(
', ',
array_map(function ($category) {
return intval($category);
}, $statusCategories)
) . ")" : "") . ($query ? "\n AND url LIKE '%" . esc_sql(
$this->wpdb->esc_like($query)
) . "%'" : "") . "\n ORDER BY " . ($orderBy ? "{$orderBy} {$direction}, " : "") . "id {$direction}\n LIMIT {$limit} OFFSET {$offset}",
\ARRAY_A
);
return array_map(function ($row) {
return $this->rowToResult($row);
}, $results);
}
/**
* @param string $buildId
* @param int|null $category
* @param string|null $query
*/
public function countWhereMatching($buildId, $category, $query): int
{
return (int) $this->wpdb->get_var(
"\n SELECT COUNT(*)\n FROM {$this->tableName}\n WHERE build_uuid = UNHEX(REPLACE('" . esc_sql(
$buildId
) . "', '-', ''))" . ($category ? "\n AND SUBSTR(status_code, 1, 1) = " . intval(
$category
) : "") . ($query ? "\n AND url LIKE '%" . esc_sql(
$this->wpdb->esc_like($query)
) . "%'" : "")
);
}
/**
* @param string $buildId
*/
public function getResultsPerStatusCategory($buildId): array
{
$rows = $this->wpdb->get_results(
$this->wpdb->prepare(
"\n SELECT SUBSTR(status_code, 1, 1) AS category, COUNT(*) AS total\n FROM {$this->tableName}\n WHERE build_uuid = UNHEX(REPLACE(%s, '-', ''))\n GROUP BY category\n ORDER BY category",
$buildId
),
\ARRAY_A
);
$resultsPerStatusCategory = [];
foreach ($rows as $row) {
$resultsPerStatusCategory[(int) $row['category']] = (int) $row['total'];
}
foreach (range(1, 5) as $statusCategory) {
$resultsPerStatusCategory[$statusCategory] = $resultsPerStatusCategory[$statusCategory] ?? 0;
}
return $resultsPerStatusCategory;
}
/**
* @param mixed[] $sha1Hashes
*/
public function getKnownSha1Hashes($sha1Hashes): array
{
return $this->wpdb->get_col(
"\n SELECT DISTINCT sha1\n FROM {$this->tableName}\n WHERE sha1 IN ('" . implode(
"', '",
esc_sql($sha1Hashes)
) . "')\n "
);
}
}