Replaces every raw GuzzleHttp\Client usage (Nominatim, web.archive.org, webmention fetch/discover/send, Bridgy syndication, IndieAuth client_id lookup, contact avatar/h-card fetch, profile image download, and the CloudConvert screenshot pipeline) with Illuminate\Support\Facades\Http, and enables Http::preventStrayRequests() globally in tests so any un-faked outbound call now fails loudly instead of silently hitting the network. The CloudConvert retry-until-finished middleware in AppServiceProvider is replaced by a plain polling loop in SaveScreenshot, which also fixes a latent bug where the old middleware decoded a response object instead of its body. Bridgy syndication jobs keep their tries=1/no-retry semantics unchanged to avoid duplicate publishes. Guzzle's PSR-7 helpers (Header, UriResolver, Utils) stay in SendWebMentions since Http has no equivalent for them. Every affected test file's Guzzle MockHandler/HandlerStack boilerplate is replaced with Http::fake()/Http::sequence(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P8j7cJhCiYDsGUgB7obKNQ
153 lines
4.9 KiB
PHP
153 lines
4.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Contact;
|
||
use Illuminate\Filesystem\Filesystem;
|
||
use Illuminate\Http\Client\RequestException;
|
||
use Illuminate\Http\RedirectResponse;
|
||
use Illuminate\Support\Arr;
|
||
use Illuminate\Support\Facades\Http;
|
||
use Illuminate\View\View;
|
||
|
||
class ContactsController extends Controller
|
||
{
|
||
/**
|
||
* List the currect contacts that can be edited.
|
||
*/
|
||
public function index(): View
|
||
{
|
||
$contacts = Contact::all();
|
||
|
||
return view('admin.contacts.index', compact('contacts'));
|
||
}
|
||
|
||
/**
|
||
* Display the form to add a new contact.
|
||
*/
|
||
public function create(): View
|
||
{
|
||
return view('admin.contacts.create');
|
||
}
|
||
|
||
/**
|
||
* Process the request to add a new contact.
|
||
*/
|
||
public function store(): RedirectResponse
|
||
{
|
||
$contact = new Contact;
|
||
$contact->name = request()->input('name');
|
||
$contact->nick = request()->input('nick');
|
||
$contact->homepage = request()->input('homepage');
|
||
$contact->twitter = request()->input('twitter');
|
||
$contact->facebook = request()->input('facebook');
|
||
$contact->save();
|
||
|
||
return redirect('/admin/contacts');
|
||
}
|
||
|
||
/**
|
||
* Show the form to edit an existing contact.
|
||
*/
|
||
public function edit(int $contactId): View
|
||
{
|
||
$contact = Contact::findOrFail($contactId);
|
||
|
||
return view('admin.contacts.edit', compact('contact'));
|
||
}
|
||
|
||
/**
|
||
* Process the request to edit a contact.
|
||
*
|
||
* @todo Allow saving profile pictures for people without homepages
|
||
*/
|
||
public function update(int $contactId): RedirectResponse
|
||
{
|
||
$contact = Contact::findOrFail($contactId);
|
||
$contact->name = request()->input('name');
|
||
$contact->nick = request()->input('nick');
|
||
$contact->homepage = request()->input('homepage');
|
||
$contact->twitter = request()->input('twitter');
|
||
$contact->facebook = request()->input('facebook');
|
||
$contact->save();
|
||
|
||
if (request()->hasFile('avatar') && (request()->input('homepage') != '')) {
|
||
$dir = parse_url(request()->input('homepage'), PHP_URL_HOST);
|
||
$destination = public_path().'/assets/profile-images/'.$dir;
|
||
$filesystem = new Filesystem;
|
||
if ($filesystem->isDirectory($destination) === false) {
|
||
$filesystem->makeDirectory($destination);
|
||
}
|
||
request()->file('avatar')->move($destination, 'image');
|
||
}
|
||
|
||
return redirect('/admin/contacts');
|
||
}
|
||
|
||
/**
|
||
* Process the request to delete a contact.
|
||
*/
|
||
public function destroy(int $contactId): RedirectResponse
|
||
{
|
||
$contact = Contact::findOrFail($contactId);
|
||
$contact->delete();
|
||
|
||
return redirect('/admin/contacts');
|
||
}
|
||
|
||
/**
|
||
* Download the avatar for a contact.
|
||
*
|
||
* This method attempts to find the microformat marked-up profile image
|
||
* from a given homepage and save it accordingly
|
||
*
|
||
* @return RedirectResponse|View
|
||
*/
|
||
public function getAvatar(int $contactId)
|
||
{
|
||
// Initialising
|
||
$avatarURL = null;
|
||
$avatar = null;
|
||
$contact = Contact::findOrFail($contactId);
|
||
if ($contact->homepage !== null && mb_strlen($contact->homepage) !== 0) {
|
||
try {
|
||
$response = Http::throw()->get($contact->homepage);
|
||
} catch (RequestException $e) {
|
||
return redirect('/admin/contacts/'.$contactId.'/edit')
|
||
->with('error', 'Bad resposne from contact’s homepage');
|
||
}
|
||
$mf2 = \Mf2\parse($response->body(), $contact->homepage);
|
||
foreach ($mf2['items'] as $microformat) {
|
||
if (Arr::get($microformat, 'type.0') === 'h-card') {
|
||
$avatarURL = Arr::get($microformat, 'properties.photo.0.value');
|
||
break;
|
||
}
|
||
}
|
||
if ($avatarURL !== null) {
|
||
try {
|
||
$avatar = Http::throw()->get($avatarURL);
|
||
} catch (RequestException $e) {
|
||
return redirect('/admin/contacts/'.$contactId.'/edit')
|
||
->with('error', 'Unable to download avatar');
|
||
}
|
||
}
|
||
if ($avatar !== null) {
|
||
$directory = public_path().'/assets/profile-images/'.parse_url($contact->homepage, PHP_URL_HOST);
|
||
$filesystem = new Filesystem;
|
||
if ($filesystem->isDirectory($directory) === false) {
|
||
$filesystem->makeDirectory($directory);
|
||
}
|
||
$filesystem->put($directory.'/image', $avatar->body());
|
||
|
||
return view('admin.contacts.getavatarsuccess', [
|
||
'homepage' => parse_url($contact->homepage, PHP_URL_HOST),
|
||
]);
|
||
}
|
||
}
|
||
|
||
return redirect('/admin/contacts/'.$contactId.'/edit');
|
||
}
|
||
}
|