You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

465 lines
16 KiB

  1. <?php
  2. /**
  3. * (c) Kitodo. Key to digital objects e.V. <contact@kitodo.org>
  4. *
  5. * This file is part of the Kitodo and TYPO3 projects.
  6. *
  7. * @license GNU General Public License version 3 or later.
  8. * For the full copyright and license information, please read the
  9. * LICENSE.txt file that was distributed with this source code.
  10. */
  11. use Kitodo\Dlf\Common\Helper;
  12. use Kitodo\Dlf\Common\Solr;
  13. use TYPO3\CMS\Core\Database\ConnectionPool;
  14. use TYPO3\CMS\Core\Utility\GeneralUtility;
  15. /**
  16. * Update class 'ext_update' for the 'dlf' extension
  17. *
  18. * @author Sebastian Meyer <sebastian.meyer@slub-dresden.de>
  19. * @package TYPO3
  20. * @subpackage dlf
  21. * @access public
  22. */
  23. class ext_update
  24. {
  25. /**
  26. * This holds the output ready to return
  27. *
  28. * @var string
  29. * @access protected
  30. */
  31. protected $content = '';
  32. /**
  33. * Triggers the update option in the extension manager
  34. *
  35. * @access public
  36. *
  37. * @return bool Should the update option be shown?
  38. */
  39. public function access(): bool
  40. {
  41. if (count($this->getMetadataConfig())) {
  42. return true;
  43. }
  44. if ($this->oldIndexRelatedTableNames()) {
  45. return true;
  46. }
  47. if ($this->solariumSolrUpdateRequired()) {
  48. return true;
  49. }
  50. if (count($this->oldFormatClasses())) {
  51. return true;
  52. }
  53. if ($this->hasNoFormatForDocument()) {
  54. return true;
  55. }
  56. return false;
  57. }
  58. /**
  59. * The main method of the class
  60. *
  61. * @access public
  62. *
  63. * @return string The content that is displayed on the website
  64. */
  65. public function main(): string
  66. {
  67. // Load localization file.
  68. $GLOBALS['LANG']->includeLLFile('EXT:dlf/Resources/Private/Language/FlashMessages.xml');
  69. // Update the metadata configuration.
  70. if (count($this->getMetadataConfig())) {
  71. $this->updateMetadataConfig();
  72. }
  73. if ($this->oldIndexRelatedTableNames()) {
  74. $this->renameIndexRelatedColumns();
  75. }
  76. if ($this->solariumSolrUpdateRequired()) {
  77. $this->doSolariumSolrUpdate();
  78. }
  79. if (count($this->oldFormatClasses())) {
  80. $this->updateFormatClasses();
  81. }
  82. // Set tx_dlf_documents.document_format to distinguish between METS and IIIF.
  83. if ($this->hasNoFormatForDocument()) {
  84. $this->updateDocumentAddFormat();
  85. }
  86. return $this->content;
  87. }
  88. /**
  89. * Get all outdated metadata configuration records
  90. *
  91. * @access protected
  92. *
  93. * @return array Array of UIDs of outdated records
  94. */
  95. protected function getMetadataConfig(): array
  96. {
  97. $uids = [];
  98. // check if tx_dlf_metadata.xpath exists anyhow
  99. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_dlf_metadata');
  100. $result = $queryBuilder
  101. ->select('*')
  102. ->from('tx_dlf_metadata')
  103. ->execute();
  104. $rows = $result->fetchAll();
  105. if ((count($rows) === 0) || !array_key_exists('xpath', $rows[0])) {
  106. return $uids;
  107. }
  108. foreach ($rows as $row) {
  109. if ($row['format'] === 0 && $row['xpath']) {
  110. $uids[] = (int)$row['uid'];
  111. }
  112. }
  113. return $uids;
  114. }
  115. /**
  116. * Check all configured Solr cores
  117. *
  118. * @access protected
  119. *
  120. * @return bool
  121. */
  122. protected function solariumSolrUpdateRequired(): bool
  123. {
  124. // Get all Solr cores that were not deleted.
  125. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_dlf_solrcores');
  126. $result = $queryBuilder
  127. ->select('index_name')
  128. ->from('tx_dlf_solrcores')
  129. ->execute();
  130. while ($resArray = $result->fetch()) {
  131. // Instantiate search object.
  132. $solr = Solr::getInstance($resArray['index_name']);
  133. if (!$solr->ready) {
  134. return true;
  135. }
  136. }
  137. return false;
  138. }
  139. /**
  140. * Check for old format classes
  141. *
  142. * @access protected
  143. *
  144. * @return array containing old format classes
  145. */
  146. protected function oldFormatClasses(): array
  147. {
  148. $oldRecords = [];
  149. // Get all records with outdated configuration.
  150. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_dlf_formats');
  151. $result = $queryBuilder
  152. ->select('tx_dlf_formats.uid AS uid', 'tx_dlf_formats.type AS type')
  153. ->from('tx_dlf_formats')
  154. ->where(
  155. $queryBuilder->expr()->isNotNull('tx_dlf_formats.class'),
  156. $queryBuilder->expr()->neq('tx_dlf_formats.class', $queryBuilder->createNamedParameter('')),
  157. $queryBuilder->expr()->like('tx_dlf_formats.class', $queryBuilder->createNamedParameter('%tx_dlf_%'))
  158. )
  159. ->execute();
  160. while ($resArray = $result->fetch()) {
  161. $oldRecords[$resArray['uid']] = $resArray['type'];
  162. }
  163. return $oldRecords;
  164. }
  165. /**
  166. * Check for old index related columns
  167. *
  168. * @access protected
  169. *
  170. * @return bool true if old index related columns exist
  171. */
  172. protected function oldIndexRelatedTableNames(): bool
  173. {
  174. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('INFORMATION_SCHEMA.COLUMNS');
  175. $result = $queryBuilder
  176. ->select('column_name')
  177. ->from('INFORMATION_SCHEMA.COLUMNS')
  178. ->where('TABLE_NAME = "tx_dlf_metadata"')
  179. ->execute();
  180. while ($resArray = $result->fetch()) {
  181. if (
  182. $resArray['column_name'] === 'tokenized'
  183. || $resArray['column_name'] === 'stored'
  184. || $resArray['column_name'] === 'indexed'
  185. || $resArray['column_name'] === 'boost'
  186. || $resArray['column_name'] === 'autocomplete'
  187. ) {
  188. return true;
  189. }
  190. }
  191. return false;
  192. }
  193. /**
  194. * check if document has format
  195. *
  196. * @access protected
  197. * @param bool $checkStructureOnly
  198. * @return bool
  199. */
  200. protected function hasNoFormatForDocument($checkStructureOnly = false): bool
  201. {
  202. // Check if column "document_format" exists.
  203. $database = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname'];
  204. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('INFORMATION_SCHEMA.COLUMNS');
  205. $result = $queryBuilder
  206. ->select('COLUMN_NAME')
  207. ->from('INFORMATION_SCHEMA.COLUMNS')
  208. ->where('TABLE_NAME="tx_dlf_documents" AND TABLE_SCHEMA="' . $database . '" AND COLUMN_NAME="document_format"')
  209. ->execute();
  210. while ($resArray = $result->fetch()) {
  211. if ($resArray['COLUMN_NAME'] === 'document_format') {
  212. if ($checkStructureOnly) {
  213. return false;
  214. }
  215. // Check if column has empty fields.
  216. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('INFORMATION_SCHEMA.COLUMNS');
  217. $count = $queryBuilder
  218. ->count('uid')
  219. ->from('tx_dlf_documents')
  220. ->where('document_format="" OR document_format IS NULL')
  221. ->execute()
  222. ->fetchColumn(0);
  223. if ($count === 0) {
  224. return false;
  225. }
  226. }
  227. }
  228. return true;
  229. }
  230. /**
  231. * Copy the data of the old index related columns to the new columns
  232. *
  233. * @access protected
  234. *
  235. * @return void
  236. */
  237. protected function renameIndexRelatedColumns(): void
  238. {
  239. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_dlf_metadata');
  240. $result = $queryBuilder
  241. ->update('tx_dlf_metadata', 'm')
  242. ->set('m.index_tokenized', 'm.tokenized')
  243. ->set('m.index_stored', 'm.stored')
  244. ->set('m.index_indexed', 'm.indexed')
  245. ->set('m.index_boost', 'm.boost')
  246. ->set('m.index_autocomplete', 'm.autocomplete')
  247. ->execute();
  248. if ($result) {
  249. Helper::addMessage(
  250. htmlspecialchars($GLOBALS['LANG']->getLL('update.copyIndexRelatedColumnsOkay')),
  251. htmlspecialchars($GLOBALS['LANG']->getLL('update.copyIndexRelatedColumns')),
  252. \TYPO3\CMS\Core\Messaging\FlashMessage::OK
  253. );
  254. } else {
  255. Helper::addMessage(
  256. htmlspecialchars($GLOBALS['LANG']->getLL('update.copyIndexRelatedColumnsNotOkay')),
  257. htmlspecialchars($GLOBALS['LANG']->getLL('update.copyIndexRelatedColumns')),
  258. \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING
  259. );
  260. }
  261. $this->content .= Helper::renderFlashMessages();
  262. }
  263. /**
  264. * Update all outdated format records
  265. *
  266. * @access protected
  267. *
  268. * @return void
  269. */
  270. protected function updateFormatClasses(): void
  271. {
  272. $oldRecords = $this->oldFormatClasses();
  273. $newValues = [
  274. 'ALTO' => 'Kitodo\\\\Dlf\\\\Format\\\\Alto', // Those are effectively single backslashes
  275. 'MODS' => 'Kitodo\\\\Dlf\\\\Format\\\\Mods',
  276. 'TEIHDR' => 'Kitodo\\\\Dlf\\\\Format\\\\TeiHeader'
  277. ];
  278. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_dlf_formats');
  279. foreach ($oldRecords as $uid => $type) {
  280. $queryBuilder
  281. ->update('tx_dlf_formats')
  282. ->where(
  283. $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid))
  284. )
  285. ->set('class', $newValues[$type])
  286. ->execute();
  287. }
  288. Helper::addMessage(
  289. htmlspecialchars($GLOBALS['LANG']->getLL('update.FormatClassesOkay')),
  290. htmlspecialchars($GLOBALS['LANG']->getLL('update.FormatClasses')),
  291. \TYPO3\CMS\Core\Messaging\FlashMessage::OK
  292. );
  293. $this->content .= Helper::renderFlashMessages();
  294. }
  295. /**
  296. * Update all outdated metadata configuration records
  297. *
  298. * @access protected
  299. *
  300. * @return void
  301. */
  302. protected function updateMetadataConfig(): void
  303. {
  304. $metadataUids = $this->getMetadataConfig();
  305. if (!empty($metadataUids)) {
  306. $data = [];
  307. // Get all old metadata configuration records.
  308. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_dlf_metadata');
  309. $result = $queryBuilder
  310. ->select('tx_dlf_metadata.uid AS uid', 'tx_dlf_metadata.pid AS pid', 'tx_dlf_metadata.cruser_id AS cruser_id', 'tx_dlf_metadata.encoded AS encoded', 'tx_dlf_metadata.xpath AS xpath', 'tx_dlf_metadata.xpath_sorting AS xpath_sorting')
  311. ->from('tx_dlf_metadata')
  312. ->where(
  313. $queryBuilder->expr()->in(
  314. 'tx_dlf_metadata.uid',
  315. $queryBuilder->createNamedParameter(
  316. $metadataUids,
  317. \TYPO3\CMS\Core\Database\Connection::PARAM_INT_ARRAY
  318. )
  319. )
  320. )
  321. ->execute();
  322. while ($resArray = $result->fetch()) {
  323. $newId = uniqid('NEW');
  324. // Copy record to new table.
  325. $data['tx_dlf_metadataformat'][$newId] = [
  326. 'pid' => $resArray['pid'],
  327. 'cruser_id' => $resArray['cruser_id'],
  328. 'parent_id' => $resArray['uid'],
  329. 'encoded' => $resArray['encoded'],
  330. 'xpath' => $resArray['xpath'],
  331. 'xpath_sorting' => $resArray['xpath_sorting']
  332. ];
  333. // Add reference to old table.
  334. $data['tx_dlf_metadata'][$resArray['uid']]['format'] = $newId;
  335. }
  336. if (!empty($data)) {
  337. // Process datamap.
  338. $substUids = Helper::processDBasAdmin($data);
  339. unset($data);
  340. if (!empty($substUids)) {
  341. Helper::addMessage(
  342. htmlspecialchars($GLOBALS['LANG']->getLL('update.metadataConfigOkay')),
  343. htmlspecialchars($GLOBALS['LANG']->getLL('update.metadataConfig')),
  344. \TYPO3\CMS\Core\Messaging\FlashMessage::OK
  345. );
  346. } else {
  347. Helper::addMessage(
  348. htmlspecialchars($GLOBALS['LANG']->getLL('update.metadataConfigNotOkay')),
  349. htmlspecialchars($GLOBALS['LANG']->getLL('update.metadataConfig')),
  350. \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING
  351. );
  352. }
  353. $this->content .= Helper::renderFlashMessages();
  354. }
  355. }
  356. }
  357. /**
  358. * Create all configured Solr cores
  359. *
  360. * @access protected
  361. *
  362. * @return void
  363. */
  364. protected function doSolariumSolrUpdate(): void
  365. {
  366. $error = false;
  367. // Get all Solr cores that were not deleted.
  368. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_dlf_solrcores');
  369. $result = $queryBuilder
  370. ->select('index_name')
  371. ->from('tx_dlf_solrcores')
  372. ->execute();
  373. while ($resArray = $result->fetch()) {
  374. // Create core if it doesn't exist.
  375. if (Solr::createCore($resArray['index_name']) !== $resArray['index_name']) {
  376. Helper::addMessage(
  377. htmlspecialchars($GLOBALS['LANG']->getLL('update.solariumSolrUpdateNotOkay')),
  378. htmlspecialchars(sprintf($GLOBALS['LANG']->getLL('update.solariumSolrUpdate'), $resArray['index_name'])),
  379. \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR
  380. );
  381. $this->content .= Helper::renderFlashMessages();
  382. $error = true;
  383. }
  384. }
  385. if (!$error) {
  386. Helper::addMessage(
  387. htmlspecialchars($GLOBALS['LANG']->getLL('update.solariumSolrUpdateOkay')),
  388. htmlspecialchars($GLOBALS['LANG']->getLL('update.solariumSolrUpdate')),
  389. \TYPO3\CMS\Core\Messaging\FlashMessage::OK
  390. );
  391. $this->content .= Helper::renderFlashMessages();
  392. }
  393. }
  394. /**
  395. * Add format type to outdated tx_dlf_documents rows
  396. *
  397. * @return void
  398. */
  399. protected function updateDocumentAddFormat(): void
  400. {
  401. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_dlf_documents');
  402. $result = $queryBuilder
  403. ->update('tx_dlf_documents')
  404. ->where(
  405. $queryBuilder->expr()->orX(
  406. $queryBuilder->expr()->eq('document_format', $queryBuilder->createNamedParameter(null)),
  407. $queryBuilder->expr()->eq('document_format', $queryBuilder->createNamedParameter(''))
  408. )
  409. )
  410. ->set('document_format', 'METS')
  411. ->execute();
  412. if ($result) {
  413. Helper::addMessage(
  414. htmlspecialchars($GLOBALS['LANG']->getLL('update.documentSetFormatForOldEntriesOkay')),
  415. htmlspecialchars($GLOBALS['LANG']->getLL('update.documentSetFormatForOldEntries')),
  416. \TYPO3\CMS\Core\Messaging\FlashMessage::OK
  417. );
  418. } else {
  419. Helper::addMessage(
  420. htmlspecialchars($GLOBALS['LANG']->getLL('update.documentSetFormatForOldEntriesNotOkay')),
  421. htmlspecialchars($GLOBALS['LANG']->getLL('update.documentSetFormatForOldEntries')),
  422. \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING
  423. );
  424. }
  425. $this->content .= Helper::renderFlashMessages();
  426. }
  427. }