Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 163 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
| CustomerController | |
0.00% |
0 / 163 |
|
0.00% |
0 / 5 |
992 | |
0.00% |
0 / 1 |
| index | |
0.00% |
0 / 59 |
|
0.00% |
0 / 1 |
182 | |||
| show | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
| delete | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
72 | |||
| export | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
| exportSubmit | |
0.00% |
0 / 67 |
|
0.00% |
0 / 1 |
72 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace App\Http\Controllers; |
| 4 | |
| 5 | use App\Enums\CustomerExportType; |
| 6 | use App\Http\Requests\CustomerExport; |
| 7 | use App\Models\Brand; |
| 8 | use App\Models\Customer; |
| 9 | use App\Models\CustomerInfo; |
| 10 | use App\Models\CustomerLogin; |
| 11 | use App\Models\SingleOptin; |
| 12 | use Illuminate\Database\Eloquent\Builder; |
| 13 | use Illuminate\Database\Query\JoinClause; |
| 14 | use Illuminate\Http\Request; |
| 15 | use Illuminate\Support\Carbon; |
| 16 | use Illuminate\Support\Facades\DB; |
| 17 | use Illuminate\Support\Facades\Schema; |
| 18 | use Illuminate\Support\Facades\Session; |
| 19 | use Illuminate\Support\Str; |
| 20 | use League\Csv\Writer; |
| 21 | use Symfony\Component\HttpFoundation\StreamedResponse; |
| 22 | |
| 23 | class CustomerController extends Controller |
| 24 | { |
| 25 | /** |
| 26 | * View all customers. |
| 27 | * |
| 28 | * @param \Illuminate\Http\Request $request |
| 29 | * @return \Illuminate\Contracts\View\View |
| 30 | */ |
| 31 | public function index(Request $request): \Illuminate\Contracts\View\View |
| 32 | { |
| 33 | $modelTable = (new Customer())->getTable(); |
| 34 | $modelClass = Customer::class; |
| 35 | $brands = Brand::query()->select('id', 'name')->get(); |
| 36 | $columns = ['customer_infos.lastname', 'customer_infos.firstname']; |
| 37 | |
| 38 | // * Jointure pour pouvoir trier les colonnes. |
| 39 | /** @var \Illuminate\Database\Eloquent\Builder */ |
| 40 | $query = CustomerInfo::query()->join('customer_logins', function (JoinClause $join) { |
| 41 | $join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id') |
| 42 | ->on('customer_logins.customer_id', '=', 'customer_infos.customer_id'); |
| 43 | })->join('customers', function (JoinClause $join) { |
| 44 | $join->on('customers.id', '=', 'customer_infos.customer_id'); |
| 45 | })->join('brands', function (JoinClause $join) { |
| 46 | $join->on('brands.id', '=', 'customer_infos.brand_id'); |
| 47 | }); |
| 48 | |
| 49 | if ($request->rst) { |
| 50 | Session::remove("crud.$modelTable.brand"); |
| 51 | Session::remove("crud.$modelTable.search"); |
| 52 | Session::remove("crud.$modelTable.sort_way"); |
| 53 | Session::remove("crud.$modelTable.sort_col"); |
| 54 | } |
| 55 | |
| 56 | if (request()->brand or Session::has("crud.$modelTable.brand")) { |
| 57 | $brand = $request->brand ?? Session::get("crud.$modelTable.brand"); |
| 58 | Session::put("crud.$modelTable.brand", $brand); |
| 59 | $query = $query->where('customer_infos.brand_id', $brand); |
| 60 | } |
| 61 | |
| 62 | // * Search on crud Label column. |
| 63 | if ($request->search or Session::has("crud.$modelTable.search")) { |
| 64 | $search = $request->search ?? Session::get("crud.$modelTable.search"); |
| 65 | Session::put("crud.$modelTable.search", $search); |
| 66 | $query->where(function ($q) use ($columns, $search) { |
| 67 | foreach ($columns as $column) { |
| 68 | $q = $q->orWhere($column, 'LIKE', "%$search%"); |
| 69 | } |
| 70 | $q = $q->orWhere('customers.email', 'LIKE', "%$search%"); |
| 71 | $q = $q->orWhere('customer_logins.last_connection', 'LIKE', "%$search%"); |
| 72 | }); |
| 73 | } |
| 74 | |
| 75 | // * Sort columns. |
| 76 | if ($request->sort_col) { |
| 77 | Session::put("crud.$modelTable.sort_col", $request->sort_col); |
| 78 | } |
| 79 | if ($request->sort_way) { |
| 80 | Session::put("crud.$modelTable.sort_way", $request->sort_way); |
| 81 | } |
| 82 | |
| 83 | if ( |
| 84 | Session::has("crud.$modelTable.sort_col") and |
| 85 | Session::has("crud.$modelTable.sort_way") and |
| 86 | (Schema::hasColumn($modelTable, Session::get("crud.$modelTable.sort_col")) or |
| 87 | in_array(Session::get("crud.$modelTable.sort_col"), [ |
| 88 | 'brands.name', |
| 89 | 'customers.email', |
| 90 | 'customer_infos.firstname', |
| 91 | 'customer_infos.lastname', |
| 92 | 'customer_infos.opt_in', |
| 93 | 'customer_logins.last_connection', |
| 94 | 'customer_logins.status' |
| 95 | ])) |
| 96 | ) { |
| 97 | $query = $query->orderBy( |
| 98 | Session::get("crud.$modelTable.sort_col"), |
| 99 | Session::get("crud.$modelTable.sort_way") |
| 100 | ); |
| 101 | } |
| 102 | |
| 103 | $searchFields = trans('Email, Prénom, Nom'); |
| 104 | |
| 105 | /** @var \Illuminate\Pagination\AbstractPaginator */ |
| 106 | $customerModels = $query->paginate(50); |
| 107 | |
| 108 | return view('customers.index', compact( |
| 109 | 'modelClass', |
| 110 | 'modelTable', |
| 111 | 'customerModels', |
| 112 | 'brands', |
| 113 | 'searchFields' |
| 114 | )); |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * Show a specific custmer. |
| 119 | * |
| 120 | * @param \App\Models\Customer $customer |
| 121 | * @param \App\Models\Brand $brand |
| 122 | * @return \Illuminate\Contracts\View\View |
| 123 | */ |
| 124 | public function show(Customer $customer, Brand $brand): \Illuminate\Contracts\View\View |
| 125 | { |
| 126 | $customerInfos = CustomerInfo::query()->where('customer_id', $customer->id) |
| 127 | ->where('brand_id', $brand->id)->firstOrFail(); |
| 128 | $customerLogin = CustomerLogin::query()->where('customer_id', $customer->id) |
| 129 | ->where('brand_id', $brand->id)->first(); |
| 130 | |
| 131 | return view('customers.show', compact( |
| 132 | 'customerInfos', |
| 133 | 'customerLogin' |
| 134 | )); |
| 135 | } |
| 136 | |
| 137 | /** |
| 138 | * Delete a specific customer info. |
| 139 | * |
| 140 | * @param \App\Models\Customer $customer |
| 141 | * @param \App\Models\Brand $brand |
| 142 | * @return \Illuminate\Http\RedirectResponse |
| 143 | */ |
| 144 | public function delete(Customer $customer, Brand $brand): \Illuminate\Http\RedirectResponse |
| 145 | { |
| 146 | try { |
| 147 | // TODO: Gestion avec mesBR. |
| 148 | /** @var \App\Models\CustomerInfo */ |
| 149 | $customerInfo = $customer->info($brand, Customer::FIRST_OR_FAIL); |
| 150 | /** @var \App\Models\CustomerLogin */ |
| 151 | $customerLogin = $customer->login($brand, Customer::FIRST_OR_FAIL); |
| 152 | |
| 153 | DB::transaction(function () use ( |
| 154 | $brand, |
| 155 | $customer, |
| 156 | $customerLogin, |
| 157 | $customerInfo |
| 158 | ) { |
| 159 | if ( |
| 160 | $customerInfo->delete() === false || |
| 161 | $customerLogin->delete() === false |
| 162 | ) { |
| 163 | throw new \ErrorException('Api failed to delete customer.'); |
| 164 | }; |
| 165 | |
| 166 | $customer->optInHistory()->whereHas('brand', function ($q) use ($brand) { |
| 167 | $q->where('id', $brand->id); |
| 168 | })->delete(); |
| 169 | |
| 170 | $logins = $customer->logins()->count(); |
| 171 | $infos = $customer->infos()->count(); |
| 172 | $history = $customer->optInHistory()->count(); |
| 173 | |
| 174 | // If customer is not associated with any brand then delete. |
| 175 | if (!$logins and !$infos and !$history) { |
| 176 | if ($customer->delete() === false) { |
| 177 | throw new \ErrorException('Api failed to update customer.'); |
| 178 | } |
| 179 | } |
| 180 | }); |
| 181 | } catch (\ErrorException $e) { |
| 182 | return redirect()->route('customers.index') |
| 183 | ->with('error', trans('Le compte n\'a pas pu être supprimé.')); |
| 184 | } //end try |
| 185 | |
| 186 | return redirect()->route('customers.index')->with('warning', trans('Compte supprimé.')); |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Show export the customers form. |
| 191 | * |
| 192 | * @return \Illuminate\Contracts\View\View |
| 193 | */ |
| 194 | public function export(): \Illuminate\Contracts\View\View |
| 195 | { |
| 196 | $brands = Brand::all(); |
| 197 | return view('customers.export', compact( |
| 198 | 'brands' |
| 199 | )); |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Exports customers following the requested export type. |
| 204 | * |
| 205 | * @param \App\Http\Requests\CustomerExport $request |
| 206 | * @return \Symfony\Component\HttpFoundation\StreamedResponse |
| 207 | * @throws \Exception If the request export type in not handled. |
| 208 | */ |
| 209 | public function exportSubmit(CustomerExport $request): \Symfony\Component\HttpFoundation\StreamedResponse |
| 210 | { |
| 211 | $pagination = 2000; |
| 212 | $header = '"lastname";"firstname";"email";"hashcode"'; |
| 213 | |
| 214 | $w = Writer::createFromString($header . PHP_EOL); |
| 215 | $w->setDelimiter(';'); |
| 216 | $w->setEnclosure('"'); |
| 217 | $w->setOutputBOM(Writer::BOM_UTF8); |
| 218 | |
| 219 | $exportType = CustomerExportType::make(\intval($request->export_type)); |
| 220 | |
| 221 | // * We send the CSV using Symfony StreamedResponse. |
| 222 | $response = new StreamedResponse(); |
| 223 | $response->headers->set('Content-Encoding', 'none'); |
| 224 | $response->headers->set('Content-Type', 'text/csv; charset=UTF-8'); |
| 225 | $response->headers->set('Description', 'File Transfer'); |
| 226 | $response->headers->set( |
| 227 | 'Content-Disposition', |
| 228 | sprintf( |
| 229 | 'attachment; filename="Export pour newsletter %s %s.csv"', |
| 230 | Carbon::now()->isoFormat('DD-MM-YYYY_HH-mm'), |
| 231 | Str::slug($exportType->label()) |
| 232 | ) |
| 233 | ); |
| 234 | |
| 235 | switch ($exportType) { |
| 236 | case CustomerExportType::both: |
| 237 | $continue = true; |
| 238 | // ! Continue |
| 239 | case CustomerExportType::customer: |
| 240 | $page = 1; |
| 241 | /** @var \Illuminate\Database\Eloquent\Builder $req */ |
| 242 | $req = CustomerInfo::query()->where('brand_id', $request->brand) |
| 243 | ->where('opt_in', true); |
| 244 | $total = $req->count(); |
| 245 | $pages = \ceil($total / $pagination); |
| 246 | do { |
| 247 | /** @var \Illuminate\Pagination\LengthAwarePaginator $items */ |
| 248 | $items = $req->with('customer', function ($q) { |
| 249 | $q->select(['id', 'email']); |
| 250 | })->paginate( |
| 251 | $pagination, |
| 252 | ['lastname', 'firstname', 'hashcode', 'customer_id'], |
| 253 | 'page', |
| 254 | $page |
| 255 | ); |
| 256 | $items->map(function (CustomerInfo $customerInfo) use ($w) { |
| 257 | $w->insertOne([ |
| 258 | 'lastname' => $customerInfo->lastname, |
| 259 | 'firstname' => $customerInfo->firstname, |
| 260 | 'email' => $customerInfo->customer->email, |
| 261 | 'hashcode' => $customerInfo->hashcode |
| 262 | ]); |
| 263 | }); |
| 264 | } while ($page++ <= $pages); |
| 265 | if (!isset($continue)) { |
| 266 | break; |
| 267 | } |
| 268 | // ! Continue |
| 269 | case CustomerExportType::singleOptin: |
| 270 | SingleOptin::query()->whereHas('brand', function (Builder $q) use ($request) { |
| 271 | $q->where('id', $request->brand); |
| 272 | })->get()->map(function (SingleOptin $singleOptin) use ($w) { |
| 273 | $w->insertOne([ |
| 274 | 'lastname' => '', |
| 275 | 'firstname' => '', |
| 276 | 'email' => $singleOptin->email, |
| 277 | 'hashcode' => $singleOptin->hashcode |
| 278 | ]); |
| 279 | }); |
| 280 | break; |
| 281 | default: |
| 282 | throw new \Exception(sprintf('Unhandled CustomerExportType %s', $exportType->label())); |
| 283 | } //end switch |
| 284 | |
| 285 | /* |
| 286 | We create a callable to output the CSV in chunk |
| 287 | with Symfony StreamResponse you can flush the body content if necessary |
| 288 | see Symfony documentation for more information |
| 289 | */ |
| 290 | |
| 291 | // * The flush value should depend on your CSV size. |
| 292 | $flush_threshold = 1000; |
| 293 | |
| 294 | $response->setCallback(function () use ($w, $flush_threshold) { |
| 295 | foreach ($w->chunk(1024) as $offset => $chunk) { |
| 296 | echo $chunk; |
| 297 | if ($offset % $flush_threshold === 0) { |
| 298 | flush(); |
| 299 | } |
| 300 | } |
| 301 | }); |
| 302 | return $response; |
| 303 | } |
| 304 | } |