/home/ivoiecob/email.hirewise-va.com/system/Console/Commands/OrphansCommand.php
<?php
namespace Aurora\System\Console\Commands;
use Aurora\System\Api;
use Aurora\System\Console\Commands\BaseCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Illuminate\Database\Capsule\Manager as Capsule;
use Psr\Log\LogLevel;
class OrphansCommand extends BaseCommand
{
/**
* @var ConsoleLogger
*/
private $logger = false;
/**
* @return void
*/
public function __construct()
{
parent::__construct();
}
protected function configure(): void
{
$this->setName('orphans')
->setDescription('Collect orphan entries')
->addOption('remove', 'r', InputOption::VALUE_NONE, 'Remove orphan entries from DB.')
;
}
protected function rewriteFile($fd, $str)
{
ftruncate($fd, 0);
fseek($fd, 0, SEEK_END);
fwrite($fd, $str);
}
protected function jsonPretify($sJsonStr)
{
$sOutput = '{';
$bFirstElement = true;
foreach ($sJsonStr as $key => $value) {
if (!$bFirstElement) {
$sOutput .= ",";
}
$bFirstElement = false;
$sOutput .= PHP_EOL . "\t\"" . $key . "\": [";
$sOutput .= PHP_EOL . "\t\t\"" . implode('","', $value) . "\"";
$sOutput .= PHP_EOL . "\t]";
}
$sOutput .= PHP_EOL . '}';
$sOutput = str_replace('\\', '\\\\', $sOutput);
return $sOutput;
}
protected function checkOrphans($fdEntities, $input, $output)
{
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Remove these orphan entries? [yes]', true);
$aOrphansEntities = [];
$aModels = $this->getAllModels();
foreach ($aModels as $moduleName => $moduleModels) {
foreach ($moduleModels as $modelPath) {
$model = str_replace('/', DIRECTORY_SEPARATOR, $modelPath);
$model = str_replace('\\', DIRECTORY_SEPARATOR, $model);
$model = explode(DIRECTORY_SEPARATOR, $model);
while ($model[0] !== 'modules') {
array_shift($model);
}
$model[0] = 'Modules';
array_unshift($model, "Aurora");
$model = implode('\\', $model);
$modelObject = new $model();
$primaryKey = $modelObject->getKeyName(); // get primary key column name
// This block is required for the work with custom connection to DB which is defined in MtaConnector module
$sConnectionName = $modelObject->getConnectionName();
if ($sConnectionName) {
$sModuleClassName = '\\Aurora\\Modules\\' . $moduleName . '\\Module';
$sModelPath = Api::GetModuleManager()->GetModulePath($moduleName);
$oModule = new $sModuleClassName($sModelPath);
$oModule->addDbConnection();
}
$this->logger->info('Checking ' . $model::query()->getQuery()->from . ' table.');
$checkOrphan = $modelObject->getOrphanIds();
switch($checkOrphan['status']) {
case 0:
$this->logger->info($checkOrphan['message']);
break;
case 1:
$aOrphansEntities[$model] = array_values($checkOrphan['orphansIds']);
sort($aOrphansEntities[$model]);
if ($input->getOption('remove') && !empty($aOrphansEntities[$model])) {
$this->logger->error($checkOrphan['message']);
$bRemove = $helper->ask($input, $output, $question);
if ($bRemove) {
$modelObject::whereIn($primaryKey, $aOrphansEntities[$model])->delete();
$this->logger->warning('Orphan entries was removed.');
} else {
$this->logger->warning('Orphan entries removing was skipped.');
}
} else {
$this->logger->error($checkOrphan['message']);
}
break;
default:
$this->logger->info($checkOrphan['message']);
break;
}
echo PHP_EOL;
}
}
return $aOrphansEntities;
}
protected function checkFileOrphans($fdEntities, $input, $output)
{
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Remove files of the orphan users? [yes]', true);
$dirFiles = \Aurora\System\Api::DataPath() . "/files";
$dirPersonalFiles = $dirFiles . "/private";
$dirOrphanFiles = $dirFiles . "/orphan_user_files";
$aOrphansEntities = [];
if (is_dir($dirPersonalFiles)) {
$this->logger->info("Checking Personal files.");
$dirs = array_diff(scandir($dirPersonalFiles), array('..', '.'));
$orphanUUIDs = array_values(array_diff($dirs, \Aurora\Modules\Core\Models\User::query()->pluck('UUID')->toArray()));
if (!empty($orphanUUIDs)) {
$aOrphansEntities['PersonalFiles'] = $orphanUUIDs;
$this->logger->error("Personal files orphans were found: " . count($orphanUUIDs));
if ($input->getOption('remove')) {
$bRemove = $helper->ask($input, $output, $question);
if ($bRemove) {
if (!is_dir($dirOrphanFiles)) {
mkdir($dirOrphanFiles);
}
foreach ($orphanUUIDs as $orphanUUID) {
rename($dirPersonalFiles . "/" . $orphanUUID, $dirOrphanFiles . "/" . $orphanUUID);
}
$this->logger->warning('Orphan user files were moved to ' . $dirOrphanFiles . '.');
} else {
$this->logger->warning('Orphan user files removing was skipped.');
}
}
} else {
$this->logger->info("Personal files orphans were not found.");
}
}
return $aOrphansEntities;
}
protected function checkDavOrphans($fdEntities, $input, $output)
{
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Remove DAV objects of the orphan users? [yes]', true);
$dbPrefix = Api::GetSettings()->DBPrefix;
$aOrphansEntities = [];
if (Capsule::schema()->hasTable('adav_calendarinstances')) {
echo PHP_EOL;
$this->logger->info("Checking DAV calendar.");
$rows = Capsule::connection()->select('SELECT aci.calendarid, aci.id FROM ' . $dbPrefix . 'adav_calendarinstances as aci
WHERE SUBSTRING(principaluri, 12) NOT IN (SELECT PublicId FROM ' . $dbPrefix . 'core_users) AND principaluri NOT LIKE \'%_dav_tenant_user@%\'');
if (count($rows) > 0) {
$this->logger->error("DAV calendars orphans were found: " . count($rows));
$aOrphansEntities['DAV-Calendars'] = array_map(function ($row) {
return $row->id;
}, $rows);
if ($input->getOption('remove')) {
$bRemove = $helper->ask($input, $output, $question);
if ($bRemove) {
foreach ($rows as $row) {
\Afterlogic\DAV\Backend::Caldav()->deleteCalendar([$row->calendarid, $row->id]);
}
}
}
} else {
$this->logger->info("DAV calendars orphans were not found.");
}
}
if (Capsule::schema()->hasTable('adav_addressbooks')) {
echo PHP_EOL;
$this->logger->info("Checking DAV addressbooks.");
$rows = Capsule::connection()->select('SELECT id FROM ' . $dbPrefix . 'adav_addressbooks WHERE (SUBSTRING(principaluri, 12) NOT IN (SELECT PublicId FROM ' . $dbPrefix . 'core_users) AND principaluri NOT LIKE \'%_dav_tenant_user@%\') OR ISNULL(principaluri)');
if (count($rows) > 0) {
$this->logger->error("DAV addressbooks orphans were found: " . count($rows));
$aOrphansEntities['DAV-Addressbooks'] = array_map(function ($row) {
return $row->id;
}, $rows);
if ($input->getOption('remove')) {
$bRemove = $helper->ask($input, $output, $question);
if ($bRemove) {
foreach ($rows as $row) {
\Afterlogic\DAV\Backend::Carddav()->deleteAddressBook($row->id);
}
}
}
} else {
$this->logger->info("DAV addressbooks orphans were not found.");
}
}
return $aOrphansEntities;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$verbosityLevelMap = [
LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL,
LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL,
];
$dirName = \Aurora\System\Logger::GetLogFileDir() . "/orphans-logs";
$entitiesFileName = $dirName . "/orphans_" . date('Y-m-d_H-i-s') . ".json";
$orphansEntities = [];
$dirname = dirname($entitiesFileName);
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
$fdEntities = fopen($entitiesFileName, 'a+') or die("Can't create migration-progress.txt file");
$this->logger = new ConsoleLogger($output, $verbosityLevelMap);
$orphansEntities = array_merge(
$orphansEntities,
$this->checkOrphans($fdEntities, $input, $output)
);
$orphansEntities = array_merge(
$orphansEntities,
$this->checkFileOrphans($fdEntities, $input, $output)
);
$orphansEntities = array_merge(
$orphansEntities,
$this->checkDavOrphans($fdEntities, $input, $output)
);
if (count($orphansEntities) > 0) {
$this->rewriteFile($fdEntities, $this->jsonPretify($orphansEntities));
}
return Command::SUCCESS;
}
}