Passed
Push — stage ( 918a10...513532 )
by Jon
09:29
created

ElementAttribute::createHistory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 13
nc 1
nop 1
1
<?php
2
3
namespace App\Models;
4
5
use App\Helpers\Macros\Traits\Languages;
6
use App\Models\Access\User\User;
7
use App\Models\Traits\BelongsToElement;
8
use App\Models\Traits\BelongsToProfileProperty;
9
use App\Models\Traits\BelongsToRelatedElement;
10
use App\Models\Traits\HasStatus;
11
use Carbon\Carbon;
12
use Culpa\Traits\Blameable;
13
use Culpa\Traits\CreatedBy;
14
use Culpa\Traits\DeletedBy;
15
use Culpa\Traits\UpdatedBy;
16
use Illuminate\Database\Eloquent\Model as Model;
17
use Illuminate\Database\Eloquent\Relations\HasMany;
18
use Illuminate\Database\Eloquent\Relations\HasOne;
19
use Illuminate\Database\Eloquent\SoftDeletes;
20
use InvalidArgumentException;
21
use Laracasts\Matryoshka\Cacheable;
22
use Venturecraft\Revisionable\RevisionableTrait;
23
use function config;
24
25
/**
26
 * App\Models\ElementAttribute
27
 *
28
 * @property int $id
29
 * @property \Carbon\Carbon|null $created_at
30
 * @property \Carbon\Carbon|null $updated_at
31
 * @property \Carbon\Carbon|null $deleted_at
32
 * @property int $created_user_id
33
 * @property int $updated_user_id
34
 * @property int $deleted_user_id
35
 * @property int $schema_property_id
36
 * @property int $profile_property_id
37
 * @property bool $is_schema_property
38
 * @property string $object
39
 * @property int $related_schema_property_id
40
 * @property string $language
41
 * @property int $status_id
42
 * @property int $last_import_id
43
 * @property bool $is_generated
44
 * @property int|null $created_by
45
 * @property int|null $updated_by
46
 * @property int|null $deleted_by
47
 * @property bool $review_reciprocal
48
 * @property int $reciprocal_property_element_id
49
 * @property-read \App\Models\Access\User\User|null $creator
50
 * @property-read \App\Models\Element $element
51
 * @property-read \App\Models\Access\User\User|null $eraser
52
 * @property mixed $languages
53
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ElementAttributeHistory[] $history
54
 * @property-read \App\Models\ElementAttribute $inverse
55
 * @property-read \App\Models\ProfileProperty $profile_property
56
 * @property-read \App\Models\ElementAttribute $reciprocal
57
 * @property-read \App\Models\Element|null $related_element
58
 * @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
59
 * @property-read \App\Models\Status|null $status
60
 * @property-read \App\Models\Access\User\User|null $updater
61
 * @method static bool|null forceDelete()
62
 * @method static \Illuminate\Database\Query\Builder|\App\Models\ElementAttribute onlyTrashed()
63
 * @method static bool|null restore()
64
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereCreatedAt($value)
65
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereCreatedBy($value)
66
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereCreatedUserId($value)
67
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereDeletedAt($value)
68
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereDeletedBy($value)
69
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereDeletedUserId($value)
70
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereId($value)
71
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereIsGenerated($value)
72
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereIsSchemaProperty($value)
73
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereLanguage($value)
74
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereLastImportId($value)
75
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereObject($value)
76
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereProfilePropertyId($value)
77
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereReciprocalPropertyElementId($value)
78
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereRelatedSchemaPropertyId($value)
79
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereReviewReciprocal($value)
80
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereSchemaPropertyId($value)
81
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereStatusId($value)
82
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereUpdatedAt($value)
83
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereUpdatedBy($value)
84
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ElementAttribute whereUpdatedUserId($value)
85
 * @method static \Illuminate\Database\Query\Builder|\App\Models\ElementAttribute withTrashed()
86
 * @method static \Illuminate\Database\Query\Builder|\App\Models\ElementAttribute withoutTrashed()
87
 * @mixin \Eloquent
88
 */
