Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
65.12% |
56 / 86 |
|
60.00% |
3 / 5 |
CRAP | |
0.00% |
0 / 1 |
| CookieConsentsTable | |
65.12% |
56 / 86 |
|
60.00% |
3 / 5 |
20.17 | |
0.00% |
0 / 1 |
| initialize | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
| validationDefault | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
1 | |||
| buildRules | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| createConsentCookie | |
94.12% |
16 / 17 |
|
0.00% |
0 / 1 |
3.00 | |||
| getLatestConsent | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
56 | |||
| 1 | <?php |
| 2 | declare(strict_types=1); |
| 3 | |
| 4 | namespace App\Model\Table; |
| 5 | |
| 6 | use App\Model\Entity\CookieConsent; |
| 7 | use Cake\Http\Cookie\Cookie; |
| 8 | use Cake\ORM\RulesChecker; |
| 9 | use Cake\ORM\Table; |
| 10 | use Cake\Validation\Validator; |
| 11 | use DateTime; |
| 12 | |
| 13 | /** |
| 14 | * CookieConsents Model |
| 15 | * |
| 16 | * @property \App\Model\Table\UsersTable&\Cake\ORM\Association\BelongsTo $Users |
| 17 | * @method \App\Model\Entity\CookieConsent newEmptyEntity() |
| 18 | * @method \App\Model\Entity\CookieConsent newEntity(array $data, array $options = []) |
| 19 | * @method array<\App\Model\Entity\CookieConsent> newEntities(array $data, array $options = []) |
| 20 | * @method \App\Model\Entity\CookieConsent get(mixed $primaryKey, array|string $finder = 'all', \Psr\SimpleCache\CacheInterface|string|null $cache = null, \Closure|string|null $cacheKey = null, mixed ...$args) |
| 21 | * @method \App\Model\Entity\CookieConsent findOrCreate($search, ?callable $callback = null, array $options = []) |
| 22 | * @method \App\Model\Entity\CookieConsent patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = []) |
| 23 | * @method array<\App\Model\Entity\CookieConsent> patchEntities(iterable $entities, array $data, array $options = []) |
| 24 | * @method \App\Model\Entity\CookieConsent|false save(\Cake\Datasource\EntityInterface $entity, array $options = []) |
| 25 | * @method \App\Model\Entity\CookieConsent saveOrFail(\Cake\Datasource\EntityInterface $entity, array $options = []) |
| 26 | * @method iterable<\App\Model\Entity\CookieConsent>|\Cake\Datasource\ResultSetInterface<\App\Model\Entity\CookieConsent>|false saveMany(iterable $entities, array $options = []) |
| 27 | * @method iterable<\App\Model\Entity\CookieConsent>|\Cake\Datasource\ResultSetInterface<\App\Model\Entity\CookieConsent> saveManyOrFail(iterable $entities, array $options = []) |
| 28 | * @method iterable<\App\Model\Entity\CookieConsent>|\Cake\Datasource\ResultSetInterface<\App\Model\Entity\CookieConsent>|false deleteMany(iterable $entities, array $options = []) |
| 29 | * @method iterable<\App\Model\Entity\CookieConsent>|\Cake\Datasource\ResultSetInterface<\App\Model\Entity\CookieConsent> deleteManyOrFail(iterable $entities, array $options = []) |
| 30 | * @mixin \Cake\ORM\Behavior\TimestampBehavior |
| 31 | */ |
| 32 | class CookieConsentsTable extends Table |
| 33 | { |
| 34 | /** |
| 35 | * Initialize method |
| 36 | * |
| 37 | * @param array<string, mixed> $config The configuration for the Table. |
| 38 | * @return void |
| 39 | */ |
| 40 | public function initialize(array $config): void |
| 41 | { |
| 42 | parent::initialize($config); |
| 43 | |
| 44 | $this->setTable('cookie_consents'); |
| 45 | $this->setDisplayField('ip_address'); |
| 46 | $this->setPrimaryKey('id'); |
| 47 | |
| 48 | $this->addBehavior('Timestamp'); |
| 49 | |
| 50 | $this->belongsTo('Users', [ |
| 51 | 'foreignKey' => 'user_id', |
| 52 | ]); |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * Default validation rules. |
| 57 | * |
| 58 | * @param \Cake\Validation\Validator $validator Validator instance. |
| 59 | * @return \Cake\Validation\Validator |
| 60 | */ |
| 61 | public function validationDefault(Validator $validator): Validator |
| 62 | { |
| 63 | $validator |
| 64 | ->uuid('user_id') |
| 65 | ->allowEmptyString('user_id'); |
| 66 | |
| 67 | $validator |
| 68 | ->scalar('session_id') |
| 69 | ->maxLength('session_id', 255) |
| 70 | ->allowEmptyString('session_id'); |
| 71 | |
| 72 | $validator |
| 73 | ->boolean('analytics_consent') |
| 74 | ->notEmptyString('analytics_consent'); |
| 75 | |
| 76 | $validator |
| 77 | ->boolean('functional_consent') |
| 78 | ->notEmptyString('functional_consent'); |
| 79 | |
| 80 | $validator |
| 81 | ->boolean('marketing_consent') |
| 82 | ->notEmptyString('marketing_consent'); |
| 83 | |
| 84 | $validator |
| 85 | ->boolean('essential_consent') |
| 86 | ->notEmptyString('essential_consent'); |
| 87 | |
| 88 | $validator |
| 89 | ->scalar('ip_address') |
| 90 | ->maxLength('ip_address', 45) |
| 91 | ->requirePresence('ip_address', 'create') |
| 92 | ->notEmptyString('ip_address'); |
| 93 | |
| 94 | $validator |
| 95 | ->scalar('user_agent') |
| 96 | ->maxLength('user_agent', 255) |
| 97 | ->requirePresence('user_agent', 'create') |
| 98 | ->notEmptyString('user_agent'); |
| 99 | |
| 100 | return $validator; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Returns a rules checker object that will be used for validating |
| 105 | * application integrity. |
| 106 | * |
| 107 | * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. |
| 108 | * @return \Cake\ORM\RulesChecker |
| 109 | */ |
| 110 | public function buildRules(RulesChecker $rules): RulesChecker |
| 111 | { |
| 112 | $rules->add($rules->existsIn(['user_id'], 'Users'), ['errorField' => 'user_id']); |
| 113 | |
| 114 | return $rules; |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * Creates a consent cookie from a consent entity |
| 119 | * getLatestConsent($sessionId, $user->getIdentifier()) |
| 120 | * |
| 121 | * @param \App\Model\Entity\CookieConsent $consent The consent entity to create cookie from |
| 122 | * @return \Cake\Http\Cookie\Cookie The configured cookie object |
| 123 | */ |
| 124 | public function createConsentCookie(CookieConsent $consent): Cookie |
| 125 | { |
| 126 | $isHttps = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'; |
| 127 | |
| 128 | $cookie = (new Cookie('consent_cookie')) |
| 129 | ->withValue(json_encode([ |
| 130 | 'user_id' => $consent->user_id, |
| 131 | 'analytics_consent' => $consent->analytics_consent, |
| 132 | 'functional_consent' => $consent->functional_consent, |
| 133 | 'marketing_consent' => $consent->marketing_consent, |
| 134 | 'essential_consent' => true, |
| 135 | 'created' => time(), |
| 136 | ])) |
| 137 | ->withExpiry(new DateTime('+1 year')) |
| 138 | ->withPath('/') |
| 139 | ->withHttpOnly(true) |
| 140 | ->withSameSite('Lax'); |
| 141 | |
| 142 | // Only set Secure flag if we're actually on HTTPS |
| 143 | if ($isHttps) { |
| 144 | $cookie = $cookie->withSecure(true); |
| 145 | } |
| 146 | |
| 147 | return $cookie; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * Gets the latest consent record prioritizing user_id over session_id |
| 152 | * |
| 153 | * @param string|null $sessionId The session ID to search for |
| 154 | * @param string|null $userId The user ID to search for |
| 155 | * @return array|null Array of consent data or null if no match found |
| 156 | */ |
| 157 | public function getLatestConsent(?string $sessionId = null, ?string $userId = null): ?array |
| 158 | { |
| 159 | $fields = [ |
| 160 | 'user_id', |
| 161 | 'session_id', |
| 162 | 'analytics_consent', |
| 163 | 'functional_consent', |
| 164 | 'marketing_consent', |
| 165 | 'essential_consent', |
| 166 | 'ip_address', |
| 167 | 'user_agent', |
| 168 | ]; |
| 169 | |
| 170 | // If both parameters are null, return early |
| 171 | if ($sessionId === null && $userId === null) { |
| 172 | return null; |
| 173 | } |
| 174 | |
| 175 | // First try to find by user_id if provided |
| 176 | if ($userId !== null) { |
| 177 | $result = $this->find() |
| 178 | ->select($fields) |
| 179 | ->where(['user_id' => $userId]) |
| 180 | ->orderBy(['created' => 'DESC']) |
| 181 | ->first(); |
| 182 | |
| 183 | if ($result !== null) { |
| 184 | return $result->toArray(); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | // If no result found by user_id and session_id is provided, try finding by session_id |
| 189 | if ($sessionId !== null) { |
| 190 | $result = $this->find() |
| 191 | ->select($fields) |
| 192 | ->where(['session_id' => $sessionId]) |
| 193 | ->orderBy(['created' => 'DESC']) |
| 194 | ->first(); |
| 195 | |
| 196 | if ($result !== null) { |
| 197 | return $result->toArray(); |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | // No matching record found |
| 202 | return null; |
| 203 | } |
| 204 | } |