TYPO3 CMS  TYPO3_8-7
FrontendUserImageUpdateWizard.php
Go to the documentation of this file.
1 <?php
3 
27 
33 {
34 
39  const RECORDS_PER_QUERY = 1000;
40 
44  protected $title = 'Migrate all file relations from fe_users.image to sys_file_references';
45 
49  protected $storage;
50 
54  protected $logger;
55 
61  protected $table = 'fe_users';
62 
68  protected $fieldToMigrate = 'image';
69 
75  protected $sourcePath = 'uploads/pics/';
76 
83  protected $targetPath = '_migrated/frontend_users/';
84 
88  protected $registry;
89 
93  protected $registryNamespace = 'FrontendUserImageUpdateWizard';
94 
98  protected $recordOffset;
99 
103  public function __construct()
104  {
105  $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
106  }
107 
111  public function init()
112  {
113  $storages = GeneralUtility::makeInstance(StorageRepository::class)->findAll();
114  $this->storage = $storages[0];
115  $this->registry = GeneralUtility::makeInstance(Registry::class);
116  $this->recordOffset = $this->registry->get($this->registryNamespace, 'recordOffset');
117  }
118 
126  public function checkForUpdate(&$description)
127  {
128  if ($this->isWizardDone()) {
129  return false;
130  }
131 
132  $description = 'This update wizard goes through all files that are referenced in the fe_users.image field'
133  . ' and adds the files to the FAL File Index.<br />'
134  . 'It also moves the files from uploads/ to the fileadmin/_migrated/ path.';
135 
136  $this->init();
137 
138  return $this->recordOffset !== [];
139  }
140 
148  public function performUpdate(array &$dbQueries, &$customMessage)
149  {
150  $customMessage = '';
151  try {
152  $this->init();
153 
154  if (!isset($this->recordOffset[$this->table])) {
155  $this->recordOffset[$this->table] = 0;
156  }
157 
158  do {
159  $limit = $this->recordOffset[$this->table] . ',' . self::RECORDS_PER_QUERY;
160  $records = $this->getRecordsFromTable($limit, $dbQueries);
161  foreach ($records as $record) {
162  $this->migrateField($record, $customMessage, $dbQueries);
163  }
164  $this->registry->set($this->registryNamespace, 'recordOffset', $this->recordOffset);
165  } while (count($records) === self::RECORDS_PER_QUERY);
166 
167  $this->registry->remove($this->registryNamespace, 'recordOffset');
168  if (empty($customMessage)) {
169  $this->markWizardAsDone();
170  }
171  } catch (\Exception $e) {
172  $customMessage .= PHP_EOL . $e->getMessage();
173  }
174 
175  return empty($customMessage);
176  }
177 
188  protected function getRecordsFromTable($limit, &$dbQueries)
189  {
190  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
191  $queryBuilder = $connectionPool->getQueryBuilderForTable($this->table);
192 
193  $queryBuilder->getRestrictions()
194  ->removeAll()
195  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
196 
197  try {
198  $result = $queryBuilder
199  ->select('uid', 'pid', $this->fieldToMigrate)
200  ->from($this->table)
201  ->where(
202  $queryBuilder->expr()->isNotNull($this->fieldToMigrate),
203  $queryBuilder->expr()->neq(
204  $this->fieldToMigrate,
205  $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
206  ),
207  $queryBuilder->expr()->comparison(
208  'CAST(CAST(' . $queryBuilder->quoteIdentifier($this->fieldToMigrate) . ' AS DECIMAL) AS CHAR)',
210  'CAST(' . $queryBuilder->quoteIdentifier($this->fieldToMigrate) . ' AS CHAR)'
211  )
212  )
213  ->orderBy('uid')
214  ->setFirstResult($limit)
215  ->execute();
216 
217  $dbQueries[] = $queryBuilder->getSQL();
218 
219  return $result->fetchAll();
220  } catch (DBALException $e) {
221  throw new \RuntimeException(
222  'Database query failed. Error was: ' . $e->getPrevious()->getMessage(),
223  1476050084
224  );
225  }
226  }
227 
237  protected function migrateField($row, &$customMessage, &$dbQueries)
238  {
239  $fieldItems = GeneralUtility::trimExplode(',', $row[$this->fieldToMigrate], true);
240  if (empty($fieldItems) || is_numeric($row[$this->fieldToMigrate])) {
241  return;
242  }
243  $fileadminDirectory = rtrim($GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'], '/') . '/';
244  $i = 0;
245 
246  if (!PATH_site) {
247  throw new \Exception('PATH_site was undefined.', 1476107387);
248  }
249 
250  $storageUid = (int)$this->storage->getUid();
251 
252  $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
253 
254  foreach ($fieldItems as $item) {
255  $fileUid = null;
256  $sourcePath = PATH_site . $this->sourcePath . $item;
257  $targetDirectory = PATH_site . $fileadminDirectory . $this->targetPath;
258  $targetPath = $targetDirectory . basename($item);
259 
260  // maybe the file was already moved, so check if the original file still exists
261  if (file_exists($sourcePath)) {
262  if (!is_dir($targetDirectory)) {
263  GeneralUtility::mkdir_deep($targetDirectory);
264  }
265 
266  // see if the file already exists in the storage
267  $fileSha1 = sha1_file($sourcePath);
268 
269  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file');
270  $queryBuilder->getRestrictions()->removeAll();
271  $existingFileRecord = $queryBuilder->select('uid')->from('sys_file')->where(
272  $queryBuilder->expr()->eq(
273  'sha1',
274  $queryBuilder->createNamedParameter($fileSha1, \PDO::PARAM_STR)
275  ),
276  $queryBuilder->expr()->eq(
277  'storage',
278  $queryBuilder->createNamedParameter($storageUid, \PDO::PARAM_INT)
279  )
280  )->execute()->fetch();
281 
282  // the file exists, the file does not have to be moved again
283  if (is_array($existingFileRecord)) {
284  $fileUid = $existingFileRecord['uid'];
285  } else {
286  // just move the file (no duplicate)
287  rename($sourcePath, $targetPath);
288  }
289  }
290 
291  if ($fileUid === null) {
292  // get the File object if it hasn't been fetched before
293  try {
294  // if the source file does not exist, we should just continue, but leave a message in the docs;
295  // ideally, the user would be informed after the update as well.
297  $file = $this->storage->getFile($this->targetPath . $item);
298  $fileUid = $file->getUid();
299  } catch (\InvalidArgumentException $e) {
300 
301  // no file found, no reference can be set
302  $this->logger->notice(
303  'File ' . $this->sourcePath . $item . ' does not exist. Reference was not migrated.',
304  [
305  'table' => $this->table,
306  'record' => $row,
307  'field' => $this->fieldToMigrate,
308  ]
309  );
310 
311  $format = 'File \'%s\' does not exist. Referencing field: %s.%d.%s. The reference was not migrated.';
312  $message = sprintf(
313  $format,
314  $this->sourcePath . $item,
315  $this->table,
316  $row['uid'],
317  $this->fieldToMigrate
318  );
319  $customMessage .= PHP_EOL . $message;
320  continue;
321  }
322  }
323 
324  if ($fileUid > 0) {
325  $fields = [
326  'fieldname' => $this->fieldToMigrate,
327  'table_local' => 'sys_file',
328  'pid' => ($this->table === 'pages' ? $row['uid'] : $row['pid']),
329  'uid_foreign' => $row['uid'],
330  'uid_local' => $fileUid,
331  'tablenames' => $this->table,
332  'crdate' => time(),
333  'tstamp' => time(),
334  'sorting' => ($i + 256),
335  'sorting_foreign' => $i,
336  ];
337 
338  $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
339  $queryBuilder->insert('sys_file_reference')->values($fields)->execute();
340  $dbQueries[] = str_replace(LF, ' ', $queryBuilder->getSQL());
341  ++$i;
342  }
343  }
344 
345  // Update referencing table's original field to now contain the count of references,
346  // but only if all new references could be set
347  if ($i === count($fieldItems)) {
348  $queryBuilder = $connectionPool->getQueryBuilderForTable($this->table);
349  $queryBuilder->update($this->table)->where(
350  $queryBuilder->expr()->eq(
351  'uid',
352  $queryBuilder->createNamedParameter($row['uid'], \PDO::PARAM_INT)
353  )
354  )->set($this->fieldToMigrate, $i)->execute();
355  $dbQueries[] = str_replace(LF, ' ', $queryBuilder->getSQL());
356  } else {
357  $this->recordOffset[$this->table]++;
358  }
359  }
360 }
static mkdir_deep($directory, $deepDirectory='')
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
$fields
Definition: pages.php:4
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']