89
class ElementAttribute extends Model
90
{
91
    protected $table = self::TABLE;
92
    const TABLE      = 'reg_schema_property_element';
93
    use SoftDeletes, Blameable, CreatedBy, UpdatedBy, DeletedBy;
94
    use RevisionableTrait;
95
    use Cacheable;
96
    use Languages, HasStatus, BelongsToProfileProperty, BelongsToElement, BelongsToRelatedElement;
97
    protected $blameable = [
98
        'created' => 'created_user_id',
99
        'updated' => 'updated_user_id',
100
        'deleted' => 'deleted_user_id',
101
    ];
102
    protected $dates                    = ['deleted_at'];
103
    protected $touches                  = ['element'];
104
    protected $guarded                  = ['id'];
105
    protected $revisionCreationsEnabled = true;
106
    protected $casts                    = [
107
        'id'                             => 'integer',
108
        'created_user_id'                => 'integer',
109
        'updated_user_id'                => 'integer',
110
        'deleted_user_id'                => 'integer',
111
        'schema_property_id'             => 'integer',
112
        'profile_property_id'            => 'integer',
113
        'is_schema_property'             => 'bool',
114
        'object'                         => 'string',
115
        'related_schema_property_id'     => 'integer',
116
        'language'                       => 'string',
117
        'status_id'                      => 'integer',
118
        'last_import_id'                 => 'integer',
119
        'is_generated'                   => 'bool',
120
        'review_reciprocal'              => 'bool',
121
        'reciprocal_property_element_id' => 'integer',
122
    ];
123
124
    /**
125
     * Create the event listeners for the saving and saved events
126
     * This lets us save revisions whenever a save is made, no matter the
127
     * http method.
128
     */
129
    protected static function boot()
130
    {
131
        parent::boot();
132
133
        static::created(function (ElementAttribute $attribute) {
134
            //make sure we don't keep making new reciprocals
135
            if ($attribute->reciprocal_property_element_id) {
136
                return;
137
            }
138
            $attribute->createHistory('added');
139
            if ($attribute->createReciprocal()) {
140
                //Sometimes we just update this attribute instead of creating a reciprocal.
141
                //This deletes the extra new history that was been added when we do that
142
                $attribute->history()->latest()->first()->delete();
143
            }
144
        });
145
        static::updated(function (ElementAttribute $attribute) {
146
            if (\count($attribute->dirtyData) === 1) {
147
                if ($attribute->isDirty('deleted_user_id') || $attribute->isDirty('deleted_by')) {
148
                    return;
149
                }
150
                if ($attribute->isDirty('related_schema_property_id')) {
151
                    $attribute->updateHistory();
152
153
                    return;
154
                }
155
                if ($attribute->isDirty('object')) {
156
                    if ($attribute->reciprocal_property_element_id) {
157
                        $attribute->reciprocal->createHistory('deleted');
158
                        $attribute->reciprocal()->delete();
159
                    }
160
                    $attribute->createReciprocal();
161
162
                    return;
163
                }
164
            }
165
            $attribute->createHistory('updated');
166
        });
167
168
        static::deleted(function (ElementAttribute $attribute) {
169
            $attribute->createHistory('deleted');
170
            if ($attribute->reciprocal_property_element_id) {
171
                $reciprocal = self::find($attribute->reciprocal_property_element_id);
172
                if ($reciprocal) {
173
                    $reciprocal->delete();
174
                }
175
            }
176
        });
177
    }
178
179
    /*
180
    |--------------------------------------------------------------------------
181
    | FUNCTIONS
182
    |--------------------------------------------------------------------------
183
    */
184
185
    public function createHistory(string $action): ElementAttributeHistory
186
    {
187
        return ElementAttributeHistory::create([
188
            'action'                     => $action,
189
            'created_user_id'            => $this->updated_user_id,
190
            'schema_property_element_id' => $this->id,
191
            'schema_property_id'         => $this->schema_property_id,
192
            'schema_id'                  => $this->element->schema_id,
193
            'profile_property_id'        => $this->profile_property_id,
194
            'object'                     => $this->object,
195
            'language'                   => $this->getAttributeFromArray('language'),
196
            'status_id'                  => $this->status_id,
197
            //this should be set to null in the parent if there was no import
198
            'import_id'                  => $this->last_import_id,
199
            //things we don't know yet. Must come from post-processing
200
            'related_schema_property_id' => null,
201
            //should be added to the schema_property model and not here
202
            'change_note'                => null,
203
        ]);
204
    }
205
206
    public function updateHistory()
207
    {
208
        $history = $this->history()->where('import_id', $this->last_import_id)->first();
209
        if ($history) {
210
            $history->update(['related_schema_property_id' => $this->related_schema_property_id]);
211
        }
212
    }
213
214
    /**
215
     * @param $elementset_id
216
     *
217
     * @return Carbon
218
     * @throws InvalidArgumentException
219
     */
220
    public static function getLatestDateForElementSet($elementset_id): Carbon
221
    {
222
        $created_at = self::getLatest($elementset_id, 'created_at');
223
        $updated_at = self::getLatest($elementset_id, 'updated_at');
224
        $deleted_at = self::getLatest($elementset_id, 'deleted_at');
225
226
        $date = collect([$created_at, $updated_at, $deleted_at])->max();
227
        try {
228
            return Carbon::createFromFormat(config('app.timestamp_format'), $date);
229
        } catch (InvalidArgumentException $e) {
230
            return null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return null returns the type null which is incompatible with the type-hinted return Carbon\Carbon.
Loading history...
231
        }
232
    }
233
234
    /**
235
     * @param int    $elementset_id
236
     * @param string $field
237
     *
238
     * @return string
239
     */
240
    private static function getLatest($elementset_id, $field)
241
    {
242
        return \DB::table(self::TABLE)
243
            ->join(Element::TABLE, Element::TABLE . '.id', '=', self::TABLE . '.schema_property_id')
244
            ->select(self::TABLE . '.' . $field)
245
            ->where(Element::TABLE . '.schema_id', $elementset_id)
246
            ->max(self::TABLE . '.' . $field);
247
    }
248
249
    public function createReciprocal()
250
    {
251
        //is the object a uri?
252
        if (! filter_var($this->object, FILTER_VALIDATE_URL)) {
253
            return false;
254
        }
255
256
        //does the profile_property have a reciprocal?
257
        if (! $this->profile_property->is_reciprocal && $this->profile_property->inverse_profile_property_id === null) {
258
            return false;
259
        }
260
261
        $reciprocalProperty = $this->profile_property->inverse_profile_property_id ?? $this->profile_property->id;
262
263
        //does it reference a known URI in a vocabulary I 'own'? (tricky -- what if it hasn't been created yet?)
264
        $relatedElement = Element::whereUri($this->object)->first();
265
        if (! $relatedElement) {
266
            $this->update(['review_reciprocal' => true]);
267
268
            return true;
269
        }
270
        //todo: this should probably be findOrFail(), the error caught and added to error log
271
        $user = User::find($this->updated_user_id);
272
        if (!$user) {
273
            return false;
274
        }
275
276
        if ($user->cant('update', $relatedElement)) {
277
            return false;
278
        }
279
280
        //todo: gonna need a review reciprocals job
281
        //create the reciprocal
282
        $attribute = self::create([
283
            'related_schema_property_id'     => $this->element->id,
284
            'object'                         => $this->element->uri,
285
            'schema_property_id'             => $relatedElement->id,
286
            'status_id'                      => $this->status_id,
287
            'profile_property_id'            => $reciprocalProperty,
288
            'last_import_id'                 => $this->last_import_id,
289
            'is_generated'                   => true,
290
            'reciprocal_property_element_id' => $this->id,
291
            'language'                       => null,
292
            'created_user_id'                => $this->updated_user_id,
293
            'updated_user_id'                => $this->updated_user_id,
294
        ]);
295
        $this->update(['reciprocal_property_element_id' => $attribute->id]);
296
    }
297
298
    /*
299
    |--------------------------------------------------------------------------
300
    | RELATIONS
301
    |--------------------------------------------------------------------------
302
    */
303
304
    public function history(): ?HasMany
305
    {
306
        return $this->hasMany(ElementAttributeHistory::class, 'schema_property_element_id', 'id');
307
    }
308
309
    public function reciprocal(): ?HasOne
310
    {
311
        return $this->hasOne(self::class, 'reciprocal_property_element_id', 'id');
312
    }
313
314
    public function inverse(): ?HasOne
315
    {
316
        return $this->hasOne(self::class, 'reciprocal_property_element_id', 'id');
317
    }
318
319
    /*
320
    |--------------------------------------------------------------------------
321
    | SCOPES
322
    |--------------------------------------------------------------------------
323
    */
324
325
    /*
326
    |--------------------------------------------------------------------------
327
    | ACCESSORS
328
    |--------------------------------------------------------------------------
329
    */
330
331
    /*
332
    |--------------------------------------------------------------------------
333
    | MUTATORS
334
    |--------------------------------------------------------------------------
335
    */
336
}
337