db->tableExists('parent_policy_acceptances')) { return; } $this->forge->addField([ 'id' => [ 'type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true, ], 'parent_id' => [ 'type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => false, ], 'school_year' => [ 'type' => 'VARCHAR', 'constraint' => 9, 'null' => false, ], 'accepted_at' => [ 'type' => 'DATETIME', 'null' => false, ], 'source' => [ 'type' => 'VARCHAR', 'constraint' => 40, 'null' => false, 'default' => 'registration', ], 'ip_address' => [ 'type' => 'VARCHAR', 'constraint' => 45, 'null' => true, ], 'user_agent' => [ 'type' => 'VARCHAR', 'constraint' => 255, 'null' => true, ], 'created_at' => [ 'type' => 'DATETIME', 'null' => true, ], 'updated_at' => [ 'type' => 'DATETIME', 'null' => true, ], ]); $this->forge->addKey('id', true); $this->forge->addUniqueKey(['parent_id', 'school_year'], 'uq_parent_policy_year'); $this->forge->addKey('school_year'); $this->forge->createTable('parent_policy_acceptances', true); $this->backfillExistingAcceptances(); } public function down(): void { $this->forge->dropTable('parent_policy_acceptances', true); } private function backfillExistingAcceptances(): void { if (! $this->db->tableExists('users')) { return; } $schoolYear = $this->configuredSchoolYear(); if ($schoolYear === null) { return; } $builder = $this->db->table('users u') ->select('u.id') ->where('u.accept_school_policy', 1); $hasUserType = $this->db->fieldExists('user_type', 'users'); $hasRoleTables = $this->db->tableExists('user_roles') && $this->db->tableExists('roles'); if ($hasUserType && $hasRoleTables) { $builder->groupStart() ->where('u.user_type', 'primary') ->orWhere( 'EXISTS (SELECT 1 FROM user_roles ur INNER JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = u.id AND LOWER(r.name) = ' . $this->db->escape('parent') . ')', null, false ) ->groupEnd(); } elseif ($hasUserType) { $builder->where('u.user_type', 'primary'); } elseif ($hasRoleTables) { $builder->where( 'EXISTS (SELECT 1 FROM user_roles ur INNER JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = u.id AND LOWER(r.name) = ' . $this->db->escape('parent') . ')', null, false ); } else { return; } $rows = $builder->get() ->getResultArray(); foreach ($rows as $row) { $parentId = (int) ($row['id'] ?? 0); if ($parentId <= 0) { continue; } $existing = $this->db->table('parent_policy_acceptances') ->where('parent_id', $parentId) ->where('school_year', $schoolYear) ->get(1) ->getRowArray(); if ($existing !== null) { continue; } $this->db->table('parent_policy_acceptances')->insert([ 'parent_id' => $parentId, 'school_year' => $schoolYear, 'accepted_at' => date('Y-m-d H:i:s'), 'source' => 'legacy_backfill', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), ]); } } private function configuredSchoolYear(): ?string { if ($this->db->tableExists('school_years')) { $row = $this->db->table('school_years') ->select('name') ->where('status', 'active') ->orderBy('id', 'DESC') ->get(1) ->getRowArray(); $name = trim((string) ($row['name'] ?? '')); if (preg_match('/^\d{4}-\d{4}$/', $name)) { return $name; } } if (! $this->db->tableExists('configuration')) { return null; } $row = $this->db->table('configuration') ->select('config_value') ->where('config_key', 'school_year') ->orderBy('id', 'DESC') ->get(1) ->getRowArray(); $name = trim((string) ($row['config_value'] ?? '')); return preg_match('/^\d{4}-\d{4}$/', $name) ? $name : null; } }