427 lines
13 KiB
PHP
427 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Enums\BalancedMenuType;
|
|
use App\Enums\EleveurOcbStatus;
|
|
use App\Models\Articles;
|
|
use App\Models\MeatTypes;
|
|
use App\Models\Moments;
|
|
use App\Models\Products;
|
|
use App\Models\Ranges;
|
|
use Exception;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Routing\Route as RoutingRoute;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Kwaadpepper\Enum\BaseEnum;
|
|
use ReflectionClass;
|
|
use Spatie\Sitemap\Sitemap;
|
|
use Spatie\Sitemap\Tags\Url;
|
|
|
|
class RefreshSiteMap implements ShouldQueue
|
|
{
|
|
use Dispatchable;
|
|
use InteractsWithQueue;
|
|
use Queueable;
|
|
use SerializesModels;
|
|
|
|
/**
|
|
* Uri that start with something in
|
|
* this table will be ignored
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $ignoreRoutes = [
|
|
'/login',
|
|
'/bo',
|
|
'/activer',
|
|
'/deconnexion',
|
|
'/reinitialiser-mot-de-passe',
|
|
'/le-club-des-gaulois/la-boutique',
|
|
'/password/confirm',
|
|
'/produit/from-menus',
|
|
'/cote-cuisine/sante-et-nutrition/menu-equilibre-j',
|
|
'/la-marque/tracabilite/search',
|
|
'/frame',
|
|
'/map',
|
|
'/_debugbar' // Hack ignore laravel debug bar
|
|
];
|
|
|
|
/**
|
|
* Undocumented variable
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $routesBinder = [
|
|
'product.show' => [
|
|
'product' => [Products::class, 'slug']
|
|
],
|
|
'products.range' => [
|
|
'slugRange' => [Ranges::class, 'slug']
|
|
],
|
|
'products.consumption-times' => [
|
|
'slugConsumptionTime' => [Ranges::class, 'slug']
|
|
],
|
|
'products.meat-type' => [
|
|
'slugMeatType' => [MeatTypes::class, 'slug']
|
|
],
|
|
'kitchen-side.health-nutrition.balanced-menu.menu' => [
|
|
'type' => [BalancedMenuType::class, 'type']
|
|
],
|
|
'club.infos.child' => [
|
|
'publishedArticle' => [Articles::class, 'slug']
|
|
],
|
|
];
|
|
|
|
/**
|
|
* Execute the job.
|
|
*
|
|
*! Ici c'est du sale !!
|
|
* TODO: mettre tout ça dans un package pour le rendre plus générique
|
|
*
|
|
* @return void
|
|
*/
|
|
public function handle()
|
|
{
|
|
$this->assertRouteBinderIsCorrect();
|
|
|
|
$siteMap = [];
|
|
|
|
$routeCollection = Route::getRoutes();
|
|
|
|
/** @var \Illuminate\Routing\Route $route */
|
|
foreach ($routeCollection as $route) {
|
|
if ($this->routeShouldBeIgnored($route)) {
|
|
continue;
|
|
}
|
|
|
|
if (!$this->routeHandlesGetMethod($route)) {
|
|
continue;
|
|
}
|
|
|
|
/** @var array to be populated with models from DB to generate a route */
|
|
$rParams = [];
|
|
|
|
/** @var \array [lluminate\Routing\Route::signatureParameters] parameters signature from the controller */
|
|
$params = $route->signatureParameters();
|
|
/** @var \array [Illuminate\Routing\Route::parameterNames] parameters name from the registered route */
|
|
$pNames = $route->parameterNames();
|
|
|
|
$hasModels = false;
|
|
foreach ($params as $param) {
|
|
|
|
/** @var ReflectionClass|null $name */
|
|
$name = $param->getType() && !$param->getType()->isBuiltin()
|
|
? new ReflectionClass($param->getType()->getName())
|
|
: null;
|
|
|
|
/** @var string $pName $parameter name on route uri */
|
|
$pName = '';
|
|
|
|
// If the route has parameters and the controller accepts it
|
|
if (count($pNames) and \array_key_exists($param->getPosition(), $pNames)) {
|
|
// Get the controller parameter name for the route
|
|
$pName = $pNames[$param->getPosition()];
|
|
}
|
|
|
|
if (config('app.debug')) {
|
|
dump(sprintf(
|
|
'Route nom : %s, Param [type: %s, nom: %s, binder: %s]',
|
|
$route->getName(),
|
|
$name ? $name->getName() : '',
|
|
$pName,
|
|
$this->hasRouteBinderParam($route, $pName) ? 'true' : 'false'
|
|
));
|
|
}
|
|
|
|
// If We have a custom binder for this route
|
|
if ($this->hasRouteBinderParam($route, $pName)) {
|
|
$hasModels = true;
|
|
$this->processWithRouteBinderParam($rParams, $route, $pName);
|
|
continue;
|
|
}
|
|
|
|
// If This route takes a model as param
|
|
if ($name and $name->isSubclassOf(Model::class)) {
|
|
$hasModels = true;
|
|
/** @var \string */
|
|
$modelClassName = $name->getName();
|
|
$this->setParamsForModel($rParams, $modelClassName, $pName);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If the route has no dynamic models as parameters
|
|
if (!count($params) or !$hasModels) {
|
|
$siteMap[] = [
|
|
route($route->getName()),
|
|
Url::CHANGE_FREQUENCY_MONTHLY,
|
|
0.1
|
|
];
|
|
} else { // If the route has dynamic models as parameters
|
|
$this->genRoute($route, $siteMap, $rParams);
|
|
}
|
|
}
|
|
|
|
$sm = Sitemap::create();
|
|
foreach ($siteMap as $smRules) {
|
|
$sm->add(Url::create($smRules[0])
|
|
->setLastModificationDate(Carbon::yesterday())
|
|
->setChangeFrequency($smRules[1])
|
|
->setPriority($smRules[2]));
|
|
}
|
|
$sm->writeToFile(\public_path('sitemap.xml'));
|
|
}
|
|
|
|
/**
|
|
* Process route with binderParam
|
|
*
|
|
* @param RoutingRoute $route
|
|
* @param string $pName
|
|
* @return void
|
|
*/
|
|
private function processWithRouteBinderParam(array &$rParams, RoutingRoute $route, string $pName)
|
|
{
|
|
$o = $this->getRouteBinderParam($route, $pName);
|
|
|
|
/** @var \string */
|
|
$modelClassName = $o[0];
|
|
/** @var \string */
|
|
$routeParamName = $o[1];
|
|
$rfLCls = (new ReflectionClass($modelClassName));
|
|
|
|
switch (true) {
|
|
case $rfLCls->isSubclassOf(Model::class):
|
|
$this->setParamsForModel($rParams, $modelClassName, $pName, $routeParamName);
|
|
break;
|
|
case $rfLCls->isSubclassOf(BaseEnum::class):
|
|
$this->setParamForEnum($rParams, $rfLCls->getName(), $pName);
|
|
break;
|
|
default:
|
|
throw new Exception(sprintf('Unhandled class type %s', $rfLCls->getName()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set route param Array for Enums
|
|
*
|
|
* @param array $rParams
|
|
* @param string $enumClassName
|
|
* @param string $pName
|
|
* @return void
|
|
*/
|
|
private function setParamForEnum(array &$rParams, string $enumClassName, string $pName)
|
|
{
|
|
foreach (forward_static_call(sprintf('%s::toArray', $enumClassName)) as $enum) {
|
|
if (!array_key_exists($pName, $rParams)) {
|
|
$rParams[$pName] = [];
|
|
}
|
|
$rParams[$pName][] = $enum->value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set route param Array for Models
|
|
*
|
|
* @param array $rParams
|
|
* @param string $modelClassName
|
|
* @param string $pName
|
|
* @param string $routeParamName
|
|
* @return void
|
|
*/
|
|
private function setParamsForModel(
|
|
array &$rParams,
|
|
string $modelClassName,
|
|
string $pName,
|
|
string $routeParamName = null
|
|
) {
|
|
$i = 0;
|
|
$limit = 10;
|
|
$modelTable = (new ReflectionClass($modelClassName))->newInstanceWithoutConstructor()->getTable();
|
|
$q = $modelClassName::query();
|
|
$q = Schema::hasColumn($modelTable, 'status') ? $q->where('status', true) : $q;
|
|
$q = Schema::hasColumn($modelTable, 'ocbStatus') ? $q->where('ocbStatus', EleveurOcbStatus::yes()->value) : $q;
|
|
$q = Schema::hasColumn($modelTable, 'star_moment') ? $q->where('star_moment', true) : $q;
|
|
do {
|
|
$results = $q->offset($i * $limit)->limit($limit)->get();
|
|
$length = count($results);
|
|
$results->map(function (Model $model) use (&$rParams, $pName, $routeParamName) {
|
|
if (!array_key_exists($pName, $rParams)) {
|
|
$rParams[$pName] = [];
|
|
}
|
|
if ($routeParamName) {
|
|
$rParams[$pName][] = $model[$routeParamName];
|
|
} else {
|
|
$rParams[$pName][] = $model;
|
|
}
|
|
});
|
|
$i++;
|
|
} while ($length);
|
|
}
|
|
|
|
/**
|
|
* Recursively generate routes for all params
|
|
*
|
|
* @param RoutingRoute $route
|
|
* @param array $siteMap
|
|
* @param array $params
|
|
* @return void
|
|
*/
|
|
private function genRoute(RoutingRoute $route, array &$siteMap, array $params)
|
|
{
|
|
$filtered = false;
|
|
foreach ($params as $pName => $param) {
|
|
if (is_array($param)) {
|
|
collect($param)->map(function ($p) use ($route, &$siteMap, $params, $pName) {
|
|
$this->genRoute($route, $siteMap, array_merge($params, [$pName => $p]));
|
|
});
|
|
return;
|
|
}
|
|
$filtered = array_key_last($params) === $pName ? true : false;
|
|
}
|
|
if ($filtered) {
|
|
$siteMap[] = [
|
|
route($route->getName(), $params),
|
|
Url::CHANGE_FREQUENCY_DAILY,
|
|
0.1
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if $route exists in routesBinder
|
|
*
|
|
* @param RoutingRoute $route
|
|
* @return boolean
|
|
*/
|
|
private function hasRouteBinder(RoutingRoute $route): bool
|
|
{
|
|
return array_key_exists($route->getName(), $this->routesBinder);
|
|
}
|
|
|
|
/**
|
|
* Check if $route exists in routesBinder and has $param
|
|
*
|
|
* @param RoutingRoute $route
|
|
* @param string $param
|
|
* @return boolean
|
|
*/
|
|
private function hasRouteBinderParam(RoutingRoute $route, string $param): bool
|
|
{
|
|
if ($this->hasRouteBinder($route)) {
|
|
return array_key_exists($param, $this->getRouteBinder($route));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the routeBinder
|
|
*
|
|
* @param RoutingRoute $route
|
|
* @return array
|
|
*/
|
|
private function getRouteBinder(RoutingRoute $route): array
|
|
{
|
|
return $this->routesBinder[$route->getName()];
|
|
}
|
|
|
|
/**
|
|
* Get the routeBinder param
|
|
*
|
|
* @param RoutingRoute $route
|
|
* @param string $param
|
|
* @return array
|
|
*/
|
|
private function getRouteBinderParam(RoutingRoute $route, string $param): array
|
|
{
|
|
return $this->routesBinder[$route->getName()][$param];
|
|
}
|
|
|
|
/**
|
|
* Assert that routeBinder is correct
|
|
*
|
|
* @throws Exception
|
|
* @return void
|
|
*/
|
|
private function assertRouteBinderIsCorrect()
|
|
{
|
|
/** @var \Illuminate\Routing\RouteCollection */
|
|
$routeCollection = Route::getRoutes();
|
|
foreach ($this->routesBinder as $routeName => $params) {
|
|
if (!$routeCollection->hasNamedRoute($routeName)) {
|
|
throw new Exception(sprintf('Route %s does not exists', $routeName));
|
|
}
|
|
foreach ($params as $pName => $p) {
|
|
if (count($p) != 2) {
|
|
throw new Exception(sprintf(
|
|
'Route %s parameter %s does not have Model classname and parameter name',
|
|
$routeName,
|
|
$pName
|
|
));
|
|
}
|
|
$rfCls = (new ReflectionClass($p[0]))->newInstanceWithoutConstructor();
|
|
if (is_subclass_of($rfCls, BaseEnum::class)) {
|
|
return;
|
|
}
|
|
if (!is_subclass_of($rfCls, Model::class)) {
|
|
throw new Exception(sprintf(
|
|
'Route "%s" parameter "%s" first element must be a child of "%s"',
|
|
$routeName,
|
|
$pName,
|
|
Model::class
|
|
));
|
|
}
|
|
if (!Schema::hasColumn($rfCls->getTable(), $p[1])) {
|
|
throw new Exception(sprintf(
|
|
'"%s" parameter "%s" second element "%s" must refer to and existing database column of "%s"',
|
|
$routeName,
|
|
$pName,
|
|
$p[1],
|
|
$p[0]
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the route handles the HTTP GET method
|
|
*
|
|
* @param RoutingRoute $route
|
|
* @return boolean
|
|
*/
|
|
private function routeHandlesGetMethod(RoutingRoute $route): bool
|
|
{
|
|
return in_array('GET', $route->methods());
|
|
}
|
|
|
|
/**
|
|
* Check if the actual route uri start with
|
|
* a partial uri from the table
|
|
* Check also if it uses middleware auth
|
|
*
|
|
* @param RoutingRoute $route
|
|
* @return boolean
|
|
*/
|
|
private function routeShouldBeIgnored(RoutingRoute $route): bool
|
|
{
|
|
foreach ($this->ignoreRoutes as $iRoute) {
|
|
if (strpos($route->uri(), ltrim($iRoute, '/')) === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
foreach ($route->middleware() as $middleware) {
|
|
if (strpos($middleware, 'auth') === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|