diff --git a/public/main/inc/global.inc.php b/public/main/inc/global.inc.php index 02685fb093..3e4268f19b 100644 --- a/public/main/inc/global.inc.php +++ b/public/main/inc/global.inc.php @@ -142,5 +142,5 @@ try { define('DEFAULT_DOCUMENT_QUOTA', 100000000); } catch (Exception $e) { $controller = new ExceptionController(); - $controller->showAction($e); + $controller->show($e); } diff --git a/public/main/inc/lib/import.lib.php b/public/main/inc/lib/import.lib.php index 59f34aa5c4..fea2b31e47 100644 --- a/public/main/inc/lib/import.lib.php +++ b/public/main/inc/lib/import.lib.php @@ -41,7 +41,7 @@ class Import * * @return array returns an array (in the system encoding) that contains all data from the CSV-file */ - public static function csvToArray($filename) + public static function csvToArray($filename, $delimiter = ';'): array { if (empty($filename)) { return []; @@ -49,15 +49,7 @@ class Import $reader = Reader::createFromPath($filename, 'r'); if ($reader) { - $reader->setDelimiter(';'); - //$reader->stripBom(true); - /*$contents = $reader->__toString(); - if (!Utf8::isUtf8($contents)) { - // If file is not in utf8 try converting to ISO-8859-15 - if ($reader->getStreamFilterMode() == 1) { - $reader->appendStreamFilter('convert.iconv.ISO-8859-15/UTF-8'); - } - }*/ + $reader->setDelimiter($delimiter); $reader->setHeaderOffset(0); $iterator = $reader->getRecords(); diff --git a/src/CoreBundle/EventListener/LegacyListener.php b/src/CoreBundle/EventListener/LegacyListener.php index 0982f87b26..3ddc2524a9 100644 --- a/src/CoreBundle/EventListener/LegacyListener.php +++ b/src/CoreBundle/EventListener/LegacyListener.php @@ -148,9 +148,9 @@ class LegacyListener $session->set('cid_reset', false); } - $session->set( - 'access_url_id', - $this->accessUrlHelper->getCurrent()->getId() - ); + $currentAccessUrl = $this->accessUrlHelper->getCurrent(); + if (null !== $currentAccessUrl) { + $session->set('access_url_id', $currentAccessUrl->getId()); + } } } diff --git a/src/CoreBundle/ServiceHelper/AccessUrlHelper.php b/src/CoreBundle/ServiceHelper/AccessUrlHelper.php index 2e2a7ce879..53ef369a0b 100644 --- a/src/CoreBundle/ServiceHelper/AccessUrlHelper.php +++ b/src/CoreBundle/ServiceHelper/AccessUrlHelper.php @@ -29,14 +29,14 @@ class AccessUrlHelper return $accessUrlEnabled; } - public function getFirstAccessUrl(): AccessUrl + public function getFirstAccessUrl(): ?AccessUrl { $urlId = $this->accessUrlRepository->getFirstId(); - return $this->accessUrlRepository->find($urlId); + return $this->accessUrlRepository->find($urlId) ?: null; } - public function getCurrent(): AccessUrl + public function getCurrent(): ?AccessUrl { static $accessUrl; diff --git a/src/CoreBundle/ServiceHelper/ThemeHelper.php b/src/CoreBundle/ServiceHelper/ThemeHelper.php index 14c3c52186..b8fb219af5 100644 --- a/src/CoreBundle/ServiceHelper/ThemeHelper.php +++ b/src/CoreBundle/ServiceHelper/ThemeHelper.php @@ -39,6 +39,10 @@ final class ThemeHelper */ public function getVisualTheme(): string { + if ('cli' === PHP_SAPI) { + return ''; + } + static $visualTheme; global $lp_theme_css; diff --git a/tests/scripts/synchronize_user_base_from_csv.php b/tests/scripts/synchronize_user_base_from_csv.php index 8607aa0cdb..5f4047d69a 100644 --- a/tests/scripts/synchronize_user_base_from_csv.php +++ b/tests/scripts/synchronize_user_base_from_csv.php @@ -14,61 +14,69 @@ anonymizes user accounts disabled for more than 3 years (applies for all URLs) o This script can be run unattended. -For the field correspondance we use the title of the columns, each title corresponding to a user field and if the column start with extra_ then it is considered as an extrafield. +For the field correspondance we use the title of the columns, each title corresponding to a user field and if the column start with extra_ then it is considered as an extrafield. username field is used to identify and match CSV and Chamilo accounts together. */ exit; // Change this to the absolute path to chamilo root folder if you move the script out of tests/scripts -$chamiloRoot = __DIR__.'/../..'; +$chamiloRoot = __DIR__.'/../../public'; // Set to true in order to get a trace of changes made by this script -$debug = false; +$debug = true; -// Set to test mode by default to only show the output, put this test variable to 0 to enable creation, modificaction and deletion of users -$test = 1; +// Set to test mode by default to only show the output, put this test variable to 0 to enable creation, modificaction y deletion of users +$test = 0; // It defines if the user not found in any of the CSV files but present in Chamilo should be deleted or disabled. By default it will be disabled. // Set it to true for users to be deleted. $deleteUsersNotFoundInCSV = false; -// Re-enable users found in CSV file and that where present but inactivated in Chamilo +// Re-enable users found in CSV file and that were present but inactivated in Chamilo $reenableUsersFoundInCSV = false; // Anonymize user accounts disabled for more than 3 years $anonymizeUserAccountsDisbaledFor3Years = false; // List of username of accounts that should not be disabled or deleted if not present in CSV -// For exemple the first admin and the anonymous user that has no username ('') +// For example the first admin and the anonymous user that has no username ('') //$usernameListNotToTouchEvenIfNotInCSV = ['admin','','test']; -// Extra field to be emptied when user is anonimized to really make it anonyme, for example the sso id of the user -// extraFieldToEmpty = "cas_user"; - +// Extra field to be emptied when user is anonymized to really make it anonymous, for example the sso id of the user +// $extraFieldToEmpty = "cas_user"; +use Chamilo\CoreBundle\Entity\Admin; use Chamilo\CoreBundle\Entity\ExtraFieldValues; use Chamilo\CoreBundle\Entity\ExtraField; use Chamilo\CoreBundle\Entity\TrackEDefault; -use Chamilo\UserBundle\Entity\User; -use Doctrine\DBAL\FetchMode; +use Chamilo\CoreBundle\Entity\User; use Doctrine\ORM\OptimisticLockException; if (php_sapi_name() !== 'cli') { die("this script is supposed to be run from the command-line\n"); } -require $chamiloRoot.'/cli-config.php'; +require_once $chamiloRoot.'/main/inc/global.inc.php'; require_once $chamiloRoot.'/main/inc/lib/api.lib.php'; require_once $chamiloRoot.'/main/inc/lib/database.constants.inc.php'; ini_set('memory_limit', -1); +$statusList = [ + 'teacher' => 1, // COURSEMANAGER + 'session_admin' => 3, // SESSIONADMIN + 'drh' => 4, // DRH + 'user' => 5, // STUDENT + 'anonymous' => 6, // ANONYMOUS + 'invited' => 20 // INVITEE +]; + +$entityManager = Database::getManager(); $allCSVUsers = []; const EXTRA_KEY = 'extra_'; -// read all users from the internal database - -$userRepository = Database::getManager()->getRepository('ChamiloUserBundle:User'); +// Read all users from the internal database +$userRepository = $entityManager->getRepository(User::class); $dbUsers = []; foreach ($userRepository->findAll() as $user) { if ($user->getId() > 1) { @@ -81,158 +89,281 @@ if ($debug) { echo count($dbUsers) . " users with id > 1 found in internal database\n"; } -if (api_is_multiple_url_enabled()) { - $accessUrls = api_get_access_urls(0,100000,'id'); -} +$adminRepo = $entityManager->getRepository(Admin::class); +$firstAdmin = $adminRepo->createQueryBuilder('a') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); -if ($debug) { - echo "accessUrls = " . print_r($accessUrls,1); +if ($firstAdmin) { + $creator = $firstAdmin->getUser(); +} else { + die("No admin found in the database\n"); } -$allCSVUsers = []; +$accessUrls = api_get_access_urls(0, 100000, 'id'); foreach ($accessUrls as $accessUrl) { $accessUrlId = $accessUrl['id']; - // Read the content of the csv file for this url (file name is URLID_users.csv so for $accessUrlId = 0 the file name would be 0_users.csv - $filename = $accessUrlId . "_users.csv" - $CSVUsers = Import :: csvToArray($filename); + $filename = $chamiloRoot . "/../tests/scripts/" . $accessUrlId . "_usersexample.csv"; - // create new user accounts found in the CSV and update the existing ones, re-enabling if necessary + if (!file_exists($filename)) { + if ($debug) { + echo "CSV file not found: $filename\n"; + } + continue; + } + + $CSVUsers = Import::csvToArray($filename, ','); + + if (!$CSVUsers) { + die("Failed to parse CSV file: $filename\n"); + } + + // Debug message + echo "Processing file: $filename with " . count($CSVUsers) . " users\n"; + + $fieldMapping = [ + 'username' => 'setUsername', + 'lastname' => 'setLastname', + 'firstname' => 'setFirstname', + 'email' => 'setEmail', + 'officialcode' => 'setOfficialCode', + 'phonenumber' => 'setPhone', + 'status' => 'setStatus', + 'expirydate' => 'setExpirationDate', + 'active' => 'setActive', + 'language' => 'setLocale', + 'password' => 'setPlainPassword' + ]; + + // Create new user accounts found in the CSV and update the existing ones, re-enabling if necessary foreach ($CSVUsers as $CSVuser) { - if (empty($CSVuser['username']) { - continue; - } - $username = $CSVuser['username']; - if (array_key_exists($username, $dbUsers)) { - $user = $dbUsers[$username]; - if ($debug) { - echo "User in DB = " . $username . " and user id = " . $user->getId() . "\n"; + + try { + + $CSVuser = array_change_key_case($CSVuser); // Convert keys to lowercase + + if (empty($CSVuser['username'])) { + echo "Skipping user with empty username\n"; + continue; } - } else { - if (!$test) { - $user = new User(); - $dbUsers[$username] = $user; - $user->setUsernameCanonical($username); + $username = strtolower($CSVuser['username']); + if (array_key_exists($username, $dbUsers)) { + $user = $dbUsers[$username]; + if ($debug) { + echo "User in DB = " . $username . " and user id = " . $user->getId() . "\n"; + } + } else { + if (!$test) { + $user = new User(); + $dbUsers[$username] = $user; + $user->setUsername($username); + $user->setUsernameCanonical($username); + } + if ($debug) { + echo 'Created ' . $username . "\n"; + echo "CSVUser = " . print_r($CSVuser, 1) . "\n"; + } } if ($debug) { - echo 'Created ' . $username . "\n"; - echo "CSVUser = " . print_r($CSVUser,1) . "\n"; + echo 'Updating ' . $username . ' fields ' . "\n"; } - } - if ($debug) { - echo 'Updating ' . $username . ' fields '."\n"; - } - if (!$test) { - foreach ($CSVuser as $fieldValue => $fieldName) { - // verify if it's an extra field or not (if it contains EXTRA_KEY at the begining of the name) - - // update every field and extra field of the user - - if (!$user->isActive() and $reenableUsersFoundInLDAP) { + + if (!$test) { + $passwordSet = false; + foreach ($CSVuser as $fieldName => $fieldValue) { + if (isset($fieldMapping[$fieldName])) { + $setter = $fieldMapping[$fieldName]; + if ($setter === 'setExpirationDate') { + $fieldValue = new DateTime($fieldValue); + } + if ($setter === 'setPlainPassword') { + $passwordSet = true; + } + if ($setter === 'setStatus') { + if (isset($statusList[$fieldValue])) { + $fieldValue = $statusList[$fieldValue]; + $user->setRoleFromStatus($fieldValue); + } else { + die("Status value '$fieldValue' not found in status list\n"); + } + } + if (method_exists($user, $setter)) { + $user->$setter($fieldValue); + } else { + die("Setter method '$setter' not found in User entity\n"); + } + } + } + + if (!$passwordSet) { + $user->setPlainPassword(api_generate_password()); + } + + if (!$user->isActive() && $reenableUsersFoundInCSV) { $user->setActive(true); } - Database::getManager()->persist($user); + + $user->setCreator($creator); + $userRepository->updateUser($user, true); + + foreach ($CSVuser as $fieldName => $fieldValue) { + if (strpos($fieldName, EXTRA_KEY) === 0) { + $extraFieldName = substr($fieldName, strlen(EXTRA_KEY)); + $extraField = $entityManager->getRepository(ExtraField::class)->findOneBy(['variable' => $extraFieldName]); + if ($extraField) { + $extraFieldValue = $entityManager->getRepository(ExtraFieldValues::class)->findOneBy(['field' => $extraField, 'itemId' => $user->getId()]); + if (!$extraFieldValue) { + $extraFieldValue = new ExtraFieldValues(); + $extraFieldValue->setField($extraField); + $extraFieldValue->setItemId($user->getId()); + } + $extraFieldValue->setFieldValue($fieldValue); + $entityManager->persist($extraFieldValue); + } else { + die("Extra field '$extraFieldName' not found in database\n"); + } + } + } + try { - Database::getManager()->flush(); - } catch (OptimisticLockException $exception) { - die($exception->getMessage()."\n"); + $entityManager->flush(); + } catch (OptimisticLockException $e) { + echo "Error processing user '{$username}': " . $e->getMessage() . "\n"; + error_log("Error processing user '{$username}': " . $e->getMessage()); + echo "Trace: " . $e->getTraceAsString() . "\n"; + continue; } - if($debug) { + + if ($debug) { echo 'Sent to DB ' . $username . " with user id = " . $user->getId() . "\n"; } UrlManager::add_user_to_url($user->getId(), $accessUrlId); } + + $allCSVUsers[$username] = $user; + + } catch (Exception $e) { + echo "Error processing user '{$username}': " . $e->getMessage() . "\n"; + error_log("Error processing user '{$username}': " . $e->getMessage()); + echo "Trace: " . $e->getTraceAsString() . "\n"; + continue; } - $allCSVUsers[$username] = $user; } } -// disable or delete user accounts not found in any CSV file depending on $deleteUsersNotFoundInLDAP - +// Disable or delete user accounts not found in any CSV file depending on $deleteUsersNotFoundInCSV $now = new DateTime(); foreach (array_diff(array_keys($dbUsers), array_keys($allCSVUsers)) as $usernameToDisable) { - if (in_array($usernameToDisable, $usernameListNotToTouchEvenIfNotInCSV)) { + if (isset($usernameListNotToTouchEvenIfNotInCSV) && in_array($usernameToDisable, $usernameListNotToTouchEvenIfNotInCSV)) { if ($debug) { - echo 'User not modified even if not present in LDAP : ' . $usernameToDisable . "\n"; + echo 'User not modified even if not present in CSV: ' . $usernameToDisable . "\n"; } } else { $user = $dbUsers[$usernameToDisable]; - if ($deleteUsersNotFoundInLDAP) { + if ($deleteUsersNotFoundInCSV) { if (!$test) { if (!UserManager::delete_user($user->getId())) { if ($debug) { echo 'Unable to delete user ' . $usernameToDisable . "\n"; } + } else { + if ($debug) { + echo 'Deleted user ' . $usernameToDisable . "\n"; + } + } + } else { + if ($debug) { + echo 'Test mode: User ' . $usernameToDisable . ' would have been deleted\n'; } } - if ($debug) { - echo 'Deleted user ' . $usernameToDisable . "\n"; - } } else { if (!$test) { if ($user->isActive()) { - // In order to avoid slow individual SQL updates, we do not call - // UserManager::disable($user->getId()); $user->setActive(false); - Database::getManager()->persist($user); - // In order to avoid slow individual SQL updates, we do not call - // Event::addEvent(LOG_USER_DISABLE, LOG_USER_ID, $user->getId()); + $entityManager->persist($user); + $trackEDefault = new TrackEDefault(); - $trackEDefault->setDefaultUserId(1); + $trackEDefault->setDefaultUserId($firstAdmin->getId()); $trackEDefault->setDefaultDate($now); $trackEDefault->setDefaultEventType(LOG_USER_DISABLE); $trackEDefault->setDefaultValueType(LOG_USER_ID); - $trackEDefault->setDefaultValue($user->getId()); - Database::getManager()->persist($trackEDefault); + $trackEDefault->setDefaultValue((string) $user->getId()); + $entityManager->persist($trackEDefault); + + try { + $entityManager->flush(); + } catch (OptimisticLockException $e) { + error_log("Error processing user " . $e->getMessage()); + echo "Trace: " . $e->getTraceAsString() . "\n"; + continue; + } + + if ($debug) { + echo 'Disabled user ' . $usernameToDisable . "\n"; + } + } else { + if ($debug) { + echo 'User ' . $usernameToDisable . ' is already disabled\n'; + } + } + } else { + if ($debug) { + echo 'Test mode: User ' . $usernameToDisable . ' would have been disabled\n'; } } - if ($debug) { - echo 'Disabled ' . $user->getUsername() . "\n"; - } - } + } } } + if (!$test) { try { - // Saving everything together - Database::getManager()->flush(); - } catch (OptimisticLockException $exception) { - die($exception->getMessage()."\n"); + $entityManager->flush(); + } catch (OptimisticLockException $e) { + error_log("Error processing user " . $e->getMessage()); + echo "Trace: " . $e->getTraceAsString() . "\n"; } } - -// anonymize user accounts disabled for more than 3 years +// Anonymize user accounts disabled for more than 3 years if ($anonymizeUserAccountsDisbaledFor3Years) { - $longDisabledUserIds = []; - foreach (Database::query( - 'select default_value - from track_e_default - where default_event_type=\'user_disable\' and default_value_type=\'user_id\' - group by default_value - having max(default_date) < date_sub(now(), interval 3 year)' - )->fetchAll(FetchMode::COLUMN) as $userId) { - $longDisabledUserIds[] = $userId; - } - $anonymizedUserIds = []; - foreach (Database::query( - 'select distinct default_value - from track_e_default - where default_event_type=\'user_anonymized\' and default_value_type=\'user_id\'' - )->fetchAll(FetchMode::COLUMN) as $userId) { - $anonymizedUserIds[] = $userId; - } + echo "Anonymizing user accounts disabled for more than 3 years\n"; + + $longDisabledUserIds = $entityManager->createQueryBuilder() + ->select('t.defaultValue') + ->from(TrackEDefault::class, 't') + ->where('t.defaultEventType = :eventType') + ->andWhere('t.defaultValueType = :valueType') + ->andWhere('t.defaultDate < :date') + ->groupBy('t.defaultValue') + ->setParameter('eventType', 'user_disable') + ->setParameter('valueType', 'user_id') + ->setParameter('date', (new DateTime())->modify('-3 years')) + ->getQuery() + ->getSingleColumnResult(); + + $anonymizedUserIds = $entityManager->createQueryBuilder() + ->select('t.defaultValue') + ->from(TrackEDefault::class, 't') + ->where('t.defaultEventType = :eventType') + ->andWhere('t.defaultValueType = :valueType') + ->distinct() + ->setParameter('eventType', 'user_anonymized') + ->setParameter('valueType', 'user_id') + ->getQuery() + ->getSingleColumnResult(); + foreach (array_diff($longDisabledUserIds, $anonymizedUserIds) as $userId) { $user = $userRepository->find($userId); if ($user && !$user->isEnabled()) { if (!$test) { try { - UserManager::anonymize($userId) - or die("could not anonymize user $userId\n"); + UserManager::anonymize($userId) or die("could not anonymize user $userId\n"); } catch (Exception $exception) { - die($exception->getMessage()."\n"); - } + die($exception->getMessage() . "\n"); + } if (isset($extraFieldToEmpty)) { - UserManager::update_extra_field_value($userId,$extraFieldToEmpty,''); + UserManager::update_extra_field_value($userId, $extraFieldToEmpty, ''); } } if ($debug) {