Skip to content

Release v0.3.7 — Phase 50: External URL Import Foundation - #924

Merged
menvil merged 47 commits into
mainfrom
release/v0.3.7-phase50-external-url-import-foundation
Jun 10, 2026
Merged

Release v0.3.7 — Phase 50: External URL Import Foundation#924
menvil merged 47 commits into
mainfrom
release/v0.3.7-phase50-external-url-import-foundation

Conversation

@menvil

@menvil menvil commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 50 adds inbound URL import foundation. Users can paste a URL into the upload form, get a preview (title, description, image via OpenGraph or direct image detection), and confirm post creation.

  • RG-795 — URL import requirements audit (docs/import/phase-50-url-import-audit.md)
  • RG-796config/import.php with safe defaults (timeouts, byte limits, allowed schemes)
  • RG-797ImportProvider enum (direct_image, open_graph, facebook, instagram, x, pinterest, unsupported)
  • RG-798UrlImportValidator with full SSRF protection (localhost, private IPs, link-local, forbidden schemes)
  • RG-799SafeImportHttpClient with timeout/redirect/size enforcement
  • RG-800DirectImageImportAdapter + ImportPreview DTO
  • RG-801OpenGraphParser (og:, twitter:, title, meta description, relative URL resolution)
  • RG-802OpenGraphImportAdapter (fetch page → parse OG → validate image URL)
  • RG-803ImportProviderDetector (detects provider from URL pattern)
  • RG-804ImportPreview DTO tests (hasImage, isSupported, warnings)
  • RG-805ImportFromUrlAction (orchestrator: validate → detect → adapt → preview)
  • RG-806allow_url_imports feature flag + UrlImportDisabledException
  • RG-807ImportUrlForm Livewire component + translations (en/ru/bg)
  • RG-808UploadPostForm integration (upload/import tabs, applyImportPreview)
  • RG-809StoreImportedImageAction (download URL as UploadedFile through existing validation)
  • RG-810 — Error handling tests (unsupported, unsafe, validation, no internal details exposed)
  • RG-811 — Import translation keys tests across en/ru/bg
  • RG-812 — Browser smoke tests for import tab flow
  • RG-813docs/import/url-import.md developer guide
  • RG-814docs/import/phase-50-url-import-review.md final checklist

Security

  • SSRF protection blocks localhost, 127.x, 10.x, 172.16–31.x, 192.168.x, 169.254.x, ::1, file://, ftp://
  • Fetch limits: 5s timeout, 2s connect timeout, 3 max redirects, 1MB HTML cap, 8MB image cap
  • Imported images go through existing upload MIME/size validation
  • Images downloaded only on user confirm, not during preview
  • Social providers (Facebook/Instagram/X) best-effort only — graceful fallback, no OAuth, no scraping

Test plan

  • composer test passes — 1758 tests
  • npm run build passes
  • Pint clean on all new files
  • RawColorGuard passes (no forbidden raw colors in new views)

🤖 Generated with Claude Code


Summary by cubic

Adds URL import with a preview in the upload form. Users paste a link, see OpenGraph or direct-image details, and apply them; the HTTP client follows redirects with per-hop SSRF checks and blocks private IPv4/IPv6, gated by allow_url_imports.

  • New Features

    • ImportUrlForm adds paste → preview → apply; UploadPostForm listens to import-preview-selected, fills fields, and downloads importedImageUrl before validation.
    • ImportFromUrlAction validates → detects → adapts; social hosts are best‑effort OG with a localized unsupported reason.
    • OpenGraphParser resolves relative/protocol‑relative image URLs and preserves non‑default ports.
    • ImportProvider enum used across adapters/DTOs; detector maps image URLs and major social hosts (SVG excluded to match allowed MIME list).
    • StoreImportedImageAction downloads on confirm through existing upload validation. Docs, tests, and i18n added. Implements RG‑795–RG‑814.
  • Security

    • DNS A/AAAA hostname resolution and per‑redirect SSRF validation; blocks localhost, private IPv4 ranges, 169.254/16, and IPv6 ::1, fe80::/10, fc00::/7.
    • SafeImportHttpClient enforces connect/overall timeouts, redirect cap, Content‑Length pre‑check, and byte limits; manual redirect following validates each hop.
    • Strict image checks: allow‑list MIME types, size caps; preview never downloads images; guards empty Content‑Type; exceptions sanitize sensitive params and avoid leaking full URLs.
    • Imports limited to https via config; validator reads import.allowed_schemes; allow_url_imports gates UI and action (config enabled defaults to false).

Written for commit 7da25a5. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Импорт постов и изображений по URL с предпросмотром и применением в форме загрузки; вкладки «Загрузить / Импорт» и переключение к загрузке при подтверждении превью.
    • Open Graph‑предпросмотр и поддержка прямых ссылок на изображения; best‑effort для популярных соцсетей; ручная загрузка как fallback.
    • Флаг функции включён по умолчанию (показывает/скрывает UI импорта).
  • Документация

    • Руководство и чеклисты по URL‑импорту, требования безопасности и ограничения.
  • Тесты

    • Набор браузерных и модульных тестов для сценариев импорта, локализаций и безопасности.

menvil and others added 30 commits June 10, 2026 18:14
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

PR реализует безопасный импорт по URL: валидация и защита от SSRF, безопасный HTTP-клиент с контролем редиректов/лимитов, детект провайдера, OpenGraph-парсер, адаптеры для прямых изображений и страниц, оркестратор ImportFromUrlAction, загрузка изображений, Livewire UI с предпросмотром, конфигурация, локализация, документация и широкий набор тестов.

Changes

URL Import Feature (Phase 50)

