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