1: <?php
2:
3: namespace Aurora\System\Console\Commands;
4:
5: use Aurora\System\Console\Commands\BaseCommand;
6: use Symfony\Component\Console\Command\Command;
7: use Symfony\Component\Console\Input\InputInterface;
8: use Symfony\Component\Console\Input\InputOption;
9: use Symfony\Component\Console\Logger\ConsoleLogger;
10: use Symfony\Component\Console\Output\OutputInterface;
11: use Symfony\Component\Console\Question\ConfirmationQuestion;
12:
13: class GetOrphansCommand extends BaseCommand
14: {
15: /**
16: * @var ConsoleLogger
17: */
18: private $logger = false;
19:
20: /**
21: * @return void
22: */
23: public function __construct()
24: {
25: parent::__construct();
26: }
27:
28: protected function configure(): void
29: {
30: $this->setName('get-orphans')
31: ->setDescription('Collect orphan entries')
32: ->addOption('remove', 'r', InputOption::VALUE_NONE, 'Remove orphan entries from DB.')
33: ;
34: }
35:
36: protected function rewriteFile($fd, $str)
37: {
38: ftruncate($fd, 0);
39: fseek($fd, 0, SEEK_END);
40: fwrite($fd, $str);
41: }
42:
43: protected function jsonPretify($sJsonStr)
44: {
45: $sOutput = '{';
46: $bFirstElement = true;
47: foreach ($sJsonStr as $key => $value) {
48: if (!$bFirstElement) {
49: $sOutput .= ",";
50: }
51: $bFirstElement = false;
52:
53: $sOutput .= PHP_EOL . "\t\"" . $key . "\": [";
54: $sOutput .= PHP_EOL . "\t\t\"" . implode('","', $value) . "\"";
55: $sOutput .= PHP_EOL . "\t]";
56: }
57: $sOutput .= PHP_EOL . '}';
58: $sOutput = str_replace('\\', '\\\\', $sOutput);
59:
60: return $sOutput;
61: }
62:
63: protected function checkOrphans($fdEntities, $input, $output)
64: {
65: $helper = $this->getHelper('question');
66: $question = new ConfirmationQuestion('Remove these orphan entries? [yes]', true);
67:
68: $aOrphansEntities = [];
69: $aModels = $this->getAllModels();
70: foreach ($aModels as $modelName => $modelPath) {
71: $model = str_replace('/', DIRECTORY_SEPARATOR, $modelPath);
72: $model = str_replace('\\', DIRECTORY_SEPARATOR, $model);
73: $model = explode(DIRECTORY_SEPARATOR, $model);
74: $modelClass = [];
75:
76: while ($model[0] !== 'modules') {
77: array_shift($model);
78: }
79: $model[0] = 'Modules';
80: array_unshift($model, "Aurora");
81: $model = implode('\\', $model);
82:
83: $this->logger->info('Checking ' . $model::query()->getQuery()->from . ' table.');
84:
85: $modelObject = new $model();
86: $checkOrphan = $modelObject->getOrphanIds();
87: switch($checkOrphan['status']) {
88: case 0:
89: $this->logger->info($checkOrphan['message']);
90: break;
91: case 1:
92: $aOrphansEntities[$model] = array_values($checkOrphan['orphansIds']);
93: if ($input->getOption('remove') && !empty($aOrphansEntities[$model])) {
94: $this->logger->error($checkOrphan['message']);
95: $bRemove = $helper->ask($input, $output, $question);
96:
97: if ($bRemove) {
98: $modelObject::whereIn('id', $aOrphansEntities[$model])->delete();
99: $this->logger->warning('Orphan entries was removed.');
100: } else {
101: $this->logger->warning('Orphan entries removing was skipped.');
102: }
103: } else {
104: $this->logger->error($checkOrphan['message']);
105: }
106: break;
107: default:
108: $this->logger->info($checkOrphan['message']);
109: break;
110: }
111: echo PHP_EOL;
112: }
113: $this->rewriteFile($fdEntities, $this->jsonPretify($aOrphansEntities));
114: }
115:
116: protected function checkFileOrphans($fdEntities, $input, $output)
117: {
118: $helper = $this->getHelper('question');
119: $question = new ConfirmationQuestion('Remove files of the orphan users? [yes]', true);
120:
121: $dirFiles = \Aurora\System\Api::DataPath() . "/files";
122: $dirPersonalFiles = $dirFiles . "/private";
123: $dirOrphanFiles = $dirFiles . "/orphan_user_files";
124: $aOrphansEntities = [];
125:
126: if (is_dir($dirPersonalFiles)) {
127: $this->logger->info("Checking Personal files.");
128:
129: $dirs = array_diff(scandir($dirPersonalFiles), array('..', '.'));
130:
131: $users = new \Aurora\Modules\Core\Models\User();
132: $orphanUUIDs = array_values(array_diff($dirs, $users::pluck('UUID')->toArray()));
133:
134: $aOrphansEntities['PersonalFiles'] = $orphanUUIDs;
135: $this->rewriteFile($fdEntities, $this->jsonPretify($aOrphansEntities));
136:
137: if (!empty($orphanUUIDs)) {
138: $this->logger->error("Personal files orphans were found: " . count($orphanUUIDs));
139:
140: if ($input->getOption('remove') && !empty($orphanUUIDs)) {
141: $bRemove = $helper->ask($input, $output, $question);
142:
143: if ($bRemove) {
144: if (!is_dir($dirOrphanFiles)) {
145: mkdir($dirOrphanFiles);
146: }
147:
148: foreach ($orphanUUIDs as $orphanUUID) {
149: rename($dirPersonalFiles."/".$orphanUUID, $dirOrphanFiles."/".$orphanUUID);
150: }
151:
152: $this->logger->warning('Orphan user files were moved to ' . $dirOrphanFiles . '.');
153: } else {
154: $this->logger->warning('Orphan user files removing was skipped.');
155: }
156: }
157: } else {
158: $this->logger->info("Personal files orphans were not found.");
159: }
160: }
161: }
162:
163: protected function execute(InputInterface $input, OutputInterface $output): int
164: {
165: $verbosityLevelMap = array(
166: 'notice' => OutputInterface::VERBOSITY_NORMAL,
167: 'info' => OutputInterface::VERBOSITY_NORMAL
168: );
169: $dirName = \Aurora\System\Api::DataPath() . "/get-orphans-logs";
170: $entitiesFileName = $dirName . "/orphans_".date('Y-m-d_H-i-s').".json";
171:
172: $dirname = dirname($entitiesFileName);
173: if (!is_dir($dirname)) {
174: mkdir($dirname, 0755, true);
175: }
176:
177: $fdEntities = fopen($entitiesFileName, 'a+') or die("Can't create migration-progress.txt file");
178:
179: $this->logger = new ConsoleLogger($output, $verbosityLevelMap);
180: $this->checkOrphans($fdEntities, $input, $output);
181: $this->checkFileOrphans($fdEntities, $input, $output);
182: return Command::SUCCESS;
183: }
184: }
185: