Passed
Push — master ( a0cda2...d8602b )
by Yannick
09:17
created

ImportAccessUrlCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 7
rs 10
1
<?php
2
// src/CoreBundle/Command/ImportAccessUrlCommand.php
3
4
namespace Chamilo\CoreBundle\Command;
5
6
use Chamilo\CoreBundle\Entity\AccessUrl;
7
use Chamilo\CoreBundle\Entity\User;
8
use Chamilo\CoreBundle\Entity\ResourceType;
9
use Doctrine\ORM\EntityManagerInterface;
10
use PhpOffice\PhpSpreadsheet\IOFactory;
11
use Symfony\Component\Console\Command\Command;
12
use Symfony\Component\Console\Input\InputArgument;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\Console\Style\SymfonyStyle;
16
17
class ImportAccessUrlCommand extends Command
18
{
19
    protected static $defaultName = 'chamilo:import-access-url';
20
21
    private EntityManagerInterface $entityManager;
22
23
    public function __construct(EntityManagerInterface $entityManager)
24
    {
25
        parent::__construct();
26
        $this->entityManager = $entityManager;
27
    }
28
29
    protected function configure(): void
30
    {
31
        $this
32
            ->setDescription('Import AccessUrl entities from an XLSX file')
33
            ->addArgument('xlsx-file', InputArgument::REQUIRED, 'Path to the XLSX file')
34
            ->addArgument('base-url', InputArgument::REQUIRED, 'Base URL for subdomains (e.g., https://somedomain.com/)')
35
            ->setHelp('This command imports AccessUrl entities from an XLSX file. The file must have a title row with "subdomain" and "description" columns. Subdomains are lowercased. The ResourceNode parent is set to AccessUrl ID = 1.');
36
    }
37
38
    protected function execute(InputInterface $input, OutputInterface $output): int
39
    {
40
        $io = new SymfonyStyle($input, $output);
41
        $io->title('Importing AccessUrl Entities');
42
43
        $xlsxFile = $input->getArgument('xlsx-file');
44
        $baseUrl = $input->getArgument('base-url');
45
46
        // Validate base URL
47
        $parsedUrl = parse_url($baseUrl);
48
        if (!isset($parsedUrl['host']) || !isset($parsedUrl['scheme'])) {
49
            $io->error("Invalid base URL: {$baseUrl}. Must include scheme and host (e.g., https://somedomain.com/).");
50
            return Command::FAILURE;
51
        }
52
        $mainDomain = $parsedUrl['host']; // e.g., 'somedomain.com'
53
        $scheme = $parsedUrl['scheme']; // e.g., 'https'
54
55
        // Validate XLSX file
56
        if (!file_exists($xlsxFile) || !is_readable($xlsxFile)) {
57
            $io->error("XLSX file not found or not readable: {$xlsxFile}");
58
            return Command::FAILURE;
59
        }
60
61
        // Get default user (ID 1)
62
        $defaultUserId = 1;
63
        $user = $this->entityManager->getRepository(User::class)->find($defaultUserId);
64
        if (!$user) {
65
            $io->error("User with ID {$defaultUserId} not found. Cannot assign to URLs.");
66
            return Command::FAILURE;
67
        }
68
69
        // Get main AccessUrl (resource_node.parent = null) and its ResourceNode
70
        $mainAccessUrl = $this->entityManager->getRepository(AccessUrl::class)->findOneBy(['parent' => null]);
71
        if (!$mainAccessUrl) {
72
            $io->error("Main AccessUrl not found.");
73
            return Command::FAILURE;
74
        }
75
76
        // Get ResourceType for AccessUrl
77
        $resourceTypeRepo = $this->entityManager->getRepository(ResourceType::class);
78
        $resourceType = $resourceTypeRepo->findOneBy(['title' => 'urls']);
79
        if (!$resourceType) {
80
            $io->error("ResourceType 'urls' not found.");
81
            return Command::FAILURE;
82
        }
83
84
        try {
85
            // Load the Excel file
86
            $spreadsheet = IOFactory::load($xlsxFile);
87
            $worksheet = $spreadsheet->getActiveSheet();
88
            $rows = $worksheet->toArray();
89
90
            // Find column indices for 'subdomain' and 'description'
91
            $headerRow = array_shift($rows); // Assume first row is header
92
            $subdomainIndex = array_search('subdomain', $headerRow);
93
            $descriptionIndex = array_search('description', $headerRow);
94
95
            if ($subdomainIndex === false || $descriptionIndex === false) {
96
                $io->error("Columns 'subdomain' or 'description' not found in the Excel file.");
97
                return Command::FAILURE;
98
            }
99
100
            $createdCount = 0;
101
            $skippedCount = 0;
102
103
            foreach ($rows as $rowNumber => $row) {
104
                $subdomain = trim($row[$subdomainIndex] ?? ''); // 'subdomain' column
105
                $description = trim($row[$descriptionIndex] ?? ''); // 'description' column
106
107
                // Skip empty subdomain
108
                if (empty($subdomain)) {
109
                    $io->warning("Row " . ($rowNumber + 2) . ": Skipping due to empty subdomain.");
110
                    $skippedCount++;
111
                    continue;
112
                }
113
114
                // Lowercase and validate subdomain
115
                $subdomain = strtolower($subdomain);
116
                if (!preg_match('/^[a-z0-9][a-z0-9\-]*[a-z0-9]$/', $subdomain)) {
117
                    $io->warning("Row " . ($rowNumber + 2) . ": Invalid subdomain '$subdomain'. Skipping.");
118
                    $skippedCount++;
119
                    continue;
120
                }
121
122
                // Construct the subdomain URL
123
                $subdomainUrl = "{$scheme}://{$subdomain}.{$mainDomain}/";
124
125
                // Check if the URL already exists
126
                $existingUrl = $this->entityManager->getRepository(AccessUrl::class)->findOneBy(['url' => $subdomainUrl]);
127
                if ($existingUrl) {
128
                    $io->warning("Row " . ($rowNumber + 2) . ": URL {$subdomainUrl} already exists. Skipping.");
129
                    $skippedCount++;
130
                    continue;
131
                }
132
133
                // Create new AccessUrl entity
134
                $accessUrl = new AccessUrl();
135
                $accessUrl->setUrl($subdomainUrl);
136
                $accessUrl->setDescription($description);
137
                $accessUrl->setActive(1); // Set as active
138
                $accessUrl->setCreatedBy($defaultUserId); // Set created_by
139
                $accessUrl->setTms(new \DateTime()); // Set timestamp
140
                //$accessUrl->setResourceNode($resourceNode); // Assign ResourceNode
141
                $accessUrl->addUser($user); // Associate user
142
                $accessUrl->setCreator($user); // Temporary hack as AccessUrl should be able to set this automatically
143
                $accessUrl->setParent($mainAccessUrl); // Set this URL as a child of the admin URL
144
145
                // Persist entity
146
                $this->entityManager->persist($accessUrl);
147
148
                $io->success("Row " . ($rowNumber + 2) . ": Created URL: {$subdomainUrl} with description: {$description}, assigned to user ID: {$defaultUserId}, parent AccessUrl ID: 1");
149
                $createdCount++;
150
            }
151
152
            // Save changes
153
            $this->entityManager->flush();
154
            $io->success("Import completed: {$createdCount} URLs created, {$skippedCount} rows skipped.");
155
156
            return Command::SUCCESS;
157
        } catch (\Exception $e) {
158
            $io->error("Error: " . $e->getMessage());
159
            return Command::FAILURE;
160
        }
161
    }
162
}
163