Exponential 5.x Kernel 7.5.40 Exponential New Stack (Symfony-based) Kernel — PHP Class Reference

UpdateTimestampsToUTCCommand.php
Go to the documentation of this file.
1 <?php
2
7 namespace eZ\Bundle\EzPublishCoreBundle\Command;
8
9 use DateTime;
10 use DateTimeZone;
11 use Doctrine\DBAL\Connection;
12 use RuntimeException;
13 use Symfony\Component\Process\Process;
14 use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
15 use Symfony\Component\Console\Input\InputInterface;
16 use Symfony\Component\Console\Output\OutputInterface;
17 use Symfony\Component\Console\Input\InputArgument;
18 use Symfony\Component\Console\Input\InputOption;
19 use Symfony\Component\Console\Helper\ProgressBar;
20 use Symfony\Component\Console\Question\ConfirmationQuestion;
21 use Symfony\Component\Process\PhpExecutableFinder;
22 use PDO;
23
24 class UpdateTimestampsToUTCCommand extends ContainerAwareCommand
25 {
26 const MAX_TIMESTAMP_VALUE = 2147483647;
27 const DEFAULT_ITERATION_COUNT = 100;
28 const MODES = [
29 'date' => ['ezdate'],
30 'datetime' => ['ezdatetime'],
31 'all' => ['ezdate', 'ezdatetime'],
32 ];
33
35 protected $done = 0;
36
38 protected $timezone;
39
41 private $mode;
42
44 private $from;
45
47 private $to;
48
50 private $connection;
51
53 private $phpPath;
54
56 private $dryRun;
57
58 protected function configure()
59 {
60 $this
61 ->setName('ezplatform:timestamps:to-utc')
62 ->setDescription('Updates ezdate & ezdatetime timestamps to UTC')
63 ->addArgument(
64 'timezone',
65 InputArgument::OPTIONAL,
66 'Original timestamp\'s TimeZone',
67 null
68 )
69 ->addOption(
70 'dry-run',
71 null,
72 InputOption::VALUE_NONE,
73 'Execute a dry run'
74 )
75 ->addOption(
76 'mode',
77 null,
78 InputOption::VALUE_REQUIRED,
79 'Select conversion scope: date, datetime, all',
80 'all'
81 )
82 ->addOption(
83 'from',
84 null,
85 InputOption::VALUE_REQUIRED,
86 'Only versions AFTER this date will be converted',
87 null
88 )
89 ->addOption(
90 'to',
91 null,
92 InputOption::VALUE_REQUIRED,
93 'Only versions BEFORE this date will be converted',
94 null
95 )
96 ->addOption(
97 'offset',
98 null,
99 InputArgument::OPTIONAL,
100 'Offset for updating records',
101 0
102 )
103 ->addOption(
104 'iteration-count',
105 null,
106 InputArgument::OPTIONAL,
107 'Limit how much records get updated by single process',
108 self::DEFAULT_ITERATION_COUNT
109 )
110 ->setHelp(
111 <<<'EOT'
112 The command <info>%command.name%</info> updates field
113 data_int in configured Legacy Storage database for a given field type.
114
115 This is to be used either when migrating from eZ Publish to eZ Platform
116 (when using platform backend instead of legacy), or when upgrading legacy
117 to v2019.03 which has been adapted to use UTC.
118
119 <warning>During the script execution the database should not be modified.
120
121 You are advised to create a backup or execute a dry run before
122 proceeding with actual update.</warning>
123
124 <warning>This command should be only ran ONCE.</warning>
125
126 Since this script can potentially run for a very long time, to avoid memory
127 exhaustion run it in production environment using <info>--env=prod</info> switch.
128 EOT
129 );
130 }
131
132 protected function initialize(InputInterface $input, OutputInterface $output)
133 {
134 /* @var \Doctrine\DBAL\Connection $databaseHandler */
135 $this->connection = $this->getContainer()->get('ezpublish.api.search_engine.legacy.connection');
136 }
137
142 protected function execute(InputInterface $input, OutputInterface $output)
143 {
144 $iterationCount = (int) $input->getOption('iteration-count');
145 $this->dryRun = $input->getOption('dry-run');
146 $this->mode = $input->getOption('mode');
147
148 if (!array_key_exists($this->mode, self::MODES)) {
149 $output->writeln(
150 sprintf('Selected mode is not supported, please use one of: %s', implode(', ', array_keys(self::MODES)))
151 );
152
153 return;
154 }
155
156 $from = $input->getOption('from');
157 $to = $input->getOption('to');
158
159 if ($from && !$this->validateDateTimeString($from, $output)) {
160 return;
161 }
162 if ($to && !$this->validateDateTimeString($to, $output)) {
163 return;
164 }
165 if ($from) {
166 $this->from = $this->dateStringToTimestamp($from);
167 }
168 if ($to) {
169 $this->to = $this->dateStringToTimestamp($to);
170 }
171
172 $consoleScript = $_SERVER['argv'][0];
173
174 if (getenv('INNER_CALL')) {
175 $this->timezone = $input->getArgument('timezone');
176 $this->processTimestamps((int) $input->getOption('offset'), $iterationCount, $output);
177 $output->writeln($this->done);
178 } else {
179 $timezone = $input->getArgument('timezone');
180 $this->timezone = $this->validateTimezone($timezone, $output);
181
182 $output->writeln([
183 sprintf('Converting timestamps for fields: %s', implode(', ', self::MODES[$this->mode])),
184 'Calculating number of Field values to update...',
185 ]);
186 $count = $this->countTimestampBasedFields();
187 $output->writeln([
188 sprintf('Found total of Field values for update: %d', $count),
189 '',
190 ]);
191
192 if ($count == 0) {
193 $output->writeln('Nothing to process, exiting.');
194
195 return;
196 }
197
198 $helper = $this->getHelper('question');
199 $question = new ConfirmationQuestion(
200 '<question>Are you sure you want to proceed?</question> ',
201 false
202 );
203
204 if (!$helper->ask($input, $output, $question)) {
205 $output->writeln('');
206
207 return;
208 }
209
210 $progressBar = $this->getProgressBar($count, $output);
211 $progressBar->start();
212
213 for ($offset = 0; $offset < $count; $offset += $iterationCount) {
214 $processScriptFragments = [
215 $this->getPhpPath(),
216 $consoleScript,
217 $this->getName(),
218 $this->timezone,
219 '--mode=' . $this->mode,
220 '--offset=' . $offset,
221 '--iteration-count=' . $iterationCount,
222 ];
223
224 if ($from) {
225 $processScriptFragments[] = '--from=' . $from;
226 }
227 if ($to) {
228 $processScriptFragments[] = '--to=' . $to;
229 }
230 if ($this->dryRun) {
231 $processScriptFragments[] = '--dry-run';
232 }
233
234 $process = new Process(
235 implode(' ', $processScriptFragments)
236 );
237
238 $process->setEnv(['INNER_CALL' => 1]);
239 $process->run();
240
241 if (!$process->isSuccessful()) {
242 throw new RuntimeException($process->getErrorOutput());
243 }
244
245 $doneInProcess = (int) $process->getOutput();
246 $this->done += $doneInProcess;
247
248 $progressBar->advance($doneInProcess);
249 }
250
251 $progressBar->finish();
252 $output->writeln([
253 '',
254 sprintf('Done: %d', $this->done),
255 ]);
256 }
257 }
258
264 protected function processTimestamps($offset, $limit, $output)
265 {
266 $timestampBasedFields = $this->getTimestampBasedFields($offset, $limit);
267
268 $dateTimeInUTC = new DateTime();
269 $dateTimeInUTC->setTimezone(new DateTimeZone('UTC'));
270
271 foreach ($timestampBasedFields as $timestampBasedField) {
272 $timestamp = $timestampBasedField['data_int'];
273 $dateTimeInUTC->setTimestamp($timestamp);
274 $newTimestamp = $this->convertToUtcTimestamp($timestamp);
275
276 //failsafe for int field limitation (dates/datetimes after 01/19/2038 @ 4:14am (UTC))
277 if ($newTimestamp <= self::MAX_TIMESTAMP_VALUE && !$this->dryRun) {
278 $this->updateTimestampToUTC($timestampBasedField['id'], $timestampBasedField['version'], $newTimestamp);
279 }
280 ++$this->done;
281 }
282 }
283
290 protected function getTimestampBasedFields($offset, $limit)
291 {
292 $query = $this->connection->createQueryBuilder();
293 $query
294 ->select('a.id, a.version, a.data_int')
295 ->from('ezcontentobject_attribute', 'a')
296 ->join('a', 'ezcontentobject_version', 'v', 'a.contentobject_id = v.contentobject_id')
297 ->where(
298 $query->expr()->in(
299 'a.data_type_string',
300 $query->createNamedParameter(self::MODES[$this->mode], Connection::PARAM_STR_ARRAY)
301 )
302 )
303 ->andWhere('a.data_int is not null')
304 ->andWhere('a.data_int > 0')
305 ->andWhere('v.version = a.version')
306 ->setFirstResult($offset)
307 ->setMaxResults($limit);
308
309 if ($this->from) {
310 $query
311 ->andWhere('v.modified >= :fromTimestamp')
312 ->setParameter('fromTimestamp', $this->from);
313 }
314 if ($this->to) {
315 $query
316 ->andWhere('v.modified <= :toTimestamp')
317 ->setParameter('toTimestamp', $this->to);
318 }
319
320 $statement = $query->execute();
321
322 return $statement->fetchAll(PDO::FETCH_ASSOC);
323 }
324
330 protected function countTimestampBasedFields()
331 {
332 $query = $this->connection->createQueryBuilder();
333 $query
334 ->select('count(*) as count')
335 ->from('ezcontentobject_attribute', 'a')
336 ->join('a', 'ezcontentobject_version', 'v', 'a.contentobject_id = v.contentobject_id')
337 ->where(
338 $query->expr()->in(
339 'a.data_type_string',
340 $query->createNamedParameter(self::MODES[$this->mode], Connection::PARAM_STR_ARRAY)
341 )
342 )
343 ->andWhere('a.data_int is not null')
344 ->andWhere('a.data_int > 0')
345 ->andWhere('v.version = a.version');
346
347 if ($this->from) {
348 $query
349 ->andWhere('v.modified >= :fromTimestamp')
350 ->setParameter('fromTimestamp', $this->from);
351 }
352 if ($this->to) {
353 $query
354 ->andWhere('v.modified <= :toTimestamp')
355 ->setParameter('toTimestamp', $this->to);
356 }
357
358 $statement = $query->execute();
359
360 return (int) $statement->fetchColumn();
361 }
362
368 protected function convertToUtcTimestamp($timestamp)
369 {
370 $dateTimeZone = new DateTimeZone($this->timezone);
371 $dateTimeZoneUTC = new DateTimeZone('UTC');
372
373 $dateTime = new DateTime(null, $dateTimeZone);
374 $dateTime->setTimestamp($timestamp);
375 $dateTimeUTC = new DateTime($dateTime->format('Y-m-d H:i:s'), $dateTimeZoneUTC);
376
377 return $dateTimeUTC->getTimestamp();
378 }
379
386 protected function validateDateTimeString($dateTimeString, OutputInterface $output)
387 {
388 try {
389 new \DateTime($dateTimeString);
390 } catch (\Exception $exception) {
391 $output->writeln('The --from and --to options must be a valid Date string.');
392
393 return false;
394 }
395
396 return true;
397 }
398
405 protected function validateTimezone($timezone, OutputInterface $output)
406 {
407 if (!$timezone) {
408 $timezone = date_default_timezone_get();
409 $output->writeln([
410 sprintf('No Timezone set, using server Timezone: %s', $timezone),
411 '',
412 ]);
413 } else {
414 if (!\in_array($timezone, timezone_identifiers_list())) {
415 $output->writeln([
416 sprintf('% is not correct Timezone.', $timezone),
417 '',
418 ]);
419
420 return;
421 }
422
423 $output->writeln([
424 sprintf('Using timezone: %s', $timezone),
425 '',
426 ]);
427 }
428
429 return $timezone;
430 }
431
440 protected function getProgressBar($maxSteps, OutputInterface $output)
441 {
442 $progressBar = new ProgressBar($output, $maxSteps);
443 $progressBar->setFormat(
444 ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%'
445 );
446
447 return $progressBar;
448 }
449
455 protected function updateTimestampToUTC(
456 $contentAttributeId,
457 $contentAttributeVersion,
458 $newTimestamp
459 ) {
460 $query = $this->connection->createQueryBuilder();
461 $query
462 ->update('ezcontentobject_attribute', 'a')
463 ->set('a.data_int', $newTimestamp)
464 ->set('a.sort_key_int', $newTimestamp)
465 ->where('a.id = :id')
466 ->andWhere('a.version = :version')
467 ->setParameter(':id', $contentAttributeId)
468 ->setParameter(':version', $contentAttributeVersion);
469
470 $query->execute();
471 }
472
476 private function getPhpPath()
477 {
478 if ($this->phpPath) {
479 return $this->phpPath;
480 }
481 $phpFinder = new PhpExecutableFinder();
482 $this->phpPath = $phpFinder->find();
483 if (!$this->phpPath) {
484 throw new RuntimeException(
485 'The php executable could not be found, it\'s needed for executing parable sub processes, so add it to your PATH environment variable and try again'
486 );
487 }
488
489 return $this->phpPath;
490 }
491
499 private function dateStringToTimestamp($dateString)
500 {
501 $date = new \DateTime($dateString);
502
503 return $date->getTimestamp();
504 }
505 }
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand
Definition: UpdateTimestampsToUTCCommand.php:25
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\getProgressBar
getProgressBar($maxSteps, OutputInterface $output)
Return configured progress bar helper.
Definition: UpdateTimestampsToUTCCommand.php:440
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\getTimestampBasedFields
getTimestampBasedFields($offset, $limit)
Definition: UpdateTimestampsToUTCCommand.php:290
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\MAX_TIMESTAMP_VALUE
const MAX_TIMESTAMP_VALUE
Definition: UpdateTimestampsToUTCCommand.php:26
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\MODES
const MODES
Definition: UpdateTimestampsToUTCCommand.php:28
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\processTimestamps
processTimestamps($offset, $limit, $output)
Definition: UpdateTimestampsToUTCCommand.php:264
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\initialize
initialize(InputInterface $input, OutputInterface $output)
Definition: UpdateTimestampsToUTCCommand.php:132
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\$phpPath
$phpPath
Definition: UpdateTimestampsToUTCCommand.php:53
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\execute
execute(InputInterface $input, OutputInterface $output)
Definition: UpdateTimestampsToUTCCommand.php:142
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\updateTimestampToUTC
updateTimestampToUTC( $contentAttributeId, $contentAttributeVersion, $newTimestamp)
Definition: UpdateTimestampsToUTCCommand.php:455
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\$done
$done
Definition: UpdateTimestampsToUTCCommand.php:35
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\countTimestampBasedFields
countTimestampBasedFields()
Counts affected timestamp based fields using captured "mode", "from" and "to" command options.
Definition: UpdateTimestampsToUTCCommand.php:330
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\$to
$to
Definition: UpdateTimestampsToUTCCommand.php:47
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\dateStringToTimestamp
dateStringToTimestamp($dateString)
Definition: UpdateTimestampsToUTCCommand.php:499
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\validateTimezone
validateTimezone($timezone, OutputInterface $output)
Definition: UpdateTimestampsToUTCCommand.php:405
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\$connection
$connection
Definition: UpdateTimestampsToUTCCommand.php:50
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\$mode
$mode
Definition: UpdateTimestampsToUTCCommand.php:41
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\convertToUtcTimestamp
convertToUtcTimestamp($timestamp)
Definition: UpdateTimestampsToUTCCommand.php:368
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\$timezone
$timezone
Definition: UpdateTimestampsToUTCCommand.php:38
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\$from
$from
Definition: UpdateTimestampsToUTCCommand.php:44
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\DEFAULT_ITERATION_COUNT
const DEFAULT_ITERATION_COUNT
Definition: UpdateTimestampsToUTCCommand.php:27
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\$dryRun
$dryRun
Definition: UpdateTimestampsToUTCCommand.php:56
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\configure
configure()
Definition: UpdateTimestampsToUTCCommand.php:58
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\getPhpPath
getPhpPath()
Definition: UpdateTimestampsToUTCCommand.php:476
eZ\Bundle\EzPublishCoreBundle\Command\UpdateTimestampsToUTCCommand\validateDateTimeString
validateDateTimeString($dateTimeString, OutputInterface $output)
Definition: UpdateTimestampsToUTCCommand.php:386
eZ\Bundle\EzPublishCoreBundle\Command
Definition: CheckURLsCommand.php:7
Copyright © 1998 – 2026 7x. All rights reserved. Licensed under the GNU GPLv2+.