Passed
Push — stage ( b80c43...225f24 )
by Jon
12:24
created

ElementAttribute::project()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
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;
0 ignored issues
show
introduced by
The trait Venturecraft\Revisionable\RevisionableTrait requires some properties which are not provided by App\Models\ElementAttribute: $revisionNullString, $revisionEnabled, $revisionCleanup, $revisionFormattedFields, $keepRevisionOf, $revisionUnknownString, $revisionFormattedFieldNames, $historyLimit, $softDelete
Loading history...
95
    use Cacheable;
0 ignored issues
show
Bug introduced by
The trait Laracasts\Matryoshka\Cacheable requires the property $timestamp which is not provided by App\Models\ElementAttribute.
Loading history...
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
158
                        //only procede if we allow statement generation
159
                        if (! $attribute->project()->generate_statements) {
160
                            return;
161
                        }
162
163
                        $attribute->reciprocal->createHistory('deleted');
164
                        $attribute->reciprocal()->delete();
165
                    }
166
167
                    $attribute->createReciprocal();
168
169
                    return;
170
                }
171
            }
172
            $attribute->createHistory('updated');
173
        });
174
175
        static::deleted(function (ElementAttribute $attribute) {
176
            $attribute->createHistory('deleted');
177
178
            //only procede if we allow statement generation
179
            if (! $attribute->project()->generate_statements) {
180
                return;
181
            }
182
183
            if ($attribute->reciprocal_property_element_id) {
184
                $reciprocal = self::find($attribute->reciprocal_property_element_id);
185
                if ($reciprocal) {
186
                    $reciprocal->delete();
187
                }
188
            }
189
        });
190
    }
191
192
    /*
193
    |--------------------------------------------------------------------------
194
    | FUNCTIONS
195
    |--------------------------------------------------------------------------
196
    */
197
198
    public function createHistory(string $action): ElementAttributeHistory
199
    {
200
        return ElementAttributeHistory::create([
201
            'action'                     => $action,
202
            'created_user_id'            => $this->updated_user_id,
203
            'schema_property_element_id' => $this->id,
204
            'schema_property_id'         => $this->schema_property_id,
205
            'schema_id'                  => $this->element->schema_id,
206
            'profile_property_id'        => $this->profile_property_id,
207
            'object'                     => $this->object,
208
            'language'                   => $this->getAttributeFromArray('language'),
209
            'status_id'                  => $this->status_id,
210
            //this should be set to null in the parent if there was no import
211
            'import_id'                  => $this->last_import_id,
212
            //things we don't know yet. Must come from post-processing
213
            'related_schema_property_id' => null,
214
            //should be added to the schema_property model and not here
215
            'change_note'                => null,
216
        ]);
217
    }
218
219
    public function updateHistory()
220
    {
221
        $history = $this->history()->where('import_id', $this->last_import_id)->first();
222
        if ($history) {
223
            $history->update(['related_schema_property_id' => $this->related_schema_property_id]);
224
        }
225
    }
226
227
    /**
228
     * @param $elementset_id
229
     *
230
     * @return Carbon
231
     * @throws InvalidArgumentException
232
     */
233
    public static function getLatestDateForElementSet($elementset_id): Carbon
234
    {
235
        $created_at = self::getLatest($elementset_id, 'created_at');
236
        $updated_at = self::getLatest($elementset_id, 'updated_at');
237
        $deleted_at = self::getLatest($elementset_id, 'deleted_at');
238
239
        $date = collect([$created_at, $updated_at, $deleted_at])->max();
240
        try {
241
            return Carbon::createFromFormat(config('app.timestamp_format'), $date);
242
        } catch (InvalidArgumentException $e) {
243
            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...
244
        }
245
    }
246
247
    /**
248
     * @param int    $elementset_id
249
     * @param string $field
250
     *
251
     * @return string
252
     */
253
    private static function getLatest($elementset_id, $field)
254
    {
255
        return \DB::table(self::TABLE)
256
            ->join(Element::TABLE, Element::TABLE . '.id', '=', self::TABLE . '.schema_property_id')
257
            ->select(self::TABLE . '.' . $field)
258
            ->where(Element::TABLE . '.schema_id', $elementset_id)
259
            ->max(self::TABLE . '.' . $field);
260
    }
261
262
    public function createReciprocal()
263
    {
264
        //only procede if we allow statement generation
265
        if (! $this->project()->generate_statements) {
266
            return false;
267
        }
268
        //is the object a uri?
269
        if (! filter_var($this->object, FILTER_VALIDATE_URL)) {
270
            return false;
271
        }
272
273
        //does the profile_property have a reciprocal?
274
        if (! $this->profile_property->is_reciprocal && $this->profile_property->inverse_profile_property_id === null) {
275
            return false;
276
        }
277
278
        $reciprocalProperty = $this->profile_property->inverse_profile_property_id ?? $this->profile_property->id;
279
280
        //does it reference a known URI in a vocabulary I 'own'? (tricky -- what if it hasn't been created yet?)
281
        $relatedElement = Element::whereUri($this->object)->first();
282
        if (! $relatedElement) {
283
            $this->update(['review_reciprocal' => true]);
284
285
            return true;
286
        }
287
        //todo: this should probably be findOrFail(), the error caught and added to error log
288
        $user = User::find($this->updated_user_id);
289
        if (!$user) {
290
            return false;
291
        }
292
293
        if ($user->cant('update', $relatedElement)) {
294
            return false;
295
        }
296
297
        //todo: gonna need a review reciprocals job
298
        //create the reciprocal
299
        $attribute = self::create([
300
            'related_schema_property_id'     => $this->element->id,
301
            'object'                         => $this->element->uri,
302
            'schema_property_id'             => $relatedElement->id,
303
            'status_id'                      => $this->status_id,
304
            'profile_property_id'            => $reciprocalProperty,
305
            'last_import_id'                 => $this->last_import_id,
306
            'is_generated'                   => true,
307
            'reciprocal_property_element_id' => $this->id,
308
            'language'                       => null,
309
            'created_user_id'                => $this->updated_user_id,
310
            'updated_user_id'                => $this->updated_user_id,
311
        ]);
312
        $this->update(['reciprocal_property_element_id' => $attribute->id]);
313
    }
314
315
    /*
316
    |--------------------------------------------------------------------------
317
    | RELATIONS
318
    |--------------------------------------------------------------------------
319
    */
320
321
    public function history(): ?HasMany
322
    {
323
        return $this->hasMany(ElementAttributeHistory::class, 'schema_property_element_id', 'id');
324
    }
325
326
    public function reciprocal(): ?HasOne
327
    {
328
        return $this->hasOne(self::class, 'reciprocal_property_element_id', 'id');
329
    }
330
331
    public function inverse(): ?HasOne
332
    {
333
        return $this->hasOne(self::class, 'reciprocal_property_element_id', 'id');
334
    }
335
336
    public function project()
337
    {
338
        return $this->element->elementset->project;
339
    }
340
341
    /*
342
    |--------------------------------------------------------------------------
343
    | SCOPES
344
    |--------------------------------------------------------------------------
345
    */
346
347
    /*
348
    |--------------------------------------------------------------------------
349
    | ACCESSORS
350
    |--------------------------------------------------------------------------
351
    */
352
353
    /*
354
    |--------------------------------------------------------------------------
355
    | MUTATORS
356
    |--------------------------------------------------------------------------
357
    */
358
}
359