Layer / File(s) Summary
Security & URL validation infrastructure
app/Support/Import/UrlImportValidator.php, app/Support/Import/SafeImportHttpClient.php, app/Exceptions/Import/ImportFetchException.php, app/Exceptions/Import/UnsafeImportUrlException.php, app/Exceptions/Import/UrlImportDisabledException.php, config/import.php
Реализованы проверки URL (блокировка приватных/loopback/link-local/metadata диапазонов, разрешённые схемы), SafeImportHttpClient с ручной обработкой редиректов, лимитами по размерам и таймаутам; исключения для ошибок fetch/SSRF/выключенной фичи.
Provider detection & OpenGraph parsing
app/Support/Import/ImportProviderDetector.php, app/Support/Import/OpenGraphParser.php, app/Support/Import/OpenGraphMetadata.php
Определение провайдера по домену/расширению, парсинг OG/Twitter метаданных с fallback на title/description и разрешением относительных URL; результат инкапсулирован в OpenGraphMetadata.
Adapters & DTOs
app/Support/Import/Adapters/DirectImageImportAdapter.php, app/Support/Import/Adapters/OpenGraphImportAdapter.php, app/Support/Import/ImportPreview.php, app/Enums/ImportProvider.php
DirectImageImportAdapter проверяет Content-Type и размер для прямых изображений; OpenGraphImportAdapter формирует preview с предупреждениями; ImportPreview DTO и ImportProvider enum добавлены.
Action orchestration & image storage
app/Actions/Import/ImportFromUrlAction.php, app/Actions/Import/StoreImportedImageAction.php
ImportFromUrlAction выполняет feature-flag проверку, валидацию URL, детект провайдера, делегирует адаптерам и обрабатывает ошибки с graceful unsupported fallback; StoreImportedImageAction скачивает и возвращает UploadedFile после валидации MIME/размера.
Livewire UI components & integration
app/Livewire/Import/ImportUrlForm.php, app/Livewire/Feed/UploadPostForm.php, resources/views/livewire/import/import-url-form.blade.php, resources/views/livewire/feed/upload-post-form.blade.php
Добавлен компонент ImportUrlForm (интерфейс импорта, загрузка превью, обработка ошибок), интеграция в UploadPostForm с вкладками Upload/Import и метод applyImportPreview, шаблоны с показом превью и подсказками.
Config, docs & i18n
config/import.php, app/Support/Settings/ProjectSettingsManager.php, docs/import/*, lang/en/import.php, lang/ru/import.php, lang/bg/import.php
Новый конфиг с лимитами и режимами провайдеров, feature flag allow_url_imports в настройках проекта, Developer Guide, audit/checklist docs и локализации для UI/ошибок.
Comprehensive test coverage
tests/Browser/ImportUrlBrowserTest.php, tests/Feature/Import/*, tests/Feature/Livewire/*, tests/Feature/Docs/*, tests/Feature/I18n/*
Широкое покрытие: валидатор, HTTP-клиент, парсер, адаптеры, детектор провайдеров, действия, загрузка изображения, Livewire-компоненты, документация и i18n проверки.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • menvil/rateguru#811: Перекрытие по инфраструктуре feature flags / ProjectSettingsManager, связанное добавлением и использованию флага allow_url_imports.
  • menvil/rateguru#232: Пересечение по изменениям UploadPostForm (поля/вкладки/методы) — потенциальный конфликт в Livewire-классе/шаблонах.

Suggested labels

release

"Я — кролик, преследую ссылку ясную,
Нужды SSRF я в рыльце отпечатал,
OpenGraph напёл, картинку подсказал,
И превью в форме мягко приложил 🌿"

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.3.7-phase50-external-url-import-foundation

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

22 issues found across 46 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="config/import.php">

<violation number="1" location="config/import.php:4">
P2: Config key `enabled` (and its `IMPORT_FROM_URL_ENABLED` env variable) is dead configuration — no code reads it. The actual feature gate is `ProjectSettingsManager::featureEnabled('allow_url_imports')`, making this key misleading.</violation>

<violation number="2" location="config/import.php:6">
P2: Config key `allowed_schemes` is defined but never consumed — `UrlImportValidator` hardcodes its own `ALLOWED_SCHEMES` constant. The config value has no effect on validation behavior, creating a security-relevant maintenance trap.</violation>

<violation number="3" location="config/import.php:21">
P3: The entire `providers` config subsection is dead configuration — no runtime code reads `config('import.providers')`. Provider detection and support logic are hardcoded in `ImportProviderDetector` and `ImportFromUrlAction`.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread app/Support/Import/SafeImportHttpClient.php Outdated
Comment thread app/Support/Import/UrlImportValidator.php
Comment thread app/Livewire/Feed/UploadPostForm.php
Comment thread tests/Feature/Docs/Phase50ImportConfigTest.php
Comment thread app/Support/Import/SafeImportHttpClient.php Outdated
Comment thread tests/Feature/Docs/Phase50ImportProviderTest.php
Comment thread app/Support/Import/ImportPreview.php Outdated
Comment thread tests/Browser/ImportUrlBrowserTest.php
Comment thread resources/views/livewire/import/import-url-form.blade.php
Comment thread config/import.php
@@ -0,0 +1,29 @@
<?php

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The entire providers config subsection is dead configuration — no runtime code reads config('import.providers'). Provider detection and support logic are hardcoded in ImportProviderDetector and ImportFromUrlAction.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At config/import.php, line 21:

<comment>The entire `providers` config subsection is dead configuration — no runtime code reads `config('import.providers')`. Provider detection and support logic are hardcoded in `ImportProviderDetector` and `ImportFromUrlAction`.</comment>

<file context>
@@ -0,0 +1,29 @@
+        'image/webp',
+    ],
+
+    'providers' => [
+        'direct_image' => true,
+        'open_graph' => true,
</file context>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/Livewire/Feed/UploadPostForm.php (1)

86-89: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Сбросьте состояние импорта после успешной отправки.

После успешного создания поста (строка 86) сбрасываются основные поля, но importedImageUrl и activeTab остаются в прежнем состоянии. Это может привести к несогласованности UI при повторном открытии формы.

🐛 Предлагаемое исправление
-            $this->reset(['title', 'description', 'sourceUrl', 'image', 'tagIds']);
+            $this->reset(['title', 'description', 'sourceUrl', 'image', 'importedImageUrl', 'tagIds']);
             $this->tagSearch = '';
+            $this->activeTab = 'upload';
             $this->originTruth = OriginType::Unknown->value;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Livewire/Feed/UploadPostForm.php` around lines 86 - 89, The form reset
after a successful post creation in UploadPostForm clears title, description,
sourceUrl, image, tagIds, tagSearch, originTruth and cuisineTruth but omits
importedImageUrl and activeTab, causing UI state inconsistencies; update the
post-success reset logic in the same method (where those other fields are reset)
to also clear importedImageUrl (set to null or empty string consistent with how
it’s declared) and reset activeTab to its default tab value (e.g., 'upload' or
the component’s initial default) so the form returns to a consistent initial
state.
tests/Feature/Import/UrlImportValidatorTest.php (1)

14-53: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Добавьте граничные SSRF-кейсы для IPv6 и диапазона 172.16/12.

Сейчас тесты не фиксируют верхнюю границу private-range и loopback IPv6, поэтому регрессия по 172.31.x.x/::1 может пройти незамеченной.

Предлагаемое расширение тестов
 it('rejects 172.16.x private range', function () {
     app(UrlImportValidator::class)->validate('http://172.16.0.1/image.jpg');
 })->throws(UnsafeImportUrlException::class);

+it('rejects 172.31.x private range', function () {
+    app(UrlImportValidator::class)->validate('http://172.31.255.255/image.jpg');
+})->throws(UnsafeImportUrlException::class);
+
+it('allows 172.32.x public range', function () {
+    $url = app(UrlImportValidator::class)->validate('http://172.32.0.1/image.jpg');
+
+    expect($url)->toBe('http://172.32.0.1/image.jpg');
+});
+
 it('rejects loopback ipv4', function () {
     app(UrlImportValidator::class)->validate('http://127.0.0.1/test');
 })->throws(UnsafeImportUrlException::class);
+
+it('rejects loopback ipv6', function () {
+    app(UrlImportValidator::class)->validate('http://[::1]/test');
+})->throws(UnsafeImportUrlException::class);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/Import/UrlImportValidatorTest.php` around lines 14 - 53, Add
boundary tests in UrlImportValidatorTest.php to cover the upper edge of the
172.16/12 private range and IPv6 loopback/ULA cases: call
UrlImportValidator::validate with a 172.31.255.255 address and assert it throws
UnsafeImportUrlException, add a complementary test for 172.32.0.1 that should be
allowed, and add tests that validate rejecting IPv6 loopback (http://[::1]/) and
rejecting IPv6 unique local address (e.g. http://[fc00::1]/) also expecting
UnsafeImportUrlException; keep using the same pattern as the existing specs so
failures surface if the validator misclassifies these edge addresses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Actions/Import/ImportFromUrlAction.php`:
- Around line 55-59: The hardcoded English message assigned to unsupportedReason
in ImportFromUrlAction (inside the catch for ImportFetchException) should be
replaced with a translatable string pulled from lang/*/import.php; update the
assignment to use the framework localization helper (e.g., __() or trans()) with
a new key like 'import.unsupported_reason_download_and_upload', and add that key
and its Russian/Bulgarian/English translations to lang/*/import.php so the UI
shows the correct localized text when ImportPreview is returned.
- Around line 44-51: When creating the new ImportPreview instance in
ImportFromUrlAction (the block that replaces provider and rewraps $preview into
new ImportPreview), ensure you also preserve the unsupportedReason (and any
other DTO fields you’re not explicitly copying) from the original $preview so an
unsupported OpenGraphImportAdapter result doesn't get turned into a "successful"
preview; update the ImportPreview constructor call to pass unsupportedReason:
$preview->unsupportedReason (or clone/copy all remaining DTO properties) so the
UI retains the refusal reason.

In `@app/Actions/Import/StoreImportedImageAction.php`:
- Around line 18-20: The SafeImportHttpClient's default max_html_bytes is still
being applied because download() calls $this->client->get($imageUrl) without
passing the image limit; update StoreImportedImageAction::download() to pass the
image-specific limit (max_image_bytes) into the HTTP client call or use the
client's method that accepts an explicit byte limit so the client does not
truncate by max_html_bytes; reference SafeImportHttpClient, download(), get(),
max_image_bytes and max_html_bytes when making the change.
- Around line 40-41: tempnam() создаёт реальный файл и возвращает путь без
расширения, а текущий код изменяет $tmpPath добавляя '.$extension' и записывает
в новый путь, оставляя исходный временный файл; исправьте это: вызовите $tmpPath
= tempnam(sys_get_temp_dir(), 'rg_import_'), запишите содержимое в именно этот
путь через file_put_contents($tmpPath, $body), затем если вам нужен файл с
расширением — выполните rename($tmpPath, $tmpPath . '.' . $extension) или
переместите/переименуйте файл в окончательное место; обновите код вокруг
tempnam(), $tmpPath и file_put_contents() в StoreImportedImageAction чтобы не
оставлять сирых файлов.

In `@app/Exceptions/Import/ImportFetchException.php`:
- Around line 9-16: The exception messages currently embed the full URL which
may leak sensitive query params; update ImportFetchException by adding a private
helper (e.g., sanitizeUrl) that strips query and fragment or redacts sensitive
query parameter values (tokens, api_key, access_token, etc.) and returns a safe
URL; then change requestFailed(...) and connectionError(...) to call this
sanitizer and include the sanitized URL in the new self(...) messages instead of
the raw $url.

In `@app/Livewire/Import/ImportUrlForm.php`:
- Around line 66-75: ImportUrlForm::usePreview() dispatches the
'import-preview-selected' event but UploadPostForm::applyImportPreview lacks the
listener attribute, so add the Livewire event listener to connect them: annotate
the applyImportPreview method in UploadPostForm with
#[On('import-preview-selected')] (or the framework's equivalent listener
attribute) so it receives the dispatched payload; ensure the method signature
accepts the payload keys (title, description, imageUrl, sourceUrl, provider) and
maps them to the upload form's properties.
- Around line 57-58: The catch block for ImportFetchException always sets
$this->error to the timeout localization key __('import.errors.timeout') which
is misleading; update the catch in ImportUrlForm to catch the exception as a
variable (ImportFetchException $e) and set $this->error to a more appropriate
value—either a generic fetch failure key like __('import.errors.fetch') or the
exception message ($e->getMessage()) if it’s safe to display, or combine both
(generic message plus sanitized exception detail) so users see an accurate,
non-misleading error.

In `@app/Support/Import/Adapters/DirectImageImportAdapter.php`:
- Around line 20-24: In DirectImageImportAdapter (and likewise in
StoreImportedImageAction) guard the Content-Type header before calling explode
by retrieving the header string, trimming and checking it's non-empty; if the
header is missing or empty, throw an ImportFetchException with a clear message
including the URL (or request context) instead of letting a TypeError occur, and
only then run strtolower(trim(explode(';', $contentType)[0])) and the existing
in_array check against $allowedMimes; ensure the same validation logic is
applied to both methods to keep behavior consistent.

In `@app/Support/Import/Adapters/OpenGraphImportAdapter.php`:
- Around line 33-37: В OpenGraphImportAdapter (где формируются $warnings —
строки на Line 33 и 36) уберите хардкод англоязычных сообщений и вместо них
возвращайте машиночитаемые коды/ключи (например 'warning.image_not_safe' и
'warning.no_image_found' или константы класса), чтобы доменный слой отдавал
только ключи; локализацию/перевод этих ключей выполняйте в UI-слое. Обновите
места, где формируются массивы $warnings в методах OpenGraphImportAdapter, чтобы
добавлять ключи вместо текста, и при необходимости добавьте enum/const в тот же
класс для централизованного списка ключей.

In `@app/Support/Import/ImportPreview.php`:
- Around line 7-14: The constructor currently accepts provider as a raw string;
change the type of the provider property in ImportPreview::__construct from
string to the ImportProvider enum (use ImportProvider as the declared type for
the public readonly $provider) so the DTO enforces allowed values; update any
callers that construct ImportPreview to pass an ImportProvider instance (or
cast/resolve strings to ImportProvider::from(...) or
ImportProvider::tryFrom(...)), and update any serialization/deserialization or
tests that expect a string to convert the enum to/from string where necessary
(e.g., when persisting or returning JSON).

In `@app/Support/Import/OpenGraphParser.php`:
- Around line 73-78: The current logic in OpenGraphParser.php that builds $base
from $pageUrl and checks str_starts_with($url, '/') incorrectly treats
protocol-relative URLs (those starting with '//') as path-relative; update the
branch that handles leading slashes to first detect protocol-relative URLs
(e.g., str_starts_with($url, '//')) and resolve them by prefixing the original
page scheme (from $parsed['scheme'] or default 'https') before returning,
otherwise keep the existing behavior of concatenating $base and path-relative
$url; ensure you reference the variables $parsed, $base and the check
str_starts_with($url, '/') when editing.

In `@app/Support/Import/SafeImportHttpClient.php`:
- Around line 18-21: The four hardcoded fallback values in SafeImportHttpClient
(the second args to config() for timeout, connect_timeout_seconds, max_redirects
and max_html_bytes) duplicate defaults from config/import.php; remove the
literal fallback arguments from the config() calls (leave single-argument
config('import.timeout_seconds') etc.) so the app consistently uses the
centralized config defaults, or if you intentionally want different fallbacks,
update them to match and add a comment explaining the divergence.
- Around line 14-41: The get method in SafeImportHttpClient currently checks
response size after loading the full body (vulnerable to DoS); update
SafeImportHttpClient::get to first perform a HEAD or initial request to inspect
the Content-Length header (use the same config values: import.max_html_bytes,
import.timeout_seconds, import.connect_timeout_seconds) and if Content-Length
exceeds max_html_bytes throw ImportFetchException::responseTooLarge($url,
$maxBytes) before downloading; if Content-Length is absent or untrusted, perform
the GET using a streaming/sink approach (Laravel HTTP streaming or sink to temp
file) and enforce a read-limit while downloading to abort and throw
ImportFetchException::responseTooLarge when the limit is exceeded, keeping the
existing ConnectionException catch and the failed-status check behavior.

In `@app/Support/Import/UrlImportValidator.php`:
- Around line 40-59: The validator currently only rejects literal IPs and '::1'
but allows hostnames that resolve to private/internal addresses and misses IPv6
link-local/ULA ranges; update UrlImportValidator so that when validating a URL
you resolve its hostname (e.g., in the same method that currently calls
assertPublicIp) using a safe DNS lookup (dns_get_record or getaddrinfo
equivalent) and iterate all resolved addresses, passing each to assertPublicIp
for checking; extend assertPublicIp to detect and reject all
private/loopback/unspecified ranges for IPv4 (127.0.0.0/8, 10.0.0.0/8,
172.16.0.0/12, 192.168.0.0/16) and for IPv6 reject ::1, link-local (fe80::/10),
unique local addresses (fc00::/7), unspecified/unsuitable addresses, and ensure
any resolved private address triggers
UnsafeImportUrlException::privateAddress($url); also handle DNS resolution
failures conservatively (treat as invalid) and check every returned address, not
just the first.

In `@config/import.php`:
- Line 4: The import-from-URL feature is enabled by default via the config entry
'enabled' => env('IMPORT_FROM_URL_ENABLED', true); change the opt-in default to
false by updating the env fallback so the config key 'enabled' uses
env('IMPORT_FROM_URL_ENABLED', false) instead; ensure any related README/docs
and tests that assume the feature on are updated to reflect the new default and
verify behavior when IMPORT_FROM_URL_ENABLED is explicitly set to true.
- Line 6: The config currently allows insecure http imports which enables MITM
content tampering; remove "http" from the default allowed schemes in
config/import.php (change 'allowed_schemes' to only include 'https') and update
the constant ALLOWED_SCHEMES in app/Support/Import/UrlImportValidator.php to
['https'] to enforce this by default; if you need to keep optional http support,
instead add explicit handling in the import flow: detect URLs with scheme 'http'
(before UrlImportValidator runs), surface a clear user warning + require
explicit user confirmation (or an opt-in flag) before proceeding.

In `@lang/ru/import.php`:
- Line 4: The Russian localization is inconsistent: the 'from_url' entry
currently reads "Импорт по ссылке" while another entry reads "Импорт по URL";
pick one term and make them consistent across the file by updating the
'from_url' value (or the "Импорт по URL" occurrence) so every import-by-URL
label uses the same phrasing (either "Импорт по ссылке" everywhere or "Импорт по
URL" everywhere).

In `@resources/views/livewire/feed/upload-post-form.blade.php`:
- Around line 40-42: The form uses an inline style to hide itself when imports
are allowed and the active tab is 'import'; replace the inline style on the form
element with a CSS class or an Alpine.js directive for maintainability. Update
the form element (the tag that currently contains wire:submit.prevent="submit"
and the condition referencing $uploadSettings->featureFlag('allow_url_imports')
&& $activeTab === 'import') to apply a conditional `@class` (e.g., hidden) or an
x-show binding instead of style="display:none", ensuring the same conditional
logic controls the visibility but without inline styles.

In `@tests/Browser/ImportUrlBrowserTest.php`:
- Around line 19-39: Update the two browser tests "import url form shows when
import tab is clicked" and "import url input is present on import tab" to assert
visibility instead of mere presence: replace the assertPresent checks for the
selectors '[data-testid="import-url-form"]' and
'[data-testid="import-url-input"]' with assertVisible so the tests verify the
elements are actually visible after clicking the '[data-testid="import-tab"]'
toggle.

In `@tests/Feature/Import/DirectImageImportAdapterTest.php`:
- Around line 31-41: The test "returns preview with source url as title
fallback" currently only asserts $preview->sourceUrl and never checks the
fallback title; update the test to assert that $preview->title equals the source
URL when no title is provided by calling
app(DirectImageImportAdapter::class)->preview('https://example.com/photo.png')
and then asserting both $preview->sourceUrl and $preview->title (i.e.
expect($preview->title)->toBe('https://example.com/photo.png')) to ensure
DirectImageImportAdapter::preview implements the title fallback.

In `@tests/Feature/Import/ImportFeatureFlagTest.php`:
- Around line 43-57: The test currently only asserts that $preview is not null;
strengthen it by asserting that ImportFromUrlAction::handle returned a supported
preview object and contains expected fields (e.g., title "Test" or content
snippet) — locate the test "allows url import when feature flag is absent using
default true", call
app(ImportFromUrlAction::class)->handle('https://example.com/page') as before,
then add assertions on $preview to verify it reports supported (e.g.,
->isSupported() or instanceof expected preview class) and that $preview->title
(or the appropriate property/method on the returned preview) equals "Test"
and/or contains expected HTML-derived data.

In `@tests/Feature/Import/StoreImportedImageActionTest.php`:
- Around line 10-30: Текущее ожидание в тесте недостаточно строгое — проверка
expect($file->getClientOriginalName())->toContain('image') не гарантирует
расширение или точное имя; обновите утверждение в тесте
StoreImportedImageActionTest чтобы проверять либо точное имя файла (например
ожидаемое getClientOriginalName() равное 'image.jpg' или другому известному
значению) либо явно проверять расширение через pathinfo или str_ends_with на
'.jpg' (работая с $file из StoreImportedImageAction::download и экземпляром
UploadedFile), чтобы обеспечить конкретную валидацию имени/расширения.

In `@tests/Feature/Livewire/UploadPostFormImportIntegrationTest.php`:
- Around line 38-52: Tests for UploadPostForm rely on the allow_url_imports
feature-flag but don't set/reset it, causing order-dependent failures; fix both
tests by explicitly setting the flag before exercising the Livewire component
(for example call config(['features.allow_url_imports' => true]) or use
Settings::set('allow_url_imports', true)) and then clear any cached settings via
the settings cache helper (e.g., app(SettingsManager::class)->clearCache() or
SettingsManager::flushCache()) so UploadPostForm reads the intended state; do
this setup at the start of the tests that reference UploadPostForm (and
optionally reset the flag after the test).

---

Outside diff comments:
In `@app/Livewire/Feed/UploadPostForm.php`:
- Around line 86-89: The form reset after a successful post creation in
UploadPostForm clears title, description, sourceUrl, image, tagIds, tagSearch,
originTruth and cuisineTruth but omits importedImageUrl and activeTab, causing
UI state inconsistencies; update the post-success reset logic in the same method
(where those other fields are reset) to also clear importedImageUrl (set to null
or empty string consistent with how it’s declared) and reset activeTab to its
default tab value (e.g., 'upload' or the component’s initial default) so the
form returns to a consistent initial state.

In `@tests/Feature/Import/UrlImportValidatorTest.php`:
- Around line 14-53: Add boundary tests in UrlImportValidatorTest.php to cover
the upper edge of the 172.16/12 private range and IPv6 loopback/ULA cases: call
UrlImportValidator::validate with a 172.31.255.255 address and assert it throws
UnsafeImportUrlException, add a complementary test for 172.32.0.1 that should be
allowed, and add tests that validate rejecting IPv6 loopback (http://[::1]/) and
rejecting IPv6 unique local address (e.g. http://[fc00::1]/) also expecting
UnsafeImportUrlException; keep using the same pattern as the existing specs so
failures surface if the validator misclassifies these edge addresses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 09f90e43-323e-42a3-bb98-3469bd417c32

📥 Commits

Reviewing files that changed from the base of the PR and between da93358 and 4a538ca.

📒 Files selected for processing (46)
  • app/Actions/Import/ImportFromUrlAction.php
  • app/Actions/Import/StoreImportedImageAction.php
  • app/Enums/ImportProvider.php
  • app/Exceptions/Import/ImportFetchException.php
  • app/Exceptions/Import/UnsafeImportUrlException.php
  • app/Exceptions/Import/UrlImportDisabledException.php
  • app/Livewire/Feed/UploadPostForm.php
  • app/Livewire/Import/ImportUrlForm.php
  • app/Support/Import/Adapters/DirectImageImportAdapter.php
  • app/Support/Import/Adapters/OpenGraphImportAdapter.php
  • app/Support/Import/ImportPreview.php
  • app/Support/Import/ImportProviderDetector.php
  • app/Support/Import/OpenGraphMetadata.php
  • app/Support/Import/OpenGraphParser.php
  • app/Support/Import/SafeImportHttpClient.php
  • app/Support/Import/UrlImportValidator.php
  • app/Support/Settings/ProjectSettingsManager.php
  • config/import.php
  • docs/import/phase-50-url-import-audit.md
  • docs/import/phase-50-url-import-review.md
  • docs/import/url-import.md
  • lang/bg/import.php
  • lang/en/import.php
  • lang/ru/import.php
  • resources/views/livewire/feed/upload-post-form.blade.php
  • resources/views/livewire/import/import-url-form.blade.php
  • tests/Browser/ImportUrlBrowserTest.php
  • tests/Feature/Docs/Phase50ImportConfigTest.php
  • tests/Feature/Docs/Phase50ImportProviderTest.php
  • tests/Feature/Docs/Phase50ReviewChecklistTest.php
  • tests/Feature/Docs/Phase50UrlImportAuditTest.php
  • tests/Feature/Docs/Phase50UrlImportDocTest.php
  • tests/Feature/I18n/ImportTranslationKeysTest.php
  • tests/Feature/Import/DirectImageImportAdapterTest.php
  • tests/Feature/Import/ImportFeatureFlagTest.php
  • tests/Feature/Import/ImportFromUrlActionTest.php
  • tests/Feature/Import/ImportPreviewDtoTest.php
  • tests/Feature/Import/ImportProviderDetectorTest.php
  • tests/Feature/Import/OpenGraphImportAdapterTest.php
  • tests/Feature/Import/OpenGraphParserTest.php
  • tests/Feature/Import/SafeImportHttpClientTest.php
  • tests/Feature/Import/StoreImportedImageActionTest.php
  • tests/Feature/Import/UrlImportValidatorTest.php
  • tests/Feature/Livewire/ImportUrlFormErrorHandlingTest.php
  • tests/Feature/Livewire/ImportUrlFormTest.php
  • tests/Feature/Livewire/UploadPostFormImportIntegrationTest.php

Comment thread app/Actions/Import/ImportFromUrlAction.php
Comment thread app/Actions/Import/ImportFromUrlAction.php Outdated
Comment thread app/Actions/Import/StoreImportedImageAction.php Outdated
Comment thread app/Actions/Import/StoreImportedImageAction.php Outdated
Comment thread app/Exceptions/Import/ImportFetchException.php Outdated
Comment thread tests/Browser/ImportUrlBrowserTest.php
Comment thread tests/Feature/Import/DirectImageImportAdapterTest.php Outdated
Comment on lines +43 to +57
it('allows url import when feature flag is absent using default true', function () {
app(ProjectSettingsManager::class)->flush();

Http::fake([
'example.com/page' => Http::response(
'<head><title>Test</title></head>',
200,
['Content-Type' => 'text/html']
),
]);

$preview = app(ImportFromUrlAction::class)->handle('https://example.com/page');

expect($preview)->not->toBeNull();
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Усильте проверку результата для теста поведения по умолчанию.

Проверка expect($preview)->not->toBeNull() на строке 56 является недостаточно специфичной. Рекомендуется дополнительно проверить, что preview действительно поддерживается и содержит ожидаемые данные.

♻️ Предлагаемое улучшение
     $preview = app(ImportFromUrlAction::class)->handle('https://example.com/page');
 
-    expect($preview)->not->toBeNull();
+    expect($preview)->not->toBeNull();
+    expect($preview->isSupported())->toBeTrue();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/Import/ImportFeatureFlagTest.php` around lines 43 - 57, The
test currently only asserts that $preview is not null; strengthen it by
asserting that ImportFromUrlAction::handle returned a supported preview object
and contains expected fields (e.g., title "Test" or content snippet) — locate
the test "allows url import when feature flag is absent using default true",
call app(ImportFromUrlAction::class)->handle('https://example.com/page') as
before, then add assertions on $preview to verify it reports supported (e.g.,
->isSupported() or instanceof expected preview class) and that $preview->title
(or the appropriate property/method on the returned preview) equals "Test"
and/or contains expected HTML-derived data.

Comment on lines +10 to +30
it('downloads and stores imported image as uploaded file', function () {
Storage::fake('public');

// Minimal 1×1 white JPEG bytes
$jpegBytes = base64_decode(
'/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAARC'.
'AABAAEBASISAAREBAREF/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAA'.
'AAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJQAAB//2Q=='
);

Http::fake([
'example.com/image.jpg' => Http::response($jpegBytes, 200, [
'Content-Type' => 'image/jpeg',
]),
]);

$file = app(StoreImportedImageAction::class)->download('https://example.com/image.jpg');

expect($file)->toBeInstanceOf(UploadedFile::class);
expect($file->getClientOriginalName())->toContain('image');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Усильте проверку имени файла.

На строке 29 проверка toContain('image') является недостаточно специфичной. Рекомендуется проверить конкретное расширение файла или полное имя, чтобы убедиться, что файл корректно именован.

♻️ Предлагаемое улучшение
     expect($file)->toBeInstanceOf(UploadedFile::class);
-    expect($file->getClientOriginalName())->toContain('image');
+    expect($file->getClientOriginalName())->toMatch('/image.*\.(jpg|jpeg)$/i');

Либо проверьте конкретное ожидаемое имя, если оно известно.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('downloads and stores imported image as uploaded file', function () {
Storage::fake('public');
// Minimal 1×1 white JPEG bytes
$jpegBytes = base64_decode(
'/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAARC'.
'AABAAEBASISAAREBAREF/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAA'.
'AAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJQAAB//2Q=='
);
Http::fake([
'example.com/image.jpg' => Http::response($jpegBytes, 200, [
'Content-Type' => 'image/jpeg',
]),
]);
$file = app(StoreImportedImageAction::class)->download('https://example.com/image.jpg');
expect($file)->toBeInstanceOf(UploadedFile::class);
expect($file->getClientOriginalName())->toContain('image');
});
it('downloads and stores imported image as uploaded file', function () {
Storage::fake('public');
// Minimal 1×1 white JPEG bytes
$jpegBytes = base64_decode(
'/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAARC'.
'AABAAEBASISAAREBAREF/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAA'.
'AAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJQAAB//2Q=='
);
Http::fake([
'example.com/image.jpg' => Http::response($jpegBytes, 200, [
'Content-Type' => 'image/jpeg',
]),
]);
$file = app(StoreImportedImageAction::class)->download('https://example.com/image.jpg');
expect($file)->toBeInstanceOf(UploadedFile::class);
expect($file->getClientOriginalName())->toMatch('/image.*\.(jpg|jpeg)$/i');
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/Import/StoreImportedImageActionTest.php` around lines 10 - 30,
Текущее ожидание в тесте недостаточно строгое — проверка
expect($file->getClientOriginalName())->toContain('image') не гарантирует
расширение или точное имя; обновите утверждение в тесте
StoreImportedImageActionTest чтобы проверять либо точное имя файла (например
ожидаемое getClientOriginalName() равное 'image.jpg' или другому известному
значению) либо явно проверять расширение через pathinfo или str_ends_with на
'.jpg' (работая с $file из StoreImportedImageAction::download и экземпляром
UploadedFile), чтобы обеспечить конкретную валидацию имени/расширения.

Comment on lines +38 to +52
it('upload form has import tab option', function () {
$user = User::factory()->create();

Livewire::actingAs($user)
->test(UploadPostForm::class)
->assertSee('data-testid="import-tab"', false);
});

it('upload form has upload tab option', function () {
$user = User::factory()->create();

Livewire::actingAs($user)
->test(UploadPostForm::class)
->assertSee('data-testid="upload-tab"', false);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Зафиксируйте feature-flag в тестах вкладок, чтобы убрать order-dependency.

Проверки import-tab/upload-tab зависят от allow_url_imports, но состояние настроек здесь не закреплено явно и не сбрасывается кэш менеджера.

Предлагаемая стабилизация тестов
 use App\Livewire\Feed\UploadPostForm;
+use App\Models\ProjectSettings;
 use App\Models\User;
+use App\Support\Settings\ProjectSettingsManager;
 use Livewire\Livewire;
 
+beforeEach(function () {
+    ProjectSettings::factory()->create([
+        'feature_flags' => ['allow_url_imports' => true],
+    ]);
+
+    app(ProjectSettingsManager::class)->flush();
+});
+
 it('fills upload form from import preview', function () {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/Livewire/UploadPostFormImportIntegrationTest.php` around lines
38 - 52, Tests for UploadPostForm rely on the allow_url_imports feature-flag but
don't set/reset it, causing order-dependent failures; fix both tests by
explicitly setting the flag before exercising the Livewire component (for
example call config(['features.allow_url_imports' => true]) or use
Settings::set('allow_url_imports', true)) and then clear any cached settings via
the settings cache helper (e.g., app(SettingsManager::class)->clearCache() or
SettingsManager::flushCache()) so UploadPostForm reads the intended state; do
this setup at the start of the tests that reference UploadPostForm (and
optionally reset the flag after the test).

menvil and others added 5 commits June 10, 2026 21:30
…s, DNS resolution

- UrlImportValidator: resolve hostnames via DNS to prevent SSRF bypass; block IPv6
  link-local (fe80::/10) and ULA (fc00::/7); use protected resolveHostname() for tests
- SafeImportHttpClient: manually follow redirects with per-hop SSRF validation; add
  optional $maxBytes param; add Content-Length pre-check; remove fallback defaults
- DirectImageImportAdapter: guard empty Content-Type; pass max_image_bytes to client;
  set title fallback to source URL
- StoreImportedImageAction: fix tempnam file leak via rename; pass max_image_bytes;
  guard empty Content-Type
- ImportFetchException: sanitize sensitive query params from URLs in messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, #[On] listener

- ImportPreview: change provider type from string to ImportProvider enum
- DirectImageImportAdapter, OpenGraphImportAdapter, ImportFromUrlAction: use
  ImportProvider enum values; OpenGraphImportAdapter uses machine-readable warning keys
- ImportFromUrlAction: preserve unsupportedReason when rewrapping social provider
  preview; use translation key instead of hardcoded English for unsupported message
- OpenGraphParser: preserve port in base URL when resolving relative URLs; handle
  protocol-relative (//...) URLs before root-relative check
- ImportProviderDetector: remove svg from IMAGE_EXTENSIONS (SVG not in allowed MIMEs)
- ImportUrlForm: add report() to generic catch; use fetch_failed key instead of
  misleading timeout key for ImportFetchException; store provider as ->value (string)
- UploadPostForm: add #[On('import-preview-selected')] listener so event from
  ImportUrlForm actually triggers applyImportPreview; reset importedImageUrl and
  activeTab after successful post submit
- Blade: replace inline style="display:none" with @Class hidden utility; show preview
  when description-only (no title/image); add referrerpolicy="no-referrer" to img
- config/import.php: change enabled default to false; restrict allowed_schemes to https
- Translations: add unsupported_reason_download_and_upload and errors.fetch_failed keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/Feature/Import/UrlImportValidatorTest.php (1)

79-83: ⚠️ Potential issue | 🟠 Major

Привести валидацию URL к единому источнику допустимых схем
Тест на tests/Feature/Import/UrlImportValidatorTest.php (79-83) ожидает, что http:// проходит, но в config/import.php разрешён только https, тогда как UrlImportValidator использует захардкоженный private const ALLOWED_SCHEMES = ['http', 'https']. Либо валидатор должен читать allowed_schemes из конфигурации, либо нужно синхронизировать конфиг и тест (убрав http).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/Import/UrlImportValidatorTest.php` around lines 79 - 83, The
UrlImportValidator currently uses a hardcoded private const ALLOWED_SCHEMES =
['http','https'] while the canonical list lives in
config('import.allowed_schemes'); update UrlImportValidator to read allowed
schemes from the configuration (e.g., use config('import.allowed_schemes') with
a sensible default) instead of the private const, and remove or reconcile the
constant; ensure the validate method and any tests (UrlImportValidatorTest) rely
on the centralized config so the test expecting 'http' passes or adjust
config/import.php to include 'http' if you prefer changing config instead.
app/Livewire/Feed/UploadPostForm.php (1)

124-132: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Импорт-превью не подключён к фактической отправке изображения.

applyImportPreview() заполняет только importedImageUrl, но submit() валидирует/передаёт только $this->image (файл). В текущем виде подтверждение URL-импорта может упираться в обязательный image или сохранять пост без импортированного изображения.

Нужно связать importedImageUrl с submit-пайплайном (конвертировать URL в UploadedFile через импорт-экшен до валидации/создания поста или эквивалентно на сервере).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Livewire/Feed/UploadPostForm.php` around lines 124 - 132,
applyImportPreview sets importedImageUrl but submit() only validates/uses
$this->image (the UploadedFile), so posts created from an import URL either fail
validation or omit the image; modify submit() (or the submit pipeline) to detect
when $this->importedImageUrl is present and, before validation/creation, run the
import action to download/convert that URL into an UploadedFile and assign it to
$this->image (or otherwise produce an object acceptable to the existing
validation rules), then proceed with the existing validation and post creation
logic; reference applyImportPreview, submit, importedImageUrl, image and the
import/download action when implementing this conversion.
♻️ Duplicate comments (1)
lang/ru/import.php (1)

4-4: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Устраните несогласованность терминологии.

Строка 4 использует «Импорт по ссылке», в то время как строка 21 содержит «Импорт по URL». Для обеспечения консистентности пользовательского интерфейса рекомендуется использовать один термин во всех строках локализации.

✨ Предлагаемые варианты исправления

Вариант 1 (использовать «по ссылке» везде):

-    'feature_disabled' => 'Импорт по URL в данный момент отключён.',
+    'feature_disabled' => 'Импорт по ссылке в данный момент отключён.',

Вариант 2 (использовать «по URL» везде):

-    'from_url' => 'Импорт по ссылке',
+    'from_url' => 'Импорт по URL',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lang/ru/import.php` at line 4, The translation for the key 'from_url'
currently reads "Импорт по ссылке" but another entry in this file uses "Импорт
по URL" — make the terminology consistent by updating the 'from_url' value to
match the chosen term (e.g., change 'from_url' => 'Импорт по URL') and scan the
rest of lang/ru/import.php to replace any other occurrences so all entries use
the same phrase.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Actions/Import/StoreImportedImageAction.php`:
- Around line 44-47: Проверь результат вызова rename() после создания
$baseTmpPath и перед вызовом file_put_contents(): если rename($baseTmpPath,
$tmpPath) вернул false — обработай ошибку (удали или закрой $baseTmpPath при
необходимости, логируй и выбрось исключение или верни ошибку), чтобы не
оставлять сирот и не писать данные в неожиданный путь; используемые символы для
правки: tempnam, $baseTmpPath, $tmpPath, rename(), file_put_contents().

In `@app/Support/Import/Adapters/DirectImageImportAdapter.php`:
- Around line 23-25: В методе импортера DirectImageImportAdapter не вставляйте
сырой $url в текст ImportFetchException — это может слить токены из
tokenized/presigned ссылок; заменить вставку полного $url в сообщениях
исключений (в местах, где выбрасывается ImportFetchException при пустом
$rawContentType и в другом месте на 29-30) на безопасную информацию (например
только хост/порт через parse_url или на маскированную/общую метку типа
"presigned URL") либо вовсе опустить URL, оставив контекст ошибки; обновите оба
места (в классе DirectImageImportAdapter где используется ImportFetchException и
переменная $rawContentType / $url) так, чтобы логировались только безопасные
значения.

In `@app/Support/Import/Adapters/OpenGraphImportAdapter.php`:
- Around line 34-37: OpenGraphImportAdapter is adding warning keys
('warning.image_not_safe' and 'warning.no_image_found') but those keys are
missing in lang/en/import.php and lang/ru/import.php and the warnings never
reach the UI because ImportUrlForm::usePreview does not include warnings in the
import-preview-selected payload and UploadPostForm::applyImportPreview does not
accept them; fix by either (A) adding the two i18n entries to both
lang/en/import.php and lang/ru/import.php and updating ImportUrlForm::usePreview
to include a warnings field in the emitted import-preview-selected payload and
updating UploadPostForm::applyImportPreview to accept and attach warnings to the
preview state, or (B) if warnings are premature, remove/stop setting them in
OpenGraphImportAdapter until the preview flow supports them—update the
OpenGraphImportAdapter, ImportUrlForm::usePreview and
UploadPostForm::applyImportPreview accordingly so keys and propagation are
consistent.

In `@app/Support/Import/UrlImportValidator.php`:
- Around line 59-62: Функция resolveHostname в UrlImportValidator использует
gethostbynamel, который возвращает только IPv4; замените логику на вызов
dns_get_record($host, DNS_A | DNS_AAAA) в методе resolveHostname чтобы получать
и A, и AAAA записи, корректно обрабатывайте возвраты false/пустой массив и
нормализуйте возвращаемую структуру (список IP-адресов) так, чтобы дальнейшая
валидация использовала оба типа адресов; уберите подавление ошибок через @ и
добавьте обработку ошибок DNS-вызова.

---

Outside diff comments:
In `@app/Livewire/Feed/UploadPostForm.php`:
- Around line 124-132: applyImportPreview sets importedImageUrl but submit()
only validates/uses $this->image (the UploadedFile), so posts created from an
import URL either fail validation or omit the image; modify submit() (or the
submit pipeline) to detect when $this->importedImageUrl is present and, before
validation/creation, run the import action to download/convert that URL into an
UploadedFile and assign it to $this->image (or otherwise produce an object
acceptable to the existing validation rules), then proceed with the existing
validation and post creation logic; reference applyImportPreview, submit,
importedImageUrl, image and the import/download action when implementing this
conversion.

In `@tests/Feature/Import/UrlImportValidatorTest.php`:
- Around line 79-83: The UrlImportValidator currently uses a hardcoded private
const ALLOWED_SCHEMES = ['http','https'] while the canonical list lives in
config('import.allowed_schemes'); update UrlImportValidator to read allowed
schemes from the configuration (e.g., use config('import.allowed_schemes') with
a sensible default) instead of the private const, and remove or reconcile the
constant; ensure the validate method and any tests (UrlImportValidatorTest) rely
on the centralized config so the test expecting 'http' passes or adjust
config/import.php to include 'http' if you prefer changing config instead.

---

Duplicate comments:
In `@lang/ru/import.php`:
- Line 4: The translation for the key 'from_url' currently reads "Импорт по
ссылке" but another entry in this file uses "Импорт по URL" — make the
terminology consistent by updating the 'from_url' value to match the chosen term
(e.g., change 'from_url' => 'Импорт по URL') and scan the rest of
lang/ru/import.php to replace any other occurrences so all entries use the same
phrase.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f1e201e-6ea7-4b8d-89da-ccc7ac994328

📥 Commits

Reviewing files that changed from the base of the PR and between 4a538ca and c27ed5a.

📒 Files selected for processing (29)
  • app/Actions/Import/ImportFromUrlAction.php
  • app/Actions/Import/StoreImportedImageAction.php
  • app/Exceptions/Import/ImportFetchException.php
  • app/Livewire/Feed/UploadPostForm.php
  • app/Livewire/Import/ImportUrlForm.php
  • app/Support/Import/Adapters/DirectImageImportAdapter.php
  • app/Support/Import/Adapters/OpenGraphImportAdapter.php
  • app/Support/Import/ImportPreview.php
  • app/Support/Import/ImportProviderDetector.php
  • app/Support/Import/OpenGraphParser.php
  • app/Support/Import/SafeImportHttpClient.php
  • app/Support/Import/UrlImportValidator.php
  • config/import.php
  • lang/bg/import.php
  • lang/en/import.php
  • lang/ru/import.php
  • resources/views/livewire/feed/upload-post-form.blade.php
  • resources/views/livewire/import/import-url-form.blade.php
  • tests/Browser/ImportUrlBrowserTest.php
  • tests/Feature/Docs/Phase50ImportConfigTest.php
  • tests/Feature/Docs/Phase50ImportProviderTest.php
  • tests/Feature/I18n/ImportTranslationKeysTest.php
  • tests/Feature/Import/DirectImageImportAdapterTest.php
  • tests/Feature/Import/ImportFromUrlActionTest.php
  • tests/Feature/Import/ImportPreviewDtoTest.php
  • tests/Feature/Import/OpenGraphImportAdapterTest.php
  • tests/Feature/Import/OpenGraphParserTest.php
  • tests/Feature/Import/SafeImportHttpClientTest.php
  • tests/Feature/Import/UrlImportValidatorTest.php

Comment thread app/Actions/Import/StoreImportedImageAction.php
Comment thread app/Support/Import/Adapters/DirectImageImportAdapter.php
Comment thread app/Support/Import/Adapters/OpenGraphImportAdapter.php Outdated
Comment thread app/Support/Import/UrlImportValidator.php
menvil and others added 2 commits June 10, 2026 22:18
…scheme config, import submit

- StoreImportedImageAction: check rename() return value; unlink base file and throw on failure
- DirectImageImportAdapter + StoreImportedImageAction: replace raw $url in exception
  messages with parse_url host only to avoid leaking presigned tokens
- OpenGraphImportAdapter: remove orphaned warning key strings (not in lang files, not
  propagated through event chain) — imageUrl null when unsafe/missing is sufficient signal
- UrlImportValidator: replace @gethostbynamel (IPv4 only) with dns_get_record(DNS_A|DNS_AAAA)
  to catch hostnames with only AAAA records pointing at private IPv6 space; remove @
  suppression; wire ALLOWED_SCHEMES from config('import.allowed_schemes') instead of
  hardcoded const so validator and config stay in sync
- UploadPostForm::submit(): download importedImageUrl via StoreImportedImageAction before
  $this->validate() so import-flow posts are not rejected by required image validation
- lang/ru: unify from_url to 'Импорт по URL' consistent with unsupported_reason key
- Tests: update UrlImportValidatorTest (http → rejects), OpenGraphImportAdapterTest
  (remove warning assertions), add UploadPostFormImportIntegrationTest submit-with-import test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@menvil
menvil merged commit d95e249 into main Jun 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant