2025-10-08 14:23:07 +03:00
< ? php
namespace App\Models ;
use Core\Model ;
class Domain extends Model
{
protected static string $table = 'domains' ;
2025-10-20 17:25:02 +03:00
/**
* Get User model instance
*/
private function getUserModel () : \App\Models\User
{
return new \App\Models\User ();
}
2025-10-08 14:23:07 +03:00
/**
* Get all domains with their notification group
*/
2025-10-20 17:04:13 +03:00
public function getAllWithGroups ( ? int $userId = null ) : array
2025-10-08 14:23:07 +03:00
{
2025-10-25 02:04:00 +03:00
$sql = " SELECT d.*, ng.name as group_name,
GROUP_CONCAT ( t . name ORDER BY t . name SEPARATOR ',' ) as tags ,
GROUP_CONCAT ( t . color ORDER BY t . name SEPARATOR '|' ) as tag_colors
2025-10-08 14:23:07 +03:00
FROM domains d
2025-10-25 02:04:00 +03:00
LEFT JOIN notification_groups ng ON d . notification_group_id = ng . id
LEFT JOIN domain_tags dt ON d . id = dt . domain_id
LEFT JOIN tags t ON dt . tag_id = t . id " ;
2025-10-20 17:04:13 +03:00
2025-10-20 18:03:16 +03:00
if ( $userId ) {
2025-10-29 13:13:56 +02:00
// In isolated mode: only show tags that belong to this user or are global
2025-10-25 02:04:00 +03:00
$sql .= " WHERE d.user_id = ? AND (t.user_id = ? OR t.user_id IS NULL) GROUP BY d.id ORDER BY d.status DESC, d.expiration_date ASC " ;
2025-10-20 17:04:13 +03:00
$stmt = $this -> db -> prepare ( $sql );
2025-10-25 02:04:00 +03:00
$stmt -> execute ([ $userId , $userId ]);
2025-10-20 17:04:13 +03:00
} else {
2025-10-29 13:13:56 +02:00
// In shared mode: show all tags
2025-10-25 02:04:00 +03:00
$sql .= " GROUP BY d.id ORDER BY d.status DESC, d.expiration_date ASC " ;
2025-10-20 17:04:13 +03:00
$stmt = $this -> db -> query ( $sql );
}
2025-10-08 14:23:07 +03:00
return $stmt -> fetchAll ();
}
/**
* Get domains expiring within days
*/
2025-10-20 17:04:13 +03:00
public function getExpiringDomains ( int $days , ? int $userId = null ) : array
2025-10-08 14:23:07 +03:00
{
$sql = " SELECT d.*, ng.name as group_name
FROM domains d
LEFT JOIN notification_groups ng ON d . notification_group_id = ng . id
WHERE d . is_active = 1
AND d . expiration_date IS NOT NULL
2025-10-29 01:29:17 +02:00
AND d . expiration_date <= DATE_ADD ( CURDATE (), INTERVAL ? + 1 DAY )
AND d . expiration_date > CURDATE () " ;
2025-10-20 17:04:13 +03:00
$params = [ $days ];
2025-10-20 18:03:16 +03:00
if ( $userId ) {
2025-10-20 17:04:13 +03:00
$sql .= " AND d.user_id = ? " ;
$params [] = $userId ;
}
$sql .= " ORDER BY d.expiration_date ASC " ;
2025-10-08 14:23:07 +03:00
$stmt = $this -> db -> prepare ( $sql );
2025-10-20 17:04:13 +03:00
$stmt -> execute ( $params );
2025-10-08 14:23:07 +03:00
return $stmt -> fetchAll ();
}
/**
* Get domains by status
*/
2025-10-20 17:04:13 +03:00
public function getByStatus ( string $status , ? int $userId = null ) : array
2025-10-08 14:23:07 +03:00
{
$sql = " SELECT d.*, ng.name as group_name
FROM domains d
LEFT JOIN notification_groups ng ON d . notification_group_id = ng . id
2025-10-20 17:04:13 +03:00
WHERE d . status = ? " ;
$params = [ $status ];
2025-10-20 18:03:16 +03:00
if ( $userId ) {
2025-10-20 17:04:13 +03:00
$sql .= " AND d.user_id = ? " ;
$params [] = $userId ;
}
$sql .= " ORDER BY d.expiration_date ASC " ;
2025-10-08 14:23:07 +03:00
$stmt = $this -> db -> prepare ( $sql );
2025-10-20 17:04:13 +03:00
$stmt -> execute ( $params );
2025-10-08 14:23:07 +03:00
return $stmt -> fetchAll ();
}
/**
* Get domain with notification channels
*/
2025-10-20 21:08:09 +03:00
public function getWithChannels ( int $id , ? int $userId = null ) : ? array
2025-10-08 14:23:07 +03:00
{
$sql = " SELECT d.*, ng.name as group_name, ng.id as group_id
FROM domains d
LEFT JOIN notification_groups ng ON d . notification_group_id = ng . id
WHERE d . id = ? " ;
2025-10-20 21:08:09 +03:00
$params = [ $id ];
if ( $userId ) {
$sql .= " AND d.user_id = ? " ;
$params [] = $userId ;
}
2025-10-08 14:23:07 +03:00
$stmt = $this -> db -> prepare ( $sql );
2025-10-20 21:08:09 +03:00
$stmt -> execute ( $params );
2025-10-08 14:23:07 +03:00
$domain = $stmt -> fetch ();
if ( ! $domain ) {
return null ;
}
// Get notification channels for this domain's group
if ( $domain [ 'group_id' ]) {
$channelModel = new NotificationChannel ();
$domain [ 'channels' ] = $channelModel -> getByGroupId ( $domain [ 'group_id' ]);
} else {
$domain [ 'channels' ] = [];
}
return $domain ;
}
2025-10-20 21:08:09 +03:00
/**
* Find domain by ID with user isolation support
*/
public function findWithIsolation ( int $id , ? int $userId = null ) : ? array
{
$sql = " SELECT * FROM domains WHERE id = ? " ;
$params = [ $id ];
if ( $userId ) {
$sql .= " AND user_id = ? " ;
$params [] = $userId ;
}
$stmt = $this -> db -> prepare ( $sql );
$stmt -> execute ( $params );
$result = $stmt -> fetch ();
return $result ? : null ;
}
2025-10-08 14:23:07 +03:00
/**
* Check if domain exists
*/
public function existsByDomain ( string $domainName ) : bool
{
$stmt = $this -> db -> prepare ( " SELECT COUNT(*) as count FROM domains WHERE domain_name = ? " );
$stmt -> execute ([ $domainName ]);
$result = $stmt -> fetch ();
return $result [ 'count' ] > 0 ;
}
/**
* Get recent domains
*/
2025-10-20 17:04:13 +03:00
public function getRecent ( int $limit = 5 , ? int $userId = null ) : array
2025-10-08 14:23:07 +03:00
{
$sql = " SELECT d.*, ng.name as group_name
FROM domains d
LEFT JOIN notification_groups ng ON d . notification_group_id = ng . id
2025-10-20 17:04:13 +03:00
WHERE d . is_active = 1 " ;
$params = [];
2025-10-20 18:03:16 +03:00
if ( $userId ) {
2025-10-20 17:04:13 +03:00
$sql .= " AND d.user_id = ? " ;
$params [] = $userId ;
}
$sql .= " ORDER BY d.created_at DESC, d.id DESC LIMIT ? " ;
$params [] = $limit ;
2025-10-08 14:23:07 +03:00
$stmt = $this -> db -> prepare ( $sql );
2025-10-20 17:04:13 +03:00
$stmt -> execute ( $params );
2025-10-08 14:23:07 +03:00
return $stmt -> fetchAll ();
}
/**
* Get dashboard statistics
*/
2025-10-20 17:04:13 +03:00
public function getStatistics ( ? int $userId = null ) : array
2025-10-08 14:23:07 +03:00
{
$stats = [
'total' => 0 ,
'active' => 0 ,
'expiring_soon' => 0 ,
'expired' => 0 ,
'inactive' => 0 ,
2025-10-20 18:38:58 +03:00
'expiring_threshold' => 30 ,
2025-10-08 14:23:07 +03:00
];
2025-10-20 17:04:13 +03:00
// Build WHERE clause for user filtering
$whereClause = " WHERE is_active = 1 " ;
$params = [];
2025-10-20 18:03:16 +03:00
if ( $userId ) {
2025-10-20 17:04:13 +03:00
$whereClause .= " AND user_id = ? " ;
$params [] = $userId ;
}
2025-10-20 12:43:51 +03:00
// Get status counts for active domains only
2025-10-20 17:04:13 +03:00
$sql = " SELECT status, COUNT(*) as count FROM domains $whereClause GROUP BY status " ;
$stmt = $this -> db -> prepare ( $sql );
$stmt -> execute ( $params );
2025-10-08 14:23:07 +03:00
$results = $stmt -> fetchAll ();
$stats [ 'total' ] = array_sum ( array_column ( $results , 'count' ));
foreach ( $results as $row ) {
$stats [ strtolower ( $row [ 'status' ])] = $row [ 'count' ];
}
2025-10-20 12:43:51 +03:00
// Get count of inactive domains (is_active = 0)
2025-10-20 17:04:13 +03:00
$inactiveWhereClause = " WHERE is_active = 0 " ;
$inactiveParams = [];
2025-10-20 18:03:16 +03:00
if ( $userId ) {
2025-10-20 17:04:13 +03:00
$inactiveWhereClause .= " AND user_id = ? " ;
$inactiveParams [] = $userId ;
}
$inactiveStmt = $this -> db -> prepare ( " SELECT COUNT(*) as count FROM domains $inactiveWhereClause " );
$inactiveStmt -> execute ( $inactiveParams );
2025-10-20 12:43:51 +03:00
$inactiveResult = $inactiveStmt -> fetch ();
$stats [ 'inactive' ] = $inactiveResult [ 'count' ] ? ? 0 ;
// Add inactive count to total
$stats [ 'total' ] += $stats [ 'inactive' ];
2025-10-20 18:38:58 +03:00
// Get expiring soon count
$settingModel = new \App\Models\Setting ();
$notificationDays = $settingModel -> getNotificationDays ();
$threshold = ! empty ( $notificationDays ) ? max ( $notificationDays ) : 30 ;
$stats [ 'expiring_threshold' ] = $threshold ;
2025-10-29 01:29:17 +02:00
$expiringWhereClause = " WHERE is_active = 1 AND expiration_date IS NOT NULL AND expiration_date <= DATE_ADD(NOW(), INTERVAL ?+1 DAY) AND expiration_date > NOW() " ;
2025-10-20 18:38:58 +03:00
$expiringParams = [ $threshold ];
if ( $userId ) {
$expiringWhereClause .= " AND user_id = ? " ;
$expiringParams [] = $userId ;
}
$expiringStmt = $this -> db -> prepare ( " SELECT COUNT(*) as count FROM domains $expiringWhereClause " );
$expiringStmt -> execute ( $expiringParams );
$expiringResult = $expiringStmt -> fetch ();
$stats [ 'expiring_soon' ] = $expiringResult [ 'count' ] ? ? 0 ;
2025-10-08 14:23:07 +03:00
return $stats ;
}
2025-10-10 14:01:19 +03:00
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
/**
* Get effective status for sorting ( active + daysLeft within threshold = expiring_soon )
*/
private static function getEffectiveStatusForSort ( array $domain , int $expiringThreshold ) : string
{
$status = $domain [ 'status' ] ? ? '' ;
if ( $status === 'inactive' ) {
return 'inactive' ;
}
if ( $status === 'active' && ! empty ( $domain [ 'expiration_date' ])) {
$daysLeft = ( int ) floor (( strtotime ( $domain [ 'expiration_date' ]) - time ()) / 86400 );
if ( $daysLeft <= $expiringThreshold && $daysLeft >= 0 ) {
return 'expiring_soon' ;
}
}
return $status ? : 'error' ;
}
2025-10-10 14:01:19 +03:00
/**
* Get filtered , sorted , and paginated domains
*/
2025-10-20 17:04:13 +03:00
public function getFilteredPaginated ( array $filters , string $sortBy , string $sortOrder , int $page , int $perPage , int $expiringThreshold = 30 , ? int $userId = null ) : array
2025-10-10 14:01:19 +03:00
{
// Get all domains with groups
2025-10-20 17:04:13 +03:00
$domains = $this -> getAllWithGroups ( $userId );
2025-10-10 14:01:19 +03:00
// Apply search filter
if ( ! empty ( $filters [ 'search' ])) {
$domains = array_filter ( $domains , function ( $domain ) use ( $filters ) {
return stripos ( $domain [ 'domain_name' ], $filters [ 'search' ]) !== false ||
stripos ( $domain [ 'registrar' ] ? ? '' , $filters [ 'search' ]) !== false ;
});
}
// Apply status filter
if ( ! empty ( $filters [ 'status' ])) {
$domains = array_filter ( $domains , function ( $domain ) use ( $filters , $expiringThreshold ) {
if ( $filters [ 'status' ] === 'expiring_soon' ) {
// Check if domain expires within configured threshold
if ( ! empty ( $domain [ 'expiration_date' ])) {
$daysLeft = floor (( strtotime ( $domain [ 'expiration_date' ]) - time ()) / 86400 );
return $daysLeft <= $expiringThreshold && $daysLeft >= 0 ;
}
return false ;
}
2025-10-20 12:43:51 +03:00
// Handle inactive filter (based on is_active field)
if ( $filters [ 'status' ] === 'inactive' ) {
return $domain [ 'is_active' ] == 0 ;
}
// Handle available and error status filters
if ( $filters [ 'status' ] === 'available' || $filters [ 'status' ] === 'error' ) {
return $domain [ 'status' ] === $filters [ 'status' ];
}
2025-10-10 14:01:19 +03:00
return $domain [ 'status' ] === $filters [ 'status' ];
});
}
// Apply group filter
if ( ! empty ( $filters [ 'group' ])) {
$domains = array_filter ( $domains , function ( $domain ) use ( $filters ) {
return $domain [ 'notification_group_id' ] == $filters [ 'group' ];
});
}
2025-10-12 12:46:16 +03:00
// Apply tag filter
if ( ! empty ( $filters [ 'tag' ])) {
2025-10-25 02:04:00 +03:00
// Get domain IDs that have the specified tag
$tagSql = " SELECT DISTINCT dt.domain_id
FROM domain_tags dt
JOIN tags t ON dt . tag_id = t . id
WHERE t . name = ? " ;
$tagParams = [ $filters [ 'tag' ]];
if ( $userId ) {
$tagSql .= " AND dt.domain_id IN (SELECT id FROM domains WHERE user_id = ?) " ;
$tagParams [] = $userId ;
}
$tagStmt = $this -> db -> prepare ( $tagSql );
$tagStmt -> execute ( $tagParams );
$taggedDomainIds = array_column ( $tagStmt -> fetchAll (), 'domain_id' );
$domains = array_filter ( $domains , function ( $domain ) use ( $taggedDomainIds ) {
return in_array ( $domain [ 'id' ], $taggedDomainIds );
2025-10-12 12:46:16 +03:00
});
}
2025-10-10 14:01:19 +03:00
// Get total count after filtering
$totalDomains = count ( $domains );
// Apply sorting
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
usort ( $domains , function ( $a , $b ) use ( $sortBy , $sortOrder , $expiringThreshold ) {
2025-10-10 14:01:19 +03:00
$aVal = $a [ $sortBy ] ? ? '' ;
$bVal = $b [ $sortBy ] ? ? '' ;
Add DNS monitoring and refresh functionality
Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
2026-03-08 14:32:05 +02:00
// When sorting by status: use effective status (active + daysLeft<=threshold = expiring_soon) and logical priority order
if ( $sortBy === 'status' ) {
$aVal = self :: getEffectiveStatusForSort ( $a , $expiringThreshold );
$bVal = self :: getEffectiveStatusForSort ( $b , $expiringThreshold );
$priority = [
'expired' => 1 ,
'redemption_period' => 2 ,
'pending_delete' => 3 ,
'expiring_soon' => 4 ,
'active' => 5 ,
'available' => 6 ,
'error' => 7 ,
'inactive' => 8 ,
];
$aOrder = $priority [ $aVal ] ? ? 99 ;
$bOrder = $priority [ $bVal ] ? ? 99 ;
$comparison = $aOrder <=> $bOrder ;
if ( $comparison === 0 ) {
$comparison = strcasecmp ( $a [ 'domain_name' ] ? ? '' , $b [ 'domain_name' ] ? ? '' );
}
return $sortOrder === 'desc' ? - $comparison : $comparison ;
}
2025-10-10 14:01:19 +03:00
$comparison = strcasecmp ( $aVal , $bVal );
return $sortOrder === 'desc' ? - $comparison : $comparison ;
});
// Calculate pagination
$totalPages = ceil ( $totalDomains / $perPage );
$page = min ( $page , max ( 1 , $totalPages )); // Ensure page is within valid range
$offset = ( $page - 1 ) * $perPage ;
// Slice array for current page
$paginatedDomains = array_slice ( $domains , $offset , $perPage );
return [
'domains' => $paginatedDomains ,
'pagination' => [
'current_page' => $page ,
'per_page' => $perPage ,
'total' => $totalDomains ,
'total_pages' => $totalPages ,
'showing_from' => $totalDomains > 0 ? $offset + 1 : 0 ,
'showing_to' => min ( $offset + $perPage , $totalDomains )
]
];
}
2025-10-12 12:46:16 +03:00
/**
* Get all unique tags from all domains
*/
2025-10-20 17:04:13 +03:00
public function getAllTags ( ? int $userId = null ) : array
2025-10-12 12:46:16 +03:00
{
2025-10-25 02:04:00 +03:00
$sql = " SELECT DISTINCT t.name
FROM tags t
JOIN domain_tags dt ON t . id = dt . tag_id
JOIN domains d ON d . id = dt . domain_id " ;
2025-10-20 17:04:13 +03:00
$params = [];
2025-10-20 18:21:05 +03:00
if ( $userId ) {
2025-10-29 13:13:56 +02:00
// In isolated mode: only show tags that belong to this user or are global
2025-10-25 02:04:00 +03:00
$sql .= " WHERE d.user_id = ? AND (t.user_id = ? OR t.user_id IS NULL) " ;
$params [] = $userId ;
2025-10-20 17:04:13 +03:00
$params [] = $userId ;
}
2025-10-29 13:13:56 +02:00
// In shared mode: show all tags (no additional filtering needed)
2025-10-20 17:04:13 +03:00
2025-10-25 02:04:00 +03:00
$sql .= " ORDER BY t.name " ;
2025-10-20 17:04:13 +03:00
$stmt = $this -> db -> prepare ( $sql );
$stmt -> execute ( $params );
2025-10-12 12:46:16 +03:00
$results = $stmt -> fetchAll ();
2025-10-25 02:04:00 +03:00
return array_column ( $results , 'name' );
}
/**
* Get tags that are assigned to specific domains
*/
public function getTagsForDomains ( array $domainIds , ? int $userId = null ) : array
{
if ( empty ( $domainIds )) {
return [];
}
$placeholders = str_repeat ( '?,' , count ( $domainIds ) - 1 ) . '?' ;
$sql = " SELECT DISTINCT t.id, t.name, t.color
FROM tags t
JOIN domain_tags dt ON t . id = dt . tag_id
WHERE dt . domain_id IN ( $placeholders ) " ;
$params = $domainIds ;
if ( $userId ) {
2025-10-29 13:13:56 +02:00
// In isolated mode: only show tags that belong to this user or are global
2025-10-25 02:04:00 +03:00
$sql .= " AND (t.user_id = ? OR t.user_id IS NULL) " ;
$params [] = $userId ;
2025-10-12 12:46:16 +03:00
}
2025-10-29 13:13:56 +02:00
// In shared mode: show all tags (no additional filtering needed)
2025-10-12 12:46:16 +03:00
2025-10-25 02:04:00 +03:00
$sql .= " ORDER BY t.name " ;
$stmt = $this -> db -> prepare ( $sql );
$stmt -> execute ( $params );
return $stmt -> fetchAll ();
2025-10-12 12:46:16 +03:00
}
2025-10-20 17:04:13 +03:00
2025-10-20 17:25:02 +03:00
2025-10-20 17:04:13 +03:00
/**
2025-10-20 17:25:02 +03:00
* Assign all domains without user_id to a specific user
2025-10-20 17:04:13 +03:00
*/
2025-10-20 17:25:02 +03:00
public function assignUnassignedDomainsToUser ( int $userId ) : int
2025-10-20 17:04:13 +03:00
{
2025-10-20 17:25:02 +03:00
$stmt = $this -> db -> prepare ( " UPDATE domains SET user_id = ? WHERE user_id IS NULL " );
2025-10-20 17:04:13 +03:00
$stmt -> execute ([ $userId ]);
2025-10-20 17:25:02 +03:00
return $stmt -> rowCount ();
2025-10-20 17:04:13 +03:00
}
/**
2025-10-20 17:25:02 +03:00
* Search domains for suggestions ( quick search )
2025-10-20 17:04:13 +03:00
*/
2025-10-20 21:08:09 +03:00
public function searchSuggestions ( string $query , int $limit = 5 , ? int $userId = null ) : array
2025-10-20 17:04:13 +03:00
{
2025-10-20 17:25:02 +03:00
$sql = " SELECT d.id, d.domain_name, d.registrar, d.expiration_date, d.status, ng.name as group_name
FROM domains d
LEFT JOIN notification_groups ng ON d . notification_group_id = ng . id
2025-10-20 21:08:09 +03:00
WHERE ( d . domain_name LIKE ?
OR d . registrar LIKE ? ) " ;
$params = [ '%' . $query . '%' , '%' . $query . '%' ];
if ( $userId ) {
$sql .= " AND d.user_id = ? " ;
$params [] = $userId ;
}
$sql .= " ORDER BY d.domain_name ASC LIMIT ? " ;
$params [] = $limit ;
2025-10-20 17:25:02 +03:00
$stmt = $this -> db -> prepare ( $sql );
2025-10-20 21:08:09 +03:00
$stmt -> execute ( $params );
2025-10-20 17:25:02 +03:00
return $stmt -> fetchAll ();
}
/**
* Search domains with user isolation support
*/
public function searchDomains ( string $query , ? int $userId = null , int $limit = 50 ) : array
{
$sql = " SELECT d.*, ng.name as group_name
FROM domains d
LEFT JOIN notification_groups ng ON d . notification_group_id = ng . id
WHERE ( d . domain_name LIKE ?
OR d . registrar LIKE ?
OR ng . name LIKE ? ) " ;
$params = [ '%' . $query . '%' , '%' . $query . '%' , '%' . $query . '%' ];
2025-10-20 18:03:16 +03:00
if ( $userId ) {
2025-10-20 17:25:02 +03:00
$sql .= " AND d.user_id = ? " ;
$params [] = $userId ;
}
$sql .= " ORDER BY d.domain_name ASC LIMIT ? " ;
$params [] = $limit ;
$stmt = $this -> db -> prepare ( $sql );
$stmt -> execute ( $params );
return $stmt -> fetchAll ();
2025-10-20 17:04:13 +03:00
}
2025-10-20 17:42:38 +03:00
/**
* Update multiple domains based on WHERE conditions
*/
public function updateWhere ( array $conditions , array $data ) : int
{
if ( empty ( $conditions ) || empty ( $data )) {
return 0 ;
}
// Build WHERE clause
$whereClause = [];
$params = [];
foreach ( $conditions as $field => $value ) {
$whereClause [] = " { $field } = ? " ;
$params [] = $value ;
}
// Build SET clause
$setClause = [];
foreach ( $data as $field => $value ) {
$setClause [] = " { $field } = ? " ;
$params [] = $value ;
}
$sql = " UPDATE domains SET " . implode ( ', ' , $setClause ) . " WHERE " . implode ( ' AND ' , $whereClause );
$stmt = $this -> db -> prepare ( $sql );
$stmt -> execute ( $params );
return $stmt -> rowCount ();
}
2025-10-25 02:04:00 +03:00
/**
* Get a single domain with tags and groups
*/
public function getWithTagsAndGroups ( int $id , ? int $userId = null ) : ? array
{
$sql = " SELECT d.*, ng.name as group_name, ng.id as group_id,
GROUP_CONCAT ( t . name ORDER BY t . name SEPARATOR ',' ) as tags ,
GROUP_CONCAT ( t . color ORDER BY t . name SEPARATOR '|' ) as tag_colors
FROM domains d
LEFT JOIN notification_groups ng ON d . notification_group_id = ng . id
LEFT JOIN domain_tags dt ON d . id = dt . domain_id
2025-10-29 19:43:50 +02:00
LEFT JOIN tags t ON dt . tag_id = t . id
WHERE d . id = ? " ;
// First parameter corresponds to d.id
2025-10-29 13:13:56 +02:00
$params = [ $id ];
2025-10-29 19:43:50 +02:00
2025-10-25 02:04:00 +03:00
if ( $userId ) {
2025-10-29 13:13:56 +02:00
// In isolated mode: only show tags that belong to this user or are global
$sql .= " AND (t.user_id = ? OR t.user_id IS NULL) " ;
$params [] = $userId ;
2025-10-25 02:04:00 +03:00
$sql .= " AND d.user_id = ? " ;
$params [] = $userId ;
}
2025-10-29 19:43:50 +02:00
2025-10-25 02:04:00 +03:00
$sql .= " GROUP BY d.id " ;
$stmt = $this -> db -> prepare ( $sql );
$stmt -> execute ( $params );
$domain = $stmt -> fetch ();
if ( ! $domain ) {
return null ;
}
// Get notification channels for this domain's group
if ( $domain [ 'group_id' ]) {
$channelModel = new NotificationChannel ();
$domain [ 'channels' ] = $channelModel -> getByGroupId ( $domain [ 'group_id' ]);
} else {
$domain [ 'channels' ] = [];
}
return $domain ;
}
2025-10-08 14:23:07 +03:00
}