diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php index b78ba7513f..03e1a26e28 100755 --- a/app/Http/Controllers/LocationsController.php +++ b/app/Http/Controllers/LocationsController.php @@ -67,7 +67,6 @@ class LocationsController extends Controller { $this->authorize('create', Location::class); $location = new Location(); - $location->id = null; // This is required to make Laravels different validation work, it errors if the parameter doesn't exist (maybe a bug)? $location->name = $request->input('name'); $location->parent_id = $request->input('parent_id', null); $location->currency = $request->input('currency', '$'); @@ -132,7 +131,6 @@ class LocationsController extends Controller return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist')); } - // Update the location data $location->name = $request->input('name'); $location->parent_id = $request->input('parent_id', null); diff --git a/app/Models/Location.php b/app/Models/Location.php index fd7522c241..dc8aa6a043 100755 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -28,7 +28,7 @@ class Location extends SnipeModel 'address2' => 'max:80|nullable', 'zip' => 'min:3|max:10|nullable', 'manager_id' => 'exists:users,id|nullable', - 'parent_id' => 'nullable|exists:locations,id|different:id', + 'parent_id' => 'non_circular:locations,id' ); protected $casts = [ diff --git a/app/Providers/ValidationServiceProvider.php b/app/Providers/ValidationServiceProvider.php index 2a675fa8d1..ea9c322765 100644 --- a/app/Providers/ValidationServiceProvider.php +++ b/app/Providers/ValidationServiceProvider.php @@ -58,6 +58,52 @@ class ValidationServiceProvider extends ServiceProvider }); + // Prevent circular references + // + // Example usage in Location model where parent_id references another Location: + // + // protected $rules = array( + // 'parent_id' => 'non_circular:locations,id,10' + // ); + // + Validator::extend('non_circular', function ($attribute, $value, $parameters, $validator) { + if (count($parameters) < 2) { + throw new \Exception('Required validator parameters: ,[,depth]'); + } + + // Parameters from the rule implementation ($pk will likely be 'id') + $table = array_get($parameters, 0); + $pk = array_get($parameters, 1); + $depth = (int) array_get($parameters, 2, 50); + + // Data from the edited model + $data = $validator->getData(); + + // The primary key value from the edited model + $data_pk = array_get($data, $pk); + $value_pk = $value; + + // If we’re editing an existing model and there is a parent value set… + while ($data_pk && $value_pk) { + + // It’s not valid for any parent id to be equal to the existing model’s id + if ($data_pk == $value_pk) { + return false; + } + + // Avoid accidental infinite loops + if (--$depth < 0) { + return false; + } + + // Get the next parent id + $value_pk = DB::table($table)->select($attribute)->where($pk, '=', $value_pk)->value($attribute); + } + + return true; + }); + + // Yo dawg. I heard you like validators. // This validates the custom validator regex in custom fields. // We're just checking that the regex won't throw an exception, not diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 23116776e3..4ece7d41c7 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -89,6 +89,7 @@ return array( 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', "unique_undeleted" => "The :attribute must be unique.", + "non_circular" => "The :attribute must not create a circular reference.", /* |--------------------------------------------------------------------------