route('login'); } $s = $s ?: request('s'); $type = in_array(request('type'), ['0', '1'], true) ? request('type') : ''; $page = max(1, (int) request('pg', 1)); $result = $this->api->get(self::RESOURCE, [ 'action' => 'list', 'page' => $page, 's' => $s, 'type' => $type, ]); return view('site.ads', $this->shared() + [ 'allAds' => $this->paginate($result, $page), 'totals' => $result['totals'] ?? ['ads' => 0, 'views' => 0, 'clicks' => 0], 'apiError' => ($result['ok'] ?? false) === true ? null : __('site.page_ads_api_error'), 's' => $s, 'type' => $type, ]); } public function create() { if (Auth::check() === false) { Auth::logout(); return redirect()->route('login'); } return view('site.adsCreate', $this->shared()); } public function store(Request $request) { if (Auth::check() === false) { Auth::logout(); return redirect()->route('login'); } $fail = $this->validateInput($request, route('site.ads.c'), true); if ($fail) { return $fail; } $result = $this->api->post(self::RESOURCE, ['action' => 'create'] + $this->payload($request)); if (($result['ok'] ?? false) !== true) { return redirect()->route('site.ads.c')->withInput()->with('error', __('site.page_ads_api_error')); } UserLog::write('ads.create', [ 'entity' => 'ads', 'entity_id' => $result['id_row'] ?? null, 'description' => $request->input('name'), ]); $this->streamLog('Criou uma publicidade no site - ID: ' . ($result['id_row'] ?? '')); return redirect()->route('site.ads.r')->with('success', __('site.page_ads_controller_new_suc')); } public function edit($id) { if (Auth::check() === false) { Auth::logout(); return redirect()->route('login'); } if (! $id) { return redirect()->route('site.ads.r'); } $result = $this->api->get(self::RESOURCE, ['action' => 'get', 'id_row' => (int) $id]); if (($result['ok'] ?? false) !== true) { return redirect()->route('site.ads.r')->with('error', __('site.page_ads_api_error')); } $ad = (object) $result['ad']; // vem escapado do banco, e o campo do formulario precisa do original $ad->code = htmlspecialchars_decode((string) $ad->code); return view('site.adsEdit', $this->shared() + ['adsData' => $ad]); } public function update(Request $request, $id) { if (Auth::check() === false) { Auth::logout(); return redirect()->route('login'); } if (! $id) { return redirect()->route('site.ads.r'); } // na edicao a imagem e opcional: em branco significa manter a atual $fail = $this->validateInput($request, route('site.ads.u', $id), false); if ($fail) { return $fail; } $result = $this->api->post(self::RESOURCE, ['action' => 'update', 'id_row' => (int) $id] + $this->payload($request)); if (($result['ok'] ?? false) !== true) { return redirect()->route('site.ads.u', $id)->withInput()->with('error', __('site.page_ads_api_error')); } $this->removeFiles($result['files'] ?? []); UserLog::write('ads.update', [ 'entity' => 'ads', 'entity_id' => (int) $id, 'description' => $request->input('name'), ]); $this->streamLog('Alterou uma publicidade no site - ID: ' . $id); return redirect()->route('site.ads.r')->with('success', __('site.page_ads_controller_edit_suc')); } public function destroy($id) { if (Auth::check() === false) { Auth::logout(); return redirect()->route('login'); } $result = $this->api->post(self::RESOURCE, ['action' => 'delete', 'ids' => [(int) $id]]); if (($result['ok'] ?? false) !== true) { return redirect()->route('site.ads.r')->with('error', __('site.page_ads_api_error')); } $this->removeFiles($result['files'] ?? []); UserLog::write('ads.delete', ['entity' => 'ads', 'entity_id' => (int) $id]); $this->streamLog('Removeu uma publicidade no site - ID: ' . $id); return redirect()->route('site.ads.r')->with('success', __('site.page_ads_controller_del_suc')); } /** Zera exibições e cliques, para recomeçar a contagem de um período. */ public function reset(Request $request) { if (Auth::check() === false) { Auth::logout(); return redirect()->route('login'); } $ids = array_values(array_filter(array_map('intval', (array) $request->ids))); if (! $ids) { return response()->json(['success' => false], 422); } $result = $this->api->post(self::RESOURCE, ['action' => 'reset', 'ids' => $ids]); if (($result['ok'] ?? false) !== true) { return response()->json(['success' => false], 422); } UserLog::write('ads.reset', ['entity' => 'ads', 'description' => count($ids) . ' anuncios']); $request->session()->flash('success', __('site.page_ads_reset_done')); return response()->json(['success' => true]); } /** * Anuncios do aplicativo Android, pela porta de streaming. * * Rota publica /android-contents/ads/{port}, consumida pelo app. Nao tem * relacao com a tela /site/ads: aqui sao os anuncios `type = '2'`, que * passam pela moderacao do manager e por isso exigem `approved = '1'`. * * Continua no MySQL direto e preso ao servidor 1, como esta em producao. * Nao entrou na migracao para a brasapi junto com o resto do controller * porque e outro consumidor e mexer nele exige testar com o app. */ public function appAndroid($port = null) { $siteData = DB::connection('mysql_site_1')->table('sites')->where('stream_port', '=', $port)->get()->first(); $appSettings = DB::connection('mysql_site_1')->table('apps')->where('port', '=', $port)->get()->first(); $allAds = null; if($siteData){ $allAds = DB::connection('mysql_site_1')->table('ads')->where('site_id','=',$siteData->id)->where('type','=','2') ->where('approved','=','1')->get(); } return view('android-ads', ['total' => (isset($allAds) ? $allAds->count() : 0), 'allAds' => (isset($allAds) ? $allAds : ''), 'app' => ($appSettings ? $appSettings : null), 'port' => $port]); } /** Upload com a pasta fixa, para quem envia nao escolher onde grava. */ public function uploadAsset(Request $request) { if (Auth::check() === false) { Auth::logout(); return redirect()->route('login'); } $request->validate(['file.*' => 'required|image|mimes:jpg,png,jpeg,bmp,webp,gif|max:102400']); $siteData = SiteContext::current(); $server = DB::table('sites_servers')->where('id', '=', session('serverID'))->value('name'); $parts = [ ['name' => 'site_id', 'contents' => session('siteID')], ['name' => 'fld', 'contents' => 'ads'], ['name' => 'md5', 'contents' => md5($siteData->name)], ]; foreach ((array) $request->file('file') as $file) { if (is_array($file)) { continue; } $parts[] = [ 'name' => 'file[]', 'contents' => fopen($file->getPathname(), 'r'), 'Mime-Type' => $file->getMimeType(), 'filename' => $file->getClientOriginalName(), ]; } try { $response = (new Client())->request('POST', 'https://' . $server . '/_apiBrasUpload/bras_brMultiple.php', ['multipart' => $parts] ); } catch (Throwable $e) { Log::warning('ads upload failed', ['site_id' => session('siteID'), 'error' => $e->getMessage()]); return response()->json(['success' => false], 502); } $files = json_decode($response->getBody()->getContents()); if (! is_array($files) || ! $files) { return response()->json(['success' => false], 502); } $name = ltrim((string) $files[0], '/'); $cut = strrpos($name, '/'); $name = $cut === false ? $name : substr($name, $cut + 1); return response()->json([ 'success' => true, 'file' => $name, 'url' => 'https://' . $server . '/contents/' . session('siteID') . '/ads/' . $name, ]); } /** @return array */ private function payload(Request $request): array { return [ 'type' => $request->input('type') === '1' ? '1' : '0', 'name' => trim(strip_tags((string) $request->input('name'))), 'url' => (string) $request->input('url'), 'target' => $request->input('target') ? '1' : '0', 'img' => (string) $request->input('feature_image'), 'code' => (string) $request->input('code'), ]; } /** * A URL pode ser um caminho interno como /promocao/22, entao nao passa * por FILTER_VALIDATE_URL, que so aceita endereco absoluto e recusava * link interno valido. */ private function validateInput(Request $request, string $back, bool $imageRequired) { if (! trim((string) $request->input('name'))) { return redirect()->to($back)->withInput()->with('error_name', __('site.page_links_controller_error_name')); } if ($request->input('type') === '1') { if (! trim((string) $request->input('code'))) { return redirect()->to($back)->withInput()->with('error_code', __('site.site_input_req')); } return null; } $url = trim((string) $request->input('url')); if ($url !== '' && ! str_starts_with($url, '/') && ! filter_var($url, FILTER_VALIDATE_URL)) { return redirect()->to($back)->withInput() ->with('error_url', __('site.page_links_controller_error_url_2')); } if ($imageRequired && ! $request->input('feature_image')) { return redirect()->to($back)->withInput()->with('error_img', __('site.site_input_req')); } return null; } private function removeFiles(array $files): void { if (! $files) { return; } $server = DB::table('sites_servers')->where('id', '=', session('serverID'))->value('name'); SiteMedia::removeMany($server, 'ads', $files); } private function paginate(array $result, int $page): LengthAwarePaginator { $items = array_map(fn ($row) => (object) $row, (array) ($result['items'] ?? [])); return new LengthAwarePaginator( $items, (int) ($result['total'] ?? 0), (int) ($result['limit'] ?? 20), $page, ['path' => request()->url(), 'pageName' => 'pg', 'query' => request()->query()] ); } private function streamLog(string $text): void { $port = SiteContext::current()->stream_port ?? null; if ($port) { setLog($port, $text, request()->ip()); } } private function shared(): array { return [ 'userLogged' => User::where('id', '=', Auth::id())->first(), 'allNotifications' => DB::table('notifications')->orderBy('id', 'desc')->limit(3)->get(), 'topHref' => 'site', 'siteData' => SiteContext::current(), 'serverData' => DB::table('sites_servers')->where('id', '=', session('serverID'))->first(), ]; } }