2026-02-21 19:59:51 +03:00
<? php
2026-04-05 22:38:36 +03:00
/**
* UserRepository — user repository
*
* @package XC_VM_Domain_User
* @author Divarion_D <https://github.com/Divarion-D>
* @copyright 2025-2026 Vateron Media
* @link https://github.com/Vateron-Media/XC_VM
* @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
*/
2026-02-21 19:59:51 +03:00
class UserRepository {
2026-06-15 18:07:36 +03:00
private static $db = null ;
2026-06-22 11:47:44 +03:00
/**
* Inject the database handler (dependency injection).
*
2026-06-23 19:46:33 +03:00
* @param \DatabaseHandler $db Database handler.
2026-06-22 11:47:44 +03:00
* @return void
*/
2026-06-15 18:07:36 +03:00
public static function setDb ( $db ) : void {
self :: $db = $db ;
}
2026-06-22 11:47:44 +03:00
/**
* Get the injected database handler.
*
* @return object Database handler.
* @throws \RuntimeException If setDb() was not called first.
*/
2026-06-15 18:07:36 +03:00
private static function db () : object {
if ( self :: $db === null ) {
throw new \RuntimeException ( static :: class . '::setDb() must be called before use.' );
}
return self :: $db ;
}
2026-06-22 11:47:44 +03:00
/**
* Fetch an admin/reseller user matching the given login credentials.
*
* @param string $rUsername Username.
* @param string $rPassword Plain-text password.
* @return array|null The user row, or null if credentials are invalid.
*/
2026-02-27 22:32:39 +03:00
public static function getAuthUserByCredentials ( $rUsername , $rPassword ) {
2026-06-15 18:07:36 +03:00
$db = self :: db ();
2026-02-21 19:59:51 +03:00
$db -> query ( 'SELECT `id`, `username`, `password`, `member_group_id`, `status` FROM `users` WHERE `username` = ? LIMIT 1;' , $rUsername );
if ( $db -> num_rows () == 1 ) {
$rRow = $db -> get_row ();
2026-04-09 21:24:48 +03:00
if ( Authenticator :: checkPassword ( $rPassword , $rRow [ 'password' ])) {
2026-02-21 19:59:51 +03:00
return $rRow ;
}
}
}
2026-06-22 11:47:44 +03:00
/**
* List resellers under a given owner.
*
* @param int $rOwner Owner user id.
* @param bool $rIncludeSelf Include the owner in the result.
* @return array Reseller rows.
*/
2026-02-27 22:32:39 +03:00
public static function getResellers ( $rOwner , $rIncludeSelf = true ) {
2026-06-15 18:07:36 +03:00
$db = self :: db ();
2026-02-21 19:59:51 +03:00
if ( $rIncludeSelf ) {
$db -> query ( 'SELECT `id`, `username` FROM `users` WHERE `owner_id` = ? OR `id` = ? ORDER BY `username` ASC;' , $rOwner , $rOwner );
} else {
$db -> query ( 'SELECT `id`, `username` FROM `users` WHERE `owner_id` = ? ORDER BY `username` ASC;' , $rOwner );
}
return $db -> get_rows ( true , 'id' );
}
2026-06-22 11:47:44 +03:00
/**
* Get the direct sub-users (reports) of the current user.
*
* @param array $rPermissions Effective permissions.
* @param array $rUserInfo Current user row.
* @param bool $rIncludeSelf Include the user themselves.
* @return array Direct report rows.
*/
2026-02-27 22:32:39 +03:00
public static function getDirectReports ( $rPermissions , $rUserInfo , $rIncludeSelf = true ) {
2026-06-15 18:07:36 +03:00
$db = self :: db ();
2026-02-21 19:59:51 +03:00
$rUserIDs = $rPermissions [ 'direct_reports' ];
if ( ! $rIncludeSelf ) {
} else {
$rUserIDs [] = $rUserInfo [ 'id' ];
}
$rReturn = array ();
if ( 0 >= count ( $rUserIDs )) {
} else {
$db -> query ( 'SELECT * FROM `users` WHERE `owner_id` IN (' . implode ( ',' , array_map ( 'intval' , $rUserIDs )) . ') ORDER BY `username` ASC;' );
if ( 0 >= $db -> num_rows ()) {
} else {
foreach ( $db -> get_rows () as $rRow ) {
$rReturn [ intval ( $rRow [ 'id' ])] = $rRow ;
}
}
}
return $rReturn ;
}
2026-06-22 11:47:44 +03:00
/**
* Resolve the parent of a user within the permission scope.
*
* @param array $rPermissions Effective permissions.
* @param array $rUserInfo Current user row.
* @param int $rID Target user id.
* @return array|null Parent user row, or null.
*/
2026-02-21 19:59:51 +03:00
public static function getParent ( $rPermissions , $rUserInfo , $rID ) {
if ( ! isset ( $rPermissions [ 'users' ][ $rID ][ 'parent' ]) || $rPermissions [ 'users' ][ $rID ][ 'parent' ] == 0 || $rPermissions [ 'users' ][ $rID ][ 'parent' ] == $rUserInfo [ 'id' ]) {
return $rID ;
}
return self :: getParent ( $rPermissions , $rUserInfo , $rPermissions [ 'users' ][ $rID ][ 'parent' ]);
}
2026-06-22 11:47:44 +03:00
/**
* Get all descendant sub-users of a user (full report tree).
*
* @param int $rUser User id.
* @return array Sub-user rows keyed by id.
*/
2026-02-27 22:32:39 +03:00
public static function getSubUsers ( $rUser ) {
2026-06-15 18:07:36 +03:00
$db = self :: db ();
2026-02-21 19:59:51 +03:00
$rReturn = array ();
$db -> query ( 'SELECT `id`, `username` FROM `users` WHERE `owner_id` = ?;' , $rUser );
foreach ( $db -> get_rows () as $rRow ) {
$rReturn [ $rRow [ 'id' ]] = array ( 'username' => $rRow [ 'username' ], 'parent' => $rUser );
2026-02-27 22:32:39 +03:00
foreach ( self :: getSubUsers ( $rRow [ 'id' ]) as $rUserID => $rUserData ) {
2026-02-21 19:59:51 +03:00
$rReturn [ $rUserID ] = $rUserData ;
}
}
return $rReturn ;
}
2026-06-22 11:47:44 +03:00
/**
* Fetch a line by id.
*
* @param int $rID Line id.
* @return array|null The line row, or null if not found.
*/
2026-02-27 22:32:39 +03:00
public static function getLineById ( $rID ) {
2026-06-15 18:07:36 +03:00
$db = self :: db ();
2026-02-21 19:59:51 +03:00
$db -> query ( 'SELECT * FROM `lines` WHERE `id` = ?;' , $rID );
if ( $db -> num_rows () != 1 ) {
} else {
return $db -> get_row ();
}
}
2026-06-22 11:47:44 +03:00
/**
* Fetch a registered (panel) user by id.
*
* @param int $rID User id.
* @return array|null The user row, or null if not found.
*/
2026-02-27 22:32:39 +03:00
public static function getRegisteredUserById ( $rID ) {
2026-06-15 18:07:36 +03:00
$db = self :: db ();
2026-02-21 19:59:51 +03:00
$db -> query ( 'SELECT * FROM `users` WHERE `id` = ?;' , $rID );
if ( $db -> num_rows () != 1 ) {
} else {
return $db -> get_row ();
}
}
2026-06-22 11:47:44 +03:00
/**
* List registered users under an owner.
*
* @param int|null $rOwner Owner id, or null for all.
* @param bool $rIncludeSelf Include the owner in the result.
* @return array Registered user rows.
*/
2026-02-27 22:32:39 +03:00
public static function getRegisteredUsers ( $rOwner = null , $rIncludeSelf = true ) {
2026-06-15 18:07:36 +03:00
$db = self :: db ();
2026-02-21 19:59:51 +03:00
$rReturn = array ();
$db -> query ( 'SELECT * FROM `users` ORDER BY `username` ASC;' );
if ( 0 >= $db -> num_rows ()) {
} else {
foreach ( $db -> get_rows () as $rRow ) {
if ( ! ( ! $rOwner || $rRow [ 'owner_id' ] == $rOwner || $rRow [ 'id' ] == $rOwner && $rIncludeSelf )) {
} else {
$rReturn [ intval ( $rRow [ 'id' ])] = $rRow ;
}
}
}
if ( count ( $rReturn ) == 0 ) {
$rReturn [ - 1 ] = array ();
}
return $rReturn ;
}
2026-06-22 11:47:44 +03:00
/**
* Resolve a streaming user's info (auth + entitlements).
*
* Looks the user up by id or username/password, then assembles their
* bouquets and, optionally, allowed channel ids and active connections.
*
* @param array $rSettings Panel settings.
* @param bool $rCached Use cached lookups.
* @param array $rBouquets Bouquet definitions.
* @param int|null $rUserID User id (when known).
* @param string|null $rUsername Username (credential lookup).
* @param string|null $rPassword Password (credential lookup).
* @param bool $rGetChannelIDs Include allowed channel ids.
* @param bool $rGetConnections Include active connections.
* @param string $rIP Client IP.
* @return array|null User info, or null if not found.
*/
2026-03-03 20:00:34 +03:00
public static function getStreamingUserInfo ( $rSettings , $rCached , $rBouquets , $rUserID = null , $rUsername = null , $rPassword = null , $rGetChannelIDs = false , $rGetConnections = false , $rIP = '' ) {
2026-06-15 18:07:36 +03:00
global $rBlockedISP , $rBlockedServers ;
$db = self :: db ();
2026-02-21 19:59:51 +03:00
$rUserInfo = null ;
if ( $rCached ) {
if ( empty ( $rPassword ) && empty ( $rUserID ) && strlen ( $rUsername ) == 32 ) {
if ( $rSettings [ 'case_sensitive_line' ]) {
2026-04-23 21:28:51 +03:00
$rTokenPath = LINES_TMP_PATH . 'line_t_' . $rUsername ;
$rUserID = ( file_exists ( $rTokenPath ) ? intval ( file_get_contents ( $rTokenPath )) : 0 );
2026-02-21 19:59:51 +03:00
} else {
2026-04-23 21:28:51 +03:00
$rTokenPath = LINES_TMP_PATH . 'line_t_' . strtolower ( $rUsername );
$rUserID = ( file_exists ( $rTokenPath ) ? intval ( file_get_contents ( $rTokenPath )) : 0 );
2026-02-21 19:59:51 +03:00
}
} else {
if ( ! empty ( $rUsername ) && ! empty ( $rPassword )) {
if ( $rSettings [ 'case_sensitive_line' ]) {
2026-04-23 21:28:51 +03:00
$rCachePath = LINES_TMP_PATH . 'line_c_' . $rUsername . '_' . $rPassword ;
$rUserID = ( file_exists ( $rCachePath ) ? intval ( file_get_contents ( $rCachePath )) : 0 );
2026-02-21 19:59:51 +03:00
} else {
2026-04-23 21:28:51 +03:00
$rCachePath = LINES_TMP_PATH . 'line_c_' . strtolower ( $rUsername ) . '_' . strtolower ( $rPassword );
$rUserID = ( file_exists ( $rCachePath ) ? intval ( file_get_contents ( $rCachePath )) : 0 );
2026-02-21 19:59:51 +03:00
}
} else {
if ( empty ( $rUserID )) {
return false ;
}
}
}
if ( $rUserID ) {
2026-04-23 21:28:51 +03:00
$rInfoPath = LINES_TMP_PATH . 'line_i_' . $rUserID ;
if ( file_exists ( $rInfoPath )) {
$rUserInfo = igbinary_unserialize ( file_get_contents ( $rInfoPath ));
} else {
return false ;
}
2026-02-21 19:59:51 +03:00
}
} else {
if ( empty ( $rPassword ) && empty ( $rUserID ) && strlen ( $rUsername ) == 32 ) {
$db -> query ( 'SELECT * FROM `lines` WHERE `is_mag` = 0 AND `is_e2` = 0 AND `access_token` = ? AND LENGTH(`access_token`) = 32' , $rUsername );
} else {
if ( ! empty ( $rUsername ) && ! empty ( $rPassword )) {
$db -> query ( 'SELECT `lines`.*, `mag_devices`.`token` AS `mag_token` FROM `lines` LEFT JOIN `mag_devices` ON `mag_devices`.`user_id` = `lines`.`id` WHERE `username` = ? AND `password` = ? LIMIT 1' , $rUsername , $rPassword );
} else {
if ( ! empty ( $rUserID )) {
$db -> query ( 'SELECT `lines`.*, `mag_devices`.`token` AS `mag_token` FROM `lines` LEFT JOIN `mag_devices` ON `mag_devices`.`user_id` = `lines`.`id` WHERE `id` = ?' , $rUserID );
} else {
return false ;
}
}
}
if ( 0 < $db -> num_rows ()) {
$rUserInfo = $db -> get_row ();
}
}
if ( ! $rUserInfo ) {
return false ;
}
if ( $rCached ) {
if ( empty ( $rPassword ) && empty ( $rUserID ) && strlen ( $rUsername ) == 32 ) {
if ( $rUsername != $rUserInfo [ 'access_token' ]) {
return false ;
}
} else {
if ( ! empty ( $rUsername ) && ! empty ( $rPassword )) {
if ( $rUsername != $rUserInfo [ 'username' ] || $rPassword != $rUserInfo [ 'password' ]) {
return false ;
}
}
}
}
if ( $rSettings [ 'county_override_1st' ] == 1 && empty ( $rUserInfo [ 'forced_country' ]) && ! empty ( $rIP ) && $rUserInfo [ 'max_connections' ] == 1 ) {
2026-03-03 20:00:34 +03:00
$rUserInfo [ 'forced_country' ] = GeoIPService :: getIPInfo ( $rIP )[ 'registered_country' ][ 'iso_code' ];
2026-02-21 19:59:51 +03:00
if ( $rCached ) {
2026-03-03 20:00:34 +03:00
file_put_contents ( SIGNALS_TMP_PATH . 'cache_' . md5 ( 'forced_country/' . $rUserInfo [ 'id' ]), json_encode ( array ( 'forced_country/' . $rUserInfo [ 'id' ], $rUserInfo [ 'forced_country' ])));
2026-02-21 19:59:51 +03:00
} else {
$db -> query ( 'UPDATE `lines` SET `forced_country` = ? WHERE `id` = ?' , $rUserInfo [ 'forced_country' ], $rUserInfo [ 'id' ]);
}
}
$allowedIPS = json_decode ( $rUserInfo [ 'allowed_ips' ], true );
$allowedUa = json_decode ( $rUserInfo [ 'allowed_ua' ], true );
$rUserInfo [ 'bouquet' ] = json_decode ( $rUserInfo [ 'bouquet' ], true );
$rUserInfo [ 'allowed_ips' ] = array_filter ( array_map ( 'trim' , is_array ( $allowedIPS ) ? $allowedIPS : []));
$rUserInfo [ 'allowed_ua' ] = array_filter ( array_map ( 'trim' , is_array ( $allowedUa ) ? $allowedUa : []));
$rUserInfo [ 'allowed_outputs' ] = array_map ( 'intval' , json_decode ( $rUserInfo [ 'allowed_outputs' ], true ));
$rUserInfo [ 'output_formats' ] = array ();
if ( $rCached ) {
foreach ( igbinary_unserialize ( file_get_contents ( CACHE_TMP_PATH . 'output_formats' )) as $rRow ) {
if ( in_array ( intval ( $rRow [ 'access_output_id' ]), $rUserInfo [ 'allowed_outputs' ])) {
$rUserInfo [ 'output_formats' ][] = $rRow [ 'output_key' ];
}
}
} else {
$db -> query ( 'SELECT `access_output_id`, `output_key` FROM `output_formats`;' );
foreach ( $db -> get_rows () as $rRow ) {
if ( in_array ( intval ( $rRow [ 'access_output_id' ]), $rUserInfo [ 'allowed_outputs' ])) {
$rUserInfo [ 'output_formats' ][] = $rRow [ 'output_key' ];
}
}
}
$rUserInfo [ 'con_isp_name' ] = null ;
$rUserInfo [ 'isp_violate' ] = 0 ;
$rUserInfo [ 'isp_is_server' ] = 0 ;
if ( $rSettings [ 'show_isps' ] == 1 && ! empty ( $rIP )) {
2026-03-03 20:00:34 +03:00
$rISPLock = GeoIPService :: getISP ( $rIP );
2026-02-21 19:59:51 +03:00
if ( is_array ( $rISPLock ) && ! empty ( $rISPLock [ 'isp' ])) {
$rUserInfo [ 'con_isp_name' ] = $rISPLock [ 'isp' ];
$rUserInfo [ 'isp_asn' ] = $rISPLock [ 'autonomous_system_number' ];
2026-03-03 20:00:34 +03:00
$rUserInfo [ 'isp_violate' ] = BlocklistService :: checkISP ( $rBlockedISP , $rUserInfo [ 'con_isp_name' ]);
2026-02-21 19:59:51 +03:00
if ( $rSettings [ 'block_svp' ] == 1 ) {
2026-03-03 20:00:34 +03:00
$rUserInfo [ 'isp_is_server' ] = intval ( BlocklistService :: checkServer ( $rBlockedServers , $rUserInfo [ 'isp_asn' ]));
2026-02-21 19:59:51 +03:00
}
}
if ( ! empty ( $rUserInfo [ 'con_isp_name' ]) && $rSettings [ 'enable_isp_lock' ] == 1 && $rUserInfo [ 'is_stalker' ] == 0 && $rUserInfo [ 'is_isplock' ] == 1 && ! empty ( $rUserInfo [ 'isp_desc' ]) && strtolower ( $rUserInfo [ 'con_isp_name' ]) != strtolower ( $rUserInfo [ 'isp_desc' ])) {
$rUserInfo [ 'isp_violate' ] = 1 ;
}
if ( $rUserInfo [ 'isp_violate' ] == 0 && strtolower ( $rUserInfo [ 'con_isp_name' ]) != strtolower ( $rUserInfo [ 'isp_desc' ])) {
if ( $rCached ) {
2026-03-03 20:00:34 +03:00
$rSignalKey = 'isp/' . $rUserInfo [ 'id' ];
file_put_contents ( SIGNALS_TMP_PATH . 'cache_' . md5 ( $rSignalKey ), json_encode ( array ( $rSignalKey , json_encode ( array ( $rUserInfo [ 'con_isp_name' ], $rUserInfo [ 'isp_asn' ])))));
2026-02-21 19:59:51 +03:00
} else {
$db -> query ( 'UPDATE `lines` SET `isp_desc` = ?, `as_number` = ? WHERE `id` = ?' , $rUserInfo [ 'con_isp_name' ], $rUserInfo [ 'isp_asn' ], $rUserInfo [ 'id' ]);
}
}
}
if ( $rGetChannelIDs ) {
$rLiveIDs = $rVODIDs = $rRadioIDs = $rCategoryIDs = $rChannelIDs = $rSeriesIDs = array ();
foreach ( $rUserInfo [ 'bouquet' ] as $rID ) {
if ( isset ( $rBouquets [ $rID ][ 'streams' ])) {
$rChannelIDs = array_merge ( $rChannelIDs , $rBouquets [ $rID ][ 'streams' ]);
}
if ( isset ( $rBouquets [ $rID ][ 'series' ])) {
$rSeriesIDs = array_merge ( $rSeriesIDs , $rBouquets [ $rID ][ 'series' ]);
}
if ( isset ( $rBouquets [ $rID ][ 'channels' ])) {
$rLiveIDs = array_merge ( $rLiveIDs , $rBouquets [ $rID ][ 'channels' ]);
}
if ( isset ( $rBouquets [ $rID ][ 'movies' ])) {
$rVODIDs = array_merge ( $rVODIDs , $rBouquets [ $rID ][ 'movies' ]);
}
if ( isset ( $rBouquets [ $rID ][ 'radios' ])) {
$rRadioIDs = array_merge ( $rRadioIDs , $rBouquets [ $rID ][ 'radios' ]);
}
}
$rUserInfo [ 'channel_ids' ] = array_map ( 'intval' , array_unique ( $rChannelIDs ));
$rUserInfo [ 'series_ids' ] = array_map ( 'intval' , array_unique ( $rSeriesIDs ));
$rUserInfo [ 'vod_ids' ] = array_map ( 'intval' , array_unique ( $rVODIDs ));
$rUserInfo [ 'live_ids' ] = array_map ( 'intval' , array_unique ( $rLiveIDs ));
$rUserInfo [ 'radio_ids' ] = array_map ( 'intval' , array_unique ( $rRadioIDs ));
}
$rAllowedCategories = array ();
$rCategoryMap = igbinary_unserialize ( file_get_contents ( CACHE_TMP_PATH . 'category_map' ));
foreach ( $rUserInfo [ 'bouquet' ] as $rID ) {
$rAllowedCategories = array_merge ( $rAllowedCategories , ( $rCategoryMap [ $rID ] ?: array ()));
}
$rUserInfo [ 'category_ids' ] = array_values ( array_unique ( $rAllowedCategories ));
return $rUserInfo ;
}
2026-03-07 21:53:45 +03:00
2026-06-22 11:47:44 +03:00
/**
* Resolve user info for streaming endpoints (wrapper over getStreamingUserInfo).
*
* @param int|null $rUserID User id (when known).
* @param string|null $rUsername Username (credential lookup).
* @param string|null $rPassword Password (credential lookup).
* @param bool $rGetChannelIDs Include allowed channel ids.
* @param bool $rGetConnections Include active connections.
* @param string $rIP Client IP.
* @return array|null User info, or null if not found.
*/
2026-03-07 21:53:45 +03:00
public static function getUserInfo ( $rUserID = null , $rUsername = null , $rPassword = null , $rGetChannelIDs = false , $rGetConnections = false , $rIP = '' ) {
2026-06-15 18:07:36 +03:00
global $rSettings ;
$db = self :: db ();
2026-03-08 16:19:30 +03:00
$rCached = $rSettings [ 'enable_cache' ];
2026-03-07 21:53:45 +03:00
$rBouquets = BouquetService :: getAll ();
$rUserInfo = null ;
if ( $rCached ) {
if ( empty ( $rPassword ) && empty ( $rUserID ) && strlen ( $rUsername ) == 32 ) {
if ( $rSettings [ 'case_sensitive_line' ]) {
2026-04-23 21:28:51 +03:00
$rTokenPath = LINES_TMP_PATH . 'line_t_' . $rUsername ;
$rUserID = ( file_exists ( $rTokenPath ) ? intval ( file_get_contents ( $rTokenPath )) : 0 );
2026-03-07 21:53:45 +03:00
} else {
2026-04-23 21:28:51 +03:00
$rTokenPath = LINES_TMP_PATH . 'line_t_' . strtolower ( $rUsername );
$rUserID = ( file_exists ( $rTokenPath ) ? intval ( file_get_contents ( $rTokenPath )) : 0 );
2026-03-07 21:53:45 +03:00
}
} else {
if ( ! empty ( $rUsername ) && ! empty ( $rPassword )) {
if ( $rSettings [ 'case_sensitive_line' ]) {
2026-04-23 21:28:51 +03:00
$rCachePath = LINES_TMP_PATH . 'line_c_' . $rUsername . '_' . $rPassword ;
$rUserID = ( file_exists ( $rCachePath ) ? intval ( file_get_contents ( $rCachePath )) : 0 );
2026-03-07 21:53:45 +03:00
} else {
2026-04-23 21:28:51 +03:00
$rCachePath = LINES_TMP_PATH . 'line_c_' . strtolower ( $rUsername ) . '_' . strtolower ( $rPassword );
$rUserID = ( file_exists ( $rCachePath ) ? intval ( file_get_contents ( $rCachePath )) : 0 );
2026-03-07 21:53:45 +03:00
}
} else {
if ( ! empty ( $rUserID )) {
} else {
return false ;
}
}
}
if ( ! $rUserID ) {
} else {
2026-04-23 21:28:51 +03:00
$rInfoPath = LINES_TMP_PATH . 'line_i_' . $rUserID ;
if ( file_exists ( $rInfoPath )) {
$rUserInfo = igbinary_unserialize ( file_get_contents ( $rInfoPath ));
} else {
return false ;
}
2026-03-07 21:53:45 +03:00
}
} else {
if ( empty ( $rPassword ) && empty ( $rUserID ) && strlen ( $rUsername ) == 32 ) {
$db -> query ( 'SELECT * FROM `lines` WHERE `is_mag` = 0 AND `is_e2` = 0 AND `access_token` = ? AND LENGTH(`access_token`) = 32' , $rUsername );
} else {
if ( ! empty ( $rUsername ) && ! empty ( $rPassword )) {
$db -> query ( 'SELECT `lines`.*, `mag_devices`.`token` AS `mag_token` FROM `lines` LEFT JOIN `mag_devices` ON `mag_devices`.`user_id` = `lines`.`id` WHERE `username` = ? AND `password` = ? LIMIT 1' , $rUsername , $rPassword );
} else {
if ( ! empty ( $rUserID )) {
$db -> query ( 'SELECT `lines`.*, `mag_devices`.`token` AS `mag_token` FROM `lines` LEFT JOIN `mag_devices` ON `mag_devices`.`user_id` = `lines`.`id` WHERE `id` = ?' , $rUserID );
} else {
return false ;
}
}
}
if ( 0 >= $db -> num_rows ()) {
} else {
$rUserInfo = $db -> get_row ();
}
}
if ( ! $rUserInfo ) {
return false ;
}
if ( ! $rCached ) {
} else {
if ( empty ( $rPassword ) && empty ( $rUserID ) && strlen ( $rUsername ) == 32 ) {
if ( $rUsername == $rUserInfo [ 'access_token' ]) {
} else {
return false ;
}
} else {
if ( empty ( $rUsername ) || empty ( $rPassword )) {
} else {
if ( ! ( $rUsername != $rUserInfo [ 'username' ] || $rPassword != $rUserInfo [ 'password' ])) {
} else {
return false ;
}
}
}
}
if ( ! ( $rSettings [ 'county_override_1st' ] == 1 && empty ( $rUserInfo [ 'forced_country' ]) && ! empty ( $rIP ) && $rUserInfo [ 'max_connections' ] == 1 )) {
} else {
$rUserInfo [ 'forced_country' ] = GeoIP :: getCountry ( $rIP )[ 'registered_country' ][ 'iso_code' ];
if ( $rCached ) {
RedisManager :: setSignal ( 'forced_country/' . $rUserInfo [ 'id' ], $rUserInfo [ 'forced_country' ]);
} else {
$db -> query ( 'UPDATE `lines` SET `forced_country` = ? WHERE `id` = ?' , $rUserInfo [ 'forced_country' ], $rUserInfo [ 'id' ]);
}
}
$allowedIPS = json_decode ( $rUserInfo [ 'allowed_ips' ], true );
$allowedUa = json_decode ( $rUserInfo [ 'allowed_ua' ], true );
$rUserInfo [ 'bouquet' ] = json_decode ( $rUserInfo [ 'bouquet' ], true );
$rUserInfo [ 'allowed_ips' ] = array_filter ( array_map ( 'trim' , is_array ( $allowedIPS ) ? $allowedIPS : []));
$rUserInfo [ 'allowed_ua' ] = array_filter ( array_map ( 'trim' , is_array ( $allowedUa ) ? $allowedUa : []));
$rUserInfo [ 'allowed_outputs' ] = array_map ( 'intval' , json_decode ( $rUserInfo [ 'allowed_outputs' ], true ));
$rUserInfo [ 'output_formats' ] = array ();
if ( $rCached ) {
foreach ( igbinary_unserialize ( file_get_contents ( CACHE_TMP_PATH . 'output_formats' )) as $rRow ) {
if ( ! in_array ( intval ( $rRow [ 'access_output_id' ]), $rUserInfo [ 'allowed_outputs' ])) {
} else {
$rUserInfo [ 'output_formats' ][] = $rRow [ 'output_key' ];
}
}
} else {
$db -> query ( 'SELECT `access_output_id`, `output_key` FROM `output_formats`;' );
foreach ( $db -> get_rows () as $rRow ) {
if ( ! in_array ( intval ( $rRow [ 'access_output_id' ]), $rUserInfo [ 'allowed_outputs' ])) {
} else {
$rUserInfo [ 'output_formats' ][] = $rRow [ 'output_key' ];
}
}
}
$rUserInfo [ 'con_isp_name' ] = null ;
$rUserInfo [ 'isp_violate' ] = 0 ;
$rUserInfo [ 'isp_is_server' ] = 0 ;
if ( $rSettings [ 'show_isps' ] != 1 || empty ( $rIP )) {
} else {
$rISPLock = GeoIP :: getISP ( $rIP );
2026-04-30 12:54:41 +03:00
if ( is_array ( $rISPLock )) {
if ( ! empty ( $rISPLock [ 'isp' ])) {
2026-03-07 21:53:45 +03:00
$rUserInfo [ 'con_isp_name' ] = $rISPLock [ 'isp' ];
$rUserInfo [ 'isp_asn' ] = $rISPLock [ 'autonomous_system_number' ];
$rUserInfo [ 'isp_violate' ] = GeoIP :: isISPBlocked ( $rUserInfo [ 'con_isp_name' ], BlocklistService :: getBlockedISP ());
2026-04-30 12:54:41 +03:00
if ( $rSettings [ 'block_svp' ] == 1 ) {
2026-03-07 21:53:45 +03:00
$rUserInfo [ 'isp_is_server' ] = intval ( GeoIP :: isASNBlocked ( $rUserInfo [ 'isp_asn' ], BlocklistService :: getBlockedServers ()));
}
}
}
if ( ! ( ! empty ( $rUserInfo [ 'con_isp_name' ]) && $rSettings [ 'enable_isp_lock' ] == 1 && $rUserInfo [ 'is_stalker' ] == 0 && $rUserInfo [ 'is_isplock' ] == 1 && ! empty ( $rUserInfo [ 'isp_desc' ]) && strtolower ( $rUserInfo [ 'con_isp_name' ]) != strtolower ( $rUserInfo [ 'isp_desc' ]))) {
} else {
$rUserInfo [ 'isp_violate' ] = 1 ;
}
if ( ! ( $rUserInfo [ 'isp_violate' ] == 0 && strtolower ( $rUserInfo [ 'con_isp_name' ]) != strtolower ( $rUserInfo [ 'isp_desc' ]))) {
} else {
if ( $rCached ) {
RedisManager :: setSignal ( 'isp/' . $rUserInfo [ 'id' ], json_encode ( array ( $rUserInfo [ 'con_isp_name' ], $rUserInfo [ 'isp_asn' ])));
} else {
$db -> query ( 'UPDATE `lines` SET `isp_desc` = ?, `as_number` = ? WHERE `id` = ?' , $rUserInfo [ 'con_isp_name' ], $rUserInfo [ 'isp_asn' ], $rUserInfo [ 'id' ]);
}
}
}
if ( ! $rGetChannelIDs ) {
} else {
$rLiveIDs = $rVODIDs = $rRadioIDs = $rCategoryIDs = $rChannelIDs = $rSeriesIDs = array ();
foreach ( $rUserInfo [ 'bouquet' ] as $rID ) {
if ( ! isset ( $rBouquets [ $rID ][ 'streams' ])) {
} else {
$rChannelIDs = array_merge ( $rChannelIDs , $rBouquets [ $rID ][ 'streams' ]);
}
if ( ! isset ( $rBouquets [ $rID ][ 'series' ])) {
} else {
$rSeriesIDs = array_merge ( $rSeriesIDs , $rBouquets [ $rID ][ 'series' ]);
}
if ( ! isset ( $rBouquets [ $rID ][ 'channels' ])) {
} else {
$rLiveIDs = array_merge ( $rLiveIDs , $rBouquets [ $rID ][ 'channels' ]);
}
if ( ! isset ( $rBouquets [ $rID ][ 'movies' ])) {
} else {
$rVODIDs = array_merge ( $rVODIDs , $rBouquets [ $rID ][ 'movies' ]);
}
if ( ! isset ( $rBouquets [ $rID ][ 'radios' ])) {
} else {
$rRadioIDs = array_merge ( $rRadioIDs , $rBouquets [ $rID ][ 'radios' ]);
}
}
$rUserInfo [ 'channel_ids' ] = array_map ( 'intval' , array_unique ( $rChannelIDs ));
$rUserInfo [ 'series_ids' ] = array_map ( 'intval' , array_unique ( $rSeriesIDs ));
$rUserInfo [ 'vod_ids' ] = array_map ( 'intval' , array_unique ( $rVODIDs ));
$rUserInfo [ 'live_ids' ] = array_map ( 'intval' , array_unique ( $rLiveIDs ));
$rUserInfo [ 'radio_ids' ] = array_map ( 'intval' , array_unique ( $rRadioIDs ));
}
$rAllowedCategories = array ();
$rCategoryMap = igbinary_unserialize ( file_get_contents ( CACHE_TMP_PATH . 'category_map' ));
foreach ( $rUserInfo [ 'bouquet' ] as $rID ) {
$rAllowedCategories = array_merge ( $rAllowedCategories , ( $rCategoryMap [ $rID ] ?: array ()));
}
$rUserInfo [ 'category_ids' ] = array_values ( array_unique ( $rAllowedCategories ));
return $rUserInfo ;
}
2026-06-22 11:47:44 +03:00
/**
* Resolve Enigma2 device user info.
*
* @param array $rDevice Device row (MAC etc.).
* @param bool $rGetChannelIDs Include allowed channel ids.
* @param bool $rGetBouquetInfo Include bouquet info.
* @param bool $rGetConnections Include active connections.
* @return array|null Device user info, or null if not found.
*/
2026-03-07 21:53:45 +03:00
public static function getE2Info ( $rDevice , $rGetChannelIDs = false , $rGetBouquetInfo = false , $rGetConnections = false ) {
2026-06-15 18:07:36 +03:00
$db = self :: db ();
2026-03-07 21:53:45 +03:00
if ( empty ( $rDevice [ 'device_id' ])) {
$db -> query ( 'SELECT * FROM `enigma2_devices` WHERE `mac` = ?' , $rDevice [ 'mac' ]);
} else {
$db -> query ( 'SELECT * FROM `enigma2_devices` WHERE `device_id` = ?' , $rDevice [ 'device_id' ]);
}
if ( 0 >= $db -> num_rows ()) {
return false ;
}
$rReturn = array ();
$rReturn [ 'enigma2' ] = $db -> get_row ();
$rReturn [ 'user_info' ] = array ();
if ( ! ( $rUserInfo = self :: getUserInfo ( $rReturn [ 'enigma2' ][ 'user_id' ], null , null , $rGetChannelIDs , $rGetConnections ))) {
} else {
$rReturn [ 'user_info' ] = $rUserInfo ;
}
$rReturn [ 'pair_line_info' ] = array ();
if ( empty ( $rReturn [ 'user_info' ])) {
} else {
$rReturn [ 'pair_line_info' ] = array ();
if ( is_null ( $rReturn [ 'user_info' ][ 'pair_id' ])) {
} else {
if ( ! ( $rUserInfo = self :: getUserInfo ( $rReturn [ 'user_info' ][ 'pair_id' ], null , null , $rGetChannelIDs , $rGetConnections ))) {
} else {
$rReturn [ 'pair_line_info' ] = $rUserInfo ;
}
}
}
return $rReturn ;
}
2026-02-21 19:59:51 +03:00
}