RTranslationTable::getInstalledTranslationTables()   C
last analyzed

Complexity

Conditions 16
Paths 36

Size

Total Lines 101
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 56
c 0
b 0
f 0
dl 0
loc 101
rs 5.5666
cc 16
nc 36
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @package     Redcore
4
 * @subpackage  Translation
5
 *
6
 * @copyright   Copyright (C) 2008 - 2021 redWEB.dk. All rights reserved.
7
 * @license     GNU General Public License version 2 or later, see LICENSE.
8
 */
9
10
defined('_JEXEC') or die;
11
12
/**
13
 * A Translation Table helper.
14
 *
15
 * @package     Redcore
16
 * @subpackage  Translation
17
 * @since       1.0
18
 */
19
final class RTranslationTable
20
{
21
	/**
22
	 * @const  string
23
	 */
24
	const COLUMN_TRANSLATE = 'translate';
25
26
	/**
27
	 * @const  string
28
	 */
29
	const COLUMN_PRIMARY = 'primary';
30
31
	/**
32
	 * @const  string
33
	 */
34
	const COLUMN_READONLY = 'readonly';
35
36
	/**
37
	 * An array to hold tables from database
38
	 *
39
	 * @var    array
40
	 * @since  1.0
41
	 */
42
	public static $installedTranslationTables = null;
43
44
	/**
45
	 * An array to hold columns from database
46
	 *
47
	 * @var    array
48
	 * @since  1.0
49
	 */
50
	public static $translationColumns = null;
51
52
	/**
53
	 * An array to hold tables from database
54
	 *
55
	 * @var    array
56
	 * @since  1.0
57
	 */
58
	public static $tableList = array();
59
60
	/**
61
	 * An array to hold tables columns from database
62
	 *
63
	 * @var    array
64
	 * @since  1.0
65
	 */
66
	public static $columnsList = array();
67
68
	/**
69
	 * An array to hold database engines from database
70
	 *
71
	 * @var    array
72
	 * @since  1.0
73
	 */
74
	public static $dbEngines = array();
75
76
	/**
77
	 * Prefix used to identify the tables
78
	 *
79
	 * @var    array
80
	 * @since  1.0
81
	 */
82
	public static $tablePrefix = '';
83
84
	/**
85
	 * Get Translations Table Columns Array
86
	 *
87
	 * @param   string  $originalTableName  Original table name
88
	 *
89
	 * @return  array  An array of table columns
90
	 */
91
	public static function getTranslationsTableColumns($originalTableName)
92
	{
93
		if (empty(self::$tablePrefix))
94
		{
95
			self::loadTables();
96
		}
97
98
		$tableName = self::getTranslationsTableName($originalTableName, self::$tablePrefix);
0 ignored issues
show
Bug introduced by
self::tablePrefix of type array is incompatible with the type string expected by parameter $prefix of RTranslationTable::getTranslationsTableName(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

98
		$tableName = self::getTranslationsTableName($originalTableName, /** @scrutinizer ignore-type */ self::$tablePrefix);
Loading history...
99
100
		if (in_array($tableName, self::$tableList))
101
		{
102
			if (empty(self::$columnsList[$tableName]))
103
			{
104
				$db = JFactory::getDbo();
105
106
				self::$columnsList[$tableName] = $db->getTableColumns($tableName, false);
107
			}
108
109
			return self::$columnsList[$tableName];
110
		}
111
112
		return array();
113
	}
114
115
	/**
116
	 * Get Translations Table Columns Array
117
	 *
118
	 * @param   string  $tableName  Original table name
119
	 * @param   bool    $addPrefix  Add prefix to the table name
120
	 *
121
	 * @return  array  An array of table columns
122
	 */
123
	public static function getTableColumns($tableName, $addPrefix = true)
124
	{
125
		if (empty(self::$tablePrefix))
126
		{
127
			self::loadTables();
128
		}
129
130
		if ($addPrefix)
131
		{
132
			$tableName = self::$tablePrefix . str_replace('#__', '', $tableName);
0 ignored issues
show
Bug introduced by
Are you sure self::tablePrefix of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

132
			$tableName = /** @scrutinizer ignore-type */ self::$tablePrefix . str_replace('#__', '', $tableName);
Loading history...
133
		}
134
135
		if (in_array($tableName, self::$tableList))
136
		{
137
			if (empty(self::$columnsList[$tableName]))
138
			{
139
				$db = JFactory::getDbo();
140
				$translate = property_exists($db, 'translate') ? $db->translate : false;
141
				$db->translate = false;
142
				self::$columnsList[$tableName] = $db->getTableColumns($tableName, false);
143
				$db->translate = $translate;
144
			}
145
146
			return self::$columnsList[$tableName];
147
		}
148
149
		return array();
150
	}
151
152
	/**
153
	 * Load all tables from current database into array
154
	 *
155
	 * @return  array  An array of table names
156
	 */
157
	public static function loadTables()
158
	{
159
		if (empty(self::$tablePrefix))
160
		{
161
			$db = JFactory::getDbo();
162
			$translate = property_exists($db, 'translate') ? $db->translate : false;
163
			$db->translate = false;
164
			self::$tableList = $db->getTableList();
165
			self::$tablePrefix = $db->getPrefix();
0 ignored issues
show
Documentation Bug introduced by
It seems like $db->getPrefix() of type string is incompatible with the declared type array of property $tablePrefix.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
166
			$db->translate = $translate;
167
		}
168
169
		return self::$tableList;
170
	}
171
172
	/**
173
	 * Reset loaded tables
174
	 *
175
	 * @return  void
176
	 */
177
	public static function resetLoadedTables()
178
	{
179
		self::$tablePrefix = '';
0 ignored issues
show
Documentation Bug introduced by
It seems like '' of type string is incompatible with the declared type array of property $tablePrefix.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
180
		self::loadTables();
181
	}
182
183
	/**
184
	 * Get table name with suffix
185
	 *
186
	 * @param   string  $originalTableName  Original table name
187
	 * @param   string  $prefix             Table name prefix
188
	 *
189
	 * @return  string  Table name used for getting translations
190
	 */
191
	public static function getTranslationsTableName($originalTableName, $prefix = '#__')
192
	{
193
		if (empty(self::$tablePrefix))
194
		{
195
			self::loadTables();
196
		}
197
198
		return $prefix . str_replace($prefix, '', $originalTableName) . '_rctranslations';
199
	}
200
201
	/**
202
	 * Install or update Translation table from current table object
203
	 *
204
	 * @param   RedcoreTableTranslation_Table  $table          Data needed for translation table creation
205
	 * @param   bool                           $notifications  Should we display notifications?
206
	 *
207
	 * @return  boolean  Returns true if translation table was successfully installed
208
	 */
209
	public static function setTranslationTable($table, $notifications = false)
210
	{
211
		$db = JFactory::getDbo();
212
		$newTable = self::getTranslationsTableName($table->name);
213
		$originalTable = str_replace('#__', '', $table->name);
214
		$originalTableWithPrefix = '#__' . $originalTable;
215
		$columns = self::getTranslationsTableColumns($originalTable);
216
		$newTableCreated = false;
217
		$innoDBSupport = self::checkIfDatabaseEngineExists();
218
219
		// We might be in installer and got new tables so we will get fresh list of the tables
220
		self::resetLoadedTables();
221
		$originalColumns = self::getTableColumns($originalTable);
222
223
		// If original table is not present then we cannot create shadow table
224
		if (empty($originalColumns))
225
		{
226
			if ($notifications)
227
			{
228
				JLog::add(
229
					JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR_TABLE', str_replace('#__', '', $table->name)), JLog::ERROR, 'jerror'
230
				);
231
			}
232
233
			return false;
234
		}
235
236
		if (empty($columns))
237
		{
238
			$newTableCreated = true;
239
			$query = 'CREATE TABLE IF NOT EXISTS ' . $db->qn($newTable)
240
				. ' ('
241
				. $db->qn('rctranslations_id') . ' int(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT, '
242
				. $db->qn('rctranslations_language') . ' char(7) NOT NULL DEFAULT ' . $db->q('') . ', '
243
				. $db->qn('rctranslations_originals') . ' TEXT NOT NULL, '
244
				. $db->qn('rctranslations_modified_by') . ' INT(11) NULL DEFAULT NULL, '
245
				. $db->qn('rctranslations_modified') . ' datetime NOT NULL DEFAULT ' . $db->q('0000-00-00 00:00:00') . ', '
246
				. $db->qn('rctranslations_state') . ' tinyint(3) NOT NULL DEFAULT ' . $db->q('1') . ', '
247
				. ' KEY ' . $db->qn('language_idx') . ' (' . $db->qn('rctranslations_language') . ',' . $db->qn('rctranslations_state') . ') '
248
				. ' ) DEFAULT CHARSET=utf8';
249
250
			if ($innoDBSupport)
251
			{
252
				$query .= ' ENGINE=InnoDB';
253
			}
254
255
			$db->setQuery($query);
256
257
			try
258
			{
259
				$db->execute();
260
261
				// Since we have new table we will reset it
262
				self::resetLoadedTables();
263
				$columns = self::getTableColumns($newTable);
264
265
				if (empty($columns))
266
				{
267
					throw new RuntimeException($newTable);
268
				}
269
			}
270
			catch (RuntimeException $e)
271
			{
272
				if ($notifications)
273
				{
274
					JLog::add(JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR', $e->getMessage()), JLog::ERROR, 'jerror');
275
				}
276
277
				return false;
278
			}
279
		}
280
281
		// Language is automatically added to the table if table exists
282
		$columns = self::removeFixedColumnsFromArray($columns);
283
		$columnKeys = array_keys($columns);
284
		$fields = $table->get('columns', array());
285
286
		foreach ($fields as $fieldKey => $field)
287
		{
288
			$fields[$fieldKey]['exists'] = false;
289
290
			foreach ($columnKeys as $columnKey => $columnKeyValue)
291
			{
292
				if ($field['name'] == $columnKeyValue && $field['column_type'] != self::COLUMN_READONLY)
293
				{
294
					unset($columnKeys[$columnKey]);
295
					$fields[$fieldKey]['exists'] = true;
296
				}
297
			}
298
		}
299
300
		// We Add New or modify existing columns
301
		if (!empty($fields))
302
		{
303
			$newColumns = array();
304
305
			foreach ($fields as $fieldKey => $field)
306
			{
307
				if ($field['column_type'] != self::COLUMN_READONLY)
308
				{
309
					if (!empty($originalColumns[$field['name']]))
310
					{
311
						$modify = $field['exists'] ? 'MODIFY' : 'ADD';
312
313
						$newColumns[] = $modify . ' COLUMN ' . $db->qn($field['name'])
314
							. ' ' . $originalColumns[$field['name']]->Type
315
							. ' NULL'
316
							. ' DEFAULT NULL ';
317
					}
318
					else
319
					{
320
						if ($notifications)
321
						{
322
							JLog::add(
323
								JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR_COLUMNS', $originalTable, $field['name']), JLog::ERROR, 'jerror'
324
							);
325
						}
326
327
						return false;
328
					}
329
				}
330
			}
331
332
			if (!empty($newColumns))
333
			{
334
				try
335
				{
336
					$query = 'ALTER TABLE ' . $db->qn($newTable) . ' ' . implode(',', $newColumns);
337
					$db->setQuery($query);
338
					$db->execute();
339
				}
340
				catch (RuntimeException $e)
341
				{
342
					if ($notifications)
343
					{
344
						JLog::add(JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR', $e->getMessage()), JLog::ERROR, 'jerror');
345
					}
346
347
					return false;
348
				}
349
			}
350
		}
351
352
		// Remove old constraints if they exist before removing the columns
353
		if (!$newTableCreated)
354
		{
355
			$loadedTable = self::getTranslationTableByName($originalTableWithPrefix);
356
			$primaryColumns = !empty($loadedTable) ? explode(',', $loadedTable->primary_columns) : array();
357
358
			self::removeExistingConstraintKeys($originalTableWithPrefix, $primaryColumns);
359
		}
360
361
		// We delete extra columns
362
		if (!empty($columnKeys) && !$newTableCreated)
363
		{
364
			$oldColumns = array();
365
366
			foreach ($columnKeys as $columnKey)
367
			{
368
				$oldColumns[] = 'DROP COLUMN ' . $db->qn($columnKey);
369
			}
370
371
			if (!empty($oldColumns))
372
			{
373
				try
374
				{
375
					$query = 'ALTER TABLE ' . $db->qn($newTable) . ' ' . implode(',', $oldColumns);
376
					$db->setQuery($query);
377
					$db->execute();
378
				}
379
				catch (RuntimeException $e)
380
				{
381
					if ($notifications)
382
					{
383
						JLog::add(JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR', $e->getMessage()), JLog::ERROR, 'jerror');
384
					}
385
386
					return false;
387
				}
388
			}
389
		}
390
391
		self::updateTableIndexKeys($fields, $newTable, $notifications);
392
393
		$constraintType = 'none';
394
395
		if (method_exists('RBootstrap', 'getConfig'))
396
		{
397
			$constraintType = RBootstrap::getConfig('translations_constraint_type', 'none');
398
		}
399
400
		// New install use default value foreign key if InnoDB is present
401
		if (($constraintType == 'foreign_keys'))
402
		{
403
			if ($innoDBSupport)
404
			{
405
				self::updateTableForeignKeys($fields, $newTable, $originalTableWithPrefix);
406
			}
407
			else
408
			{
409
				if ($notifications)
410
				{
411
					JLog::add(JText::_('COM_REDCORE_TRANSLATION_TABLE_CONTENT_ELEMENT_INNODB_MISSING'), JLog::WARNING, 'jerror');
412
				}
413
			}
414
		}
415
		elseif ($constraintType == 'triggers')
416
		{
417
			self::updateTableTriggers($fields, $newTable, $originalTableWithPrefix, $notifications);
418
		}
419
420
		return true;
421
	}
422
423
	/**
424
	 * Install Content Element from XML file
425
	 *
426
	 * @param   string  $option         The Extension Name ex. com_redcore
427
	 * @param   string  $xmlFile        XML file to install
428
	 * @param   bool    $fullPath       Full path to the XML file
429
	 * @param   bool    $notifications  Full path to the XML file
430
	 *
431
	 * @return  boolean  Returns true if Content element was successfully installed
432
	 */
433
	public static function installContentElement($option = 'com_redcore', $xmlFile = '', $fullPath = false, $notifications = false)
434
	{
435
		// Load Content Element
436
		$contentElement = RTranslationContentElement::getContentElement($option, $xmlFile, $fullPath);
437
438
		if (empty($contentElement) || empty($contentElement->table))
439
		{
440
			if ($notifications)
441
			{
442
				JLog::add(
443
					JText::sprintf(
444
						'LIB_REDCORE_TRANSLATION_TABLE_CONTENT_ELEMENT_NOT_INSTALLED', empty($contentElement->table) ? empty($contentElement->table) : ''
445
					),
446
					JLog::WARNING, 'jerror'
447
				);
448
			}
449
450
			return false;
451
		}
452
453
		// Create table with fields
454
		$db = JFactory::getDbo();
455
		$originalTable = '#__' . $contentElement->table;
456
457
		// Check if that table is already installed
458
		$fields = array();
459
		$contentElement->fieldsToColumns = array();
460
		$primaryKeys = array();
461
		$fieldsXml = $contentElement->getTranslateFields();
462
463
		foreach ($fieldsXml as $field)
464
		{
465
			$fieldAttributes = (array) $field->attributes();
466
			$fieldAttributes = !empty($fieldAttributes) ? $fieldAttributes['@attributes'] : array();
467
468
			$fieldsToColumn = array();
469
			$fieldsToColumn['name'] = (string) $fieldAttributes['name'];
470
			$fieldsToColumn['title'] = (string) $field;
471
			$fieldsToColumn['description'] = !empty($fieldAttributes['description']) ? (string) $fieldAttributes['description'] : '';
472
			$fieldsToColumn['fallback'] = isset($fieldAttributes['alwaysFallback']) && (string) $fieldAttributes['alwaysFallback'] == 'true' ? 1 : 0;
473
			$fieldsToColumn['value_type'] = (string) $fieldAttributes['type'];
474
			$fieldsToColumn['params'] = $fieldAttributes;
475
476
			foreach ($fieldsToColumn['params'] as $paramKey => $paramValue)
477
			{
478
				if (in_array($paramKey, array('name', 'type', 'alwaysFallback', 'translate')))
479
				{
480
					unset($fieldsToColumn['params'][$paramKey]);
481
				}
482
			}
483
484
			$translate = isset($fieldAttributes['translate']) && ((string) $fieldAttributes['translate']) == '0' ? false : true;
485
			$primaryKey = isset($fieldAttributes['type']) && ((string) $fieldAttributes['type']) == 'referenceid' ? true : false;
486
487
			if ($primaryKey)
488
			{
489
				$fieldsToColumn['column_type'] = self::COLUMN_PRIMARY;
490
				$fieldsToColumn['value_type'] = 'referenceid';
491
			}
492
			elseif (!$translate)
493
			{
494
				$fieldsToColumn['column_type'] = self::COLUMN_READONLY;
495
			}
496
			else
497
			{
498
				$fieldsToColumn['column_type'] = self::COLUMN_TRANSLATE;
499
			}
500
501
			$contentElement->fieldsToColumns[] = $fieldsToColumn;
502
503
			// We are not saving this fields, we only show them in editor
504
			if (isset($fieldAttributes['translate']) && (string) $fieldAttributes['translate'] == '0' && (string) $fieldAttributes['type'] != 'referenceid')
505
			{
506
				continue;
507
			}
508
509
			$fieldName = (string) $fieldAttributes['name'];
510
			$fields[$fieldName] = $db->qn($fieldName);
511
512
			if ($primaryKey)
513
			{
514
				$primaryKeys[$fieldName] = $db->qn($fieldName);
515
			}
516
		}
517
518
		if (empty($fields))
519
		{
520
			if ($notifications)
521
			{
522
				JLog::add(
523
					JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR_NO_FIELDS', $xmlFile), JLog::ERROR, 'jerror'
524
				);
525
			}
526
527
			return false;
528
		}
529
530
		$loadedTable = self::getTranslationTableByName($originalTable);
531
		$contentElement->tableId = !empty($loadedTable) ? $loadedTable->id : 0;
532
533
		self::setInstalledTranslationTables(
534
			$option,
535
			$originalTable,
536
			$contentElement,
537
			$notifications
538
		);
539
540
		return true;
541
	}
542
543
	/**
544
	 * Adds table Index Keys based on primary keys for improved performance
545
	 *
546
	 * @param   array   $fields         Array of fields from translation table
547
	 * @param   string  $newTable       Table name
548
	 * @param   bool    $notifications  Table name
549
	 *
550
	 * @return  boolean  Returns true if Content element was successfully installed
551
	 */
552
	public static function updateTableIndexKeys($fields, $newTable, $notifications = false)
553
	{
554
		$indexKeys = array();
555
		$db = JFactory::getDbo();
556
557
		$tableKeys      = $db->getTableKeys($newTable);
558
		$languageKey    = $db->qn('rctranslations_language');
559
		$stateKey       = $db->qn('rctranslations_state');
560
561
		foreach ($fields as $field)
562
		{
563
			if ((string) $field['value_type'] == 'referenceid')
564
			{
565
				$fieldName      = (string) $field['name'];
566
				$constraintKey  = md5($newTable . '_' . $fieldName . '_idx');
567
				$keyFound       = false;
568
569
				foreach ($tableKeys as $tableKey)
570
				{
571
					if ($tableKey->Key_name == $constraintKey)
572
					{
573
						$keyFound = true;
574
						break;
575
					}
576
				}
577
578
				if (!$keyFound)
579
				{
580
					$indexKeys[$constraintKey] = 'ADD KEY '
581
						. $db->qn($constraintKey)
582
						. ' (' . $languageKey . ',' . $stateKey . ',' . $db->qn($fieldName) . ')';
583
				}
584
			}
585
		}
586
587
		if (!empty($indexKeys))
588
		{
589
			$indexKeysQuery = 'ALTER TABLE '
590
				. $db->qn($newTable)
591
				. ' '
592
				. implode(', ', $indexKeys);
593
594
			try
595
			{
596
				$db->setQuery($indexKeysQuery);
597
				$db->execute();
598
			}
599
			catch (RuntimeException $e)
600
			{
601
				if ($notifications)
602
				{
603
					JLog::add(JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR', $e->getMessage()), JLog::ERROR, 'jerror');
604
				}
605
606
				return false;
607
			}
608
		}
609
610
		return true;
611
	}
612
613
	/**
614
	 * Adds table triggers based on primary keys for constraint between tables
615
	 *
616
	 * @param   array   $fields         Array of fields from translation table
617
	 * @param   string  $newTable       Translation Table name
618
	 * @param   string  $originalTable  Original Table name
619
	 * @param   bool    $notifications  Show notifications
620
	 *
621
	 * @return  boolean  Returns true if triggers are successfully installed
622
	 */
623
	public static function updateTableTriggers($fields, $newTable, $originalTable, $notifications = false)
624
	{
625
		$db = JFactory::getDbo();
626
		$triggerKey = md5($originalTable . '_rctranslationstrigger');
627
		$primaryKeys = array();
628
629
		if (!empty($fields))
630
		{
631
			foreach ($fields as $field)
632
			{
633
				if ((string) $field['type'] == 'referenceid')
634
				{
635
					$fieldName = (string) $field['name'];
636
					$primaryKeys[] = $db->qn($fieldName) . ' = OLD.' . $db->qn($fieldName);
637
				}
638
			}
639
		}
640
641
		if (!empty($primaryKeys))
642
		{
643
			$query = 'CREATE TRIGGER ' . $db->qn($triggerKey) . '
644
				AFTER DELETE
645
				ON ' . $db->qn($originalTable) . '
646
				FOR EACH ROW BEGIN
647
					DELETE FROM ' . $db->qn($newTable) . ' WHERE ' . implode(' AND ', $primaryKeys) . ';
648
				END;';
649
650
			try
651
			{
652
				$db->setQuery($query);
653
				$db->execute();
654
			}
655
			catch (RuntimeException $e)
656
			{
657
				if ($notifications)
658
				{
659
					JLog::add(JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR', $e->getMessage()), JLog::ERROR, 'jerror');
660
				}
661
662
				return false;
663
			}
664
		}
665
666
		return true;
667
	}
668
669
	/**
670
	 * Removes table foreign keys based on primary keys for constraint between tables
671
	 *
672
	 * @param   string  $originalTable  Original Table name
673
	 * @param   array   $primaryKeys    Primary keys of the table
674
	 *
675
	 * @return  boolean  Returns true if Foreign keys are successfully installed
676
	 */
677
	public static function removeExistingConstraintKeys($originalTable, $primaryKeys = array())
678
	{
679
		$innoDBSupport = self::checkIfDatabaseEngineExists();
680
681
		// Remove Triggers
682
		$db = JFactory::getDbo();
683
		$triggerKey = md5($originalTable . '_rctranslationstrigger');
684
685
		try
686
		{
687
			$db->setQuery('DROP TRIGGER IF EXISTS ' . $db->qn($triggerKey));
688
			$db->execute();
689
		}
690
		catch (RuntimeException $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
691
		{
692
693
		}
694
695
		if ($innoDBSupport && !empty($primaryKeys))
696
		{
697
			$newTable = self::getTranslationsTableName($originalTable, '');
698
699
			foreach ($primaryKeys as $primaryKey => $primaryKeyQuoted)
700
			{
701
				$constraintKey = $db->qn(md5($newTable . '_' . $primaryKeyQuoted . '_fk'));
702
703
				$query = 'ALTER TABLE ' . $db->qn($newTable) . ' DROP FOREIGN KEY ' . $constraintKey . ' # ' . $newTable . '_' . $primaryKey . '_fk';
704
705
				try
706
				{
707
					$db->setQuery($query);
708
					@$db->execute();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for execute(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

708
					/** @scrutinizer ignore-unhandled */ @$db->execute();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
709
				}
710
				catch (RuntimeException $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
711
				{
712
713
				}
714
			}
715
		}
716
	}
717
718
	/**
719
	 * Adds table foreign keys Keys based on primary keys for constraint between tables
720
	 *
721
	 * @param   array   $fields         Array of fields from translation table
722
	 * @param   string  $newTable       Translation Table name
723
	 * @param   string  $originalTable  Original Table name
724
	 * @param   bool    $notifications  Show notifications
725
	 *
726
	 * @return  boolean  Returns true if Foreign keys are successfully installed
727
	 */
728
	public static function updateTableForeignKeys($fields, $newTable, $originalTable, $notifications = false)
729
	{
730
		$db = JFactory::getDbo();
731
732
		if (!empty($fields))
733
		{
734
			foreach ($fields as $field)
735
			{
736
				if ((string) $field['value_type'] == 'referenceid')
737
				{
738
					$fieldName = (string) $field['name'];
739
740
					if (!empty($fieldName))
741
					{
742
						$primaryKey = $db->qn($fieldName);
743
						$constraintKey = $db->qn(md5($newTable . '_' . $fieldName . '_fk'));
744
						$query = 'ALTER TABLE ' . $db->qn($newTable) . ' ADD CONSTRAINT '
745
							. $constraintKey
746
							. ' FOREIGN KEY (' . $primaryKey . ')'
747
							. ' REFERENCES ' . $db->qn($originalTable) . ' (' . $primaryKey . ')'
748
							. '	ON DELETE CASCADE'
749
							. ' ON UPDATE NO ACTION ';
750
751
						try
752
						{
753
							$db->setQuery($query);
754
							$db->execute();
755
						}
756
						catch (RuntimeException $e)
757
						{
758
							if ($notifications)
759
							{
760
								JLog::add(JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR', $e->getMessage()), JLog::WARNING, 'jerror');
761
							}
762
763
							return false;
764
						}
765
					}
766
				}
767
			}
768
		}
769
770
		return true;
771
	}
772
773
	/**
774
	 * Purge Tables
775
	 *
776
	 * @param   array  $cid  Table IDs
777
	 *
778
	 * @return  boolean  Returns true if Table was successfully purged
779
	 */
780
	public static function purgeTables($cid)
781
	{
782
		$db = JFactory::getDbo();
783
784
		if (!is_array($cid))
0 ignored issues
show
introduced by
The condition is_array($cid) is always true.
Loading history...
785
		{
786
			$cid = array($cid);
787
		}
788
789
		foreach ($cid as $id)
790
		{
791
			$table = self::getTranslationTableById($id);
792
793
			if ($table)
0 ignored issues
show
Bug Best Practice introduced by
The expression $table of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
794
			{
795
				$newTableName = self::getTranslationsTableName($table->name);
796
797
				try
798
				{
799
					$db->truncateTable($newTableName);
800
				}
801
				catch (RuntimeException $e)
802
				{
803
					JLog::add(JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR', $e->getMessage()), JLog::WARNING, 'jerror');
804
805
					return false;
806
				}
807
			}
808
		}
809
810
		return true;
811
	}
812
813
	/**
814
	 * Delete Content Element Table and XML file
815
	 *
816
	 * @param   string  $option   The Extension Name ex. com_redcore
817
	 * @param   string  $xmlFile  XML file to install
818
	 *
819
	 * @return  boolean  Returns true if Content element was successfully purged
820
	 */
821
	public static function deleteContentElement($option = 'com_redcore', $xmlFile = '')
822
	{
823
		$xmlFilePath = RTranslationContentElement::getContentElementXmlPath($option, $xmlFile);
824
825
		try
826
		{
827
			JFile::delete($xmlFilePath);
828
		}
829
		catch (Exception $e)
830
		{
831
			JLog::add(JText::sprintf('LIB_REDCORE_TRANSLATIONS_CONTENT_ELEMENT_ERROR', $e->getMessage()), JLog::WARNING, 'jerror');
832
833
			return false;
834
		}
835
836
		return true;
837
	}
838
839
	/**
840
	 * Preforms Batch action against all Content elements of given Extension
841
	 *
842
	 * @param   string  $option         The Extension Name ex. com_redcore
843
	 * @param   string  $action         Action to preform
844
	 * @param   bool    $notifications  Show notifications
845
	 *
846
	 * @return  boolean  Returns true if Action was successful
847
	 */
848
	public static function batchContentElements($option = 'com_redcore', $action = '', $notifications = false)
849
	{
850
		RTranslationContentElement::$contentElements = array();
851
		$contentElements = RTranslationContentElement::getContentElements(true, $option);
852
		$tables = self::getInstalledTranslationTables(true);
853
854
		if (!empty($contentElements))
855
		{
856
			foreach ($contentElements as $componentName => $contentElement)
857
			{
858
				if (!empty($option) && $option != $contentElement->extension)
859
				{
860
					continue;
861
				}
862
863
				// We will check if this table is installed from a different path than the one provided
864
				foreach ($tables as $table)
865
				{
866
					if (str_replace('#__', '', $table->name) == $contentElement->table
867
						&& $table->xml_path != RTranslationContentElement::getPathWithoutBase($contentElement->contentElementXmlPath))
868
					{
869
						// We skip this file because it is not the one that is already installed
870
						continue 2;
871
					}
872
				}
873
874
				switch ($action)
875
				{
876
					case 'install':
877
						if (!empty($contentElement->table))
878
						{
879
							self::installContentElement($option, $contentElement->contentElementXmlPath, true, $notifications);
880
						}
881
882
						break;
883
					case 'delete':
884
						self::deleteContentElement($option, $contentElement->contentElementXml);
885
						break;
886
				}
887
			}
888
		}
889
890
		return true;
891
	}
892
893
	/**
894
	 * Upload Content Element to redcore media location
895
	 *
896
	 * @param   array  $files  The array of Files (file descriptor returned by PHP)
897
	 *
898
	 * @return  boolean  Returns true if Upload was successful
899
	 */
900
	public static function uploadContentElement($files = array())
901
	{
902
		$uploadOptions = array(
903
			'allowedFileExtensions' => 'xml',
904
			'allowedMIMETypes'      => 'application/xml, text/xml',
905
			'overrideExistingFile'  => true,
906
		);
907
908
		return RFilesystemFile::uploadFiles($files, RTranslationContentElement::getContentElementFolderPath('upload'), $uploadOptions);
0 ignored issues
show
Bug Best Practice introduced by
The expression return RFilesystemFile::...load'), $uploadOptions) returns the type array|array<mixed,array<string,mixed|string>> which is incompatible with the documented return type boolean.
Loading history...
909
	}
910
911
	/**
912
	 * Remove fixed columns from array
913
	 *
914
	 * @param   array  $columns  All the columns from the table
915
	 *
916
	 * @return  array  Filtered array of columns
917
	 */
918
	public static function removeFixedColumnsFromArray($columns = array())
919
	{
920
		unset($columns['rctranslations_id']);
921
		unset($columns['rctranslations_language']);
922
		unset($columns['rctranslations_originals']);
923
		unset($columns['rctranslations_modified']);
924
		unset($columns['rctranslations_modified_by']);
925
		unset($columns['rctranslations_state']);
926
927
		return $columns;
928
	}
929
930
	/**
931
	 * Creates json encoded original value with hashed column values needed for editor
932
	 *
933
	 * @param   array|object  $original  Original data array
934
	 * @param   array         $columns   All the columns from the table
935
	 *
936
	 * @return  string  Json encoded string of array values
937
	 */
938
	public static function createOriginalValueFromColumns($original = array(), $columns = array())
939
	{
940
		$data = array();
941
		$original = (array) $original;
942
943
		foreach ($columns as $column)
944
		{
945
			$data[$column] = md5(isset($original[$column]) ? $original[$column] : '');
946
		}
947
948
		return json_encode($data);
949
	}
950
951
	/**
952
	 * Checks if InnoDB database engine is installed and enabled on the MySQL server
953
	 *
954
	 * @param   string  $engine  Database Engine name
955
	 *
956
	 * @return  boolean  Returns true if InnoDB engine is active
957
	 */
958
	public static function checkIfDatabaseEngineExists($engine = 'InnoDB')
959
	{
960
		if (!isset(self::$dbEngines[$engine]))
961
		{
962
			self::$dbEngines[$engine] = false;
963
			$db = JFactory::getDbo();
964
965
			$db->setQuery('SHOW ENGINES');
966
			$results = $db->loadObjectList();
967
968
			if (!empty($results))
969
			{
970
				foreach ($results as $result)
971
				{
972
					if (strtoupper($result->Engine) == strtoupper($engine))
973
					{
974
						if (strtoupper($result->Support) != 'NO')
975
						{
976
							self::$dbEngines[$engine] = true;
977
						}
978
					}
979
				}
980
			}
981
		}
982
983
		return self::$dbEngines[$engine];
984
	}
985
986
	/**
987
	 * Get list of all translation tables with columns
988
	 *
989
	 * @param   bool  $fullLoad   Full load tables
990
	 * @param   bool  $isEnabled  If true is just return "Enabled" translation table.
991
	 *
992
	 * @return  array             Array or table with columns columns
993
	 */
994
	public static function getInstalledTranslationTables($fullLoad = false, $isEnabled = false)
995
	{
996
		static $fullLoaded;
997
998
		if ($fullLoad && (is_null($fullLoaded) || !$fullLoaded))
999
		{
1000
			$db = JFactory::getDbo();
1001
			$oldTranslate = isset($db->translate) ? $db->translate : false;
1002
1003
			// We do not want to translate this value
1004
			$db->translate = false;
1005
1006
			$query = $db->getQuery(true)
1007
				->select('tt.*')
1008
				->from($db->qn('#__redcore_translation_tables', 'tt'))
1009
				->order('tt.title');
1010
1011
			$tables = $db->setQuery($query)->loadObjectList('name');
1012
1013
			foreach ($tables as $key => $table)
1014
			{
1015
				$tables[$key]->columns = explode(',', $table->primary_columns . ',' . $table->translate_columns);
1016
				$tables[$key]->primaryKeys = explode(',', $table->primary_columns);
1017
				$tables[$key]->fallbackColumns = explode(',', $table->fallback_columns);
1018
				$tables[$key]->table = $table->name;
1019
				$tables[$key]->option = $table->extension_name;
1020
				$tables[$key]->formLinks = json_decode($table->form_links, true);
1021
1022
				if (isset(self::$translationColumns[$key]))
1023
				{
1024
					$tables[$key]->allColumns = self::$translationColumns[$key];
1025
				}
1026
			}
1027
1028
			// We put translation check back on
1029
			$db->translate = $oldTranslate;
1030
			self::$installedTranslationTables = $tables;
1031
1032
			$fullLoaded = true;
1033
		}
1034
1035
		if (!$fullLoad && !isset(self::$installedTranslationTables))
1036
		{
1037
			$db = JFactory::getDbo();
1038
			$oldTranslate = isset($db->translate) ? $db->translate : false;
1039
1040
			// We do not want to translate this value
1041
			$db->translate = false;
1042
1043
			$query = $db->getQuery(true)
1044
				->select(
1045
					array(
1046
						$db->qn('tt.translate_columns', 'columns'),
1047
						$db->qn('tt.primary_columns', 'primaryKeys'),
1048
						$db->qn('tt.fallback_columns', 'fallbackColumns'),
1049
						$db->qn('tt.name', 'table'),
1050
						$db->qn('tt.title', 'title'),
1051
						$db->qn('tt.extension_name', 'option'),
1052
						$db->qn('tt.form_links', 'formLinks'),
1053
					)
1054
				)
1055
				->from($db->qn('#__redcore_translation_tables', 'tt'))
1056
				->where('tt.state = 1')
1057
				->order('tt.title');
1058
1059
			$tables = $db->setQuery($query)->loadObjectList('table');
1060
1061
			foreach ($tables as $key => $table)
1062
			{
1063
				$tables[$key]->columns = explode(',', $table->primaryKeys . ',' . $table->columns);
1064
				$tables[$key]->primaryKeys = explode(',', $table->primaryKeys);
1065
				$tables[$key]->fallbackColumns = explode(',', $table->fallbackColumns);
1066
				$tables[$key]->formLinks = json_decode($table->formLinks, true);
1067
1068
				if (isset(self::$translationColumns[$key]))
1069
				{
1070
					$tables[$key]->allColumns = self::$translationColumns[$key];
1071
				}
1072
			}
1073
1074
			// We put translation check back on
1075
			$db->translate = $oldTranslate;
1076
			self::$installedTranslationTables = $tables;
1077
		}
1078
1079
		if ($isEnabled && $fullLoaded)
1080
		{
1081
			$tables = self::$installedTranslationTables;
1082
1083
			foreach ($tables as $key => $table)
1084
			{
1085
				if (!$table->state)
1086
				{
1087
					unset($tables[$key]);
1088
				}
1089
			}
1090
1091
			return $tables;
1092
		}
1093
1094
		return self::$installedTranslationTables;
1095
	}
1096
1097
	/**
1098
	 * Populate translation column data for the table
1099
	 *
1100
	 * @param   string  $tableName  Table name
1101
	 *
1102
	 * @return  array               Array or table with columns columns
1103
	 */
1104
	public static function setTranslationTableWithColumn($tableName)
1105
	{
1106
		$table = self::getTranslationTableByName($tableName);
1107
1108
		if ($table)
0 ignored issues
show
Bug Best Practice introduced by
The expression $table of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1109
		{
1110
			if (!isset(self::$translationColumns[$table->name]))
1111
			{
1112
				$db = JFactory::getDbo();
1113
1114
				$query = $db->getQuery(true)
1115
					->select('tc.*')
1116
					->from($db->qn('#__redcore_translation_columns', 'tc'))
1117
					->where($db->qn('translation_table_id') . ' = ' . $table->id)
1118
					->order($db->qn('id'));
1119
1120
				self::$translationColumns[$table->name] = $db->setQuery($query)->loadAssocList('name');
1121
1122
				foreach (self::$translationColumns[$table->name] as $key => $column)
1123
				{
1124
					if (!empty($column['params']))
1125
					{
1126
						self::$translationColumns[$table->name][$key] = array_merge($column, json_decode($column['params'], true));
1127
					}
1128
				}
1129
			}
1130
1131
			self::$installedTranslationTables[$table->name]->allColumns = self::$translationColumns[$table->name];
1132
1133
			return self::$installedTranslationTables[$table->name];
1134
		}
1135
1136
		return $table;
1137
	}
1138
1139
	/**
1140
	 * Get translation table with columns
1141
	 *
1142
	 * @param   int  $id  Translation table ID
1143
	 *
1144
	 * @return  array  Array or table with columns columns
1145
	 */
1146
	public static function getTranslationTableById($id)
1147
	{
1148
		$tables = self::getInstalledTranslationTables(true);
1149
1150
		foreach ($tables as $table)
1151
		{
1152
			if ($table->id == $id)
1153
			{
1154
				return $table;
1155
			}
1156
		}
1157
1158
		return null;
1159
	}
1160
1161
	/**
1162
	 * Get translation table with columns
1163
	 *
1164
	 * @param   string  $name  Translation table Name
1165
	 *
1166
	 * @return  array          Array or table with columns columns
1167
	 */
1168
	public static function getTranslationTableByName($name)
1169
	{
1170
		$tables = self::getInstalledTranslationTables(true);
1171
		$name = '#__' . str_replace('#__', '', $name);
1172
1173
		return isset($tables[$name]) ? $tables[$name] : null;
1174
	}
1175
1176
	/**
1177
	 * Set a value to translation table list
1178
	 *
1179
	 * @param   string                      $option          Extension option name
1180
	 * @param   string                      $table           Table name
1181
	 * @param   RTranslationContentElement  $contentElement  Content Element
1182
	 * @param   bool                        $notifications   Show notifications
1183
	 *
1184
	 * @return  array  Array or table with columns columns
1185
	 */
1186
	public static function setInstalledTranslationTables($option, $table, $contentElement, $notifications = false)
1187
	{
1188
		// Initialize installed tables before proceeding
1189
		self::getInstalledTranslationTables(true);
1190
		$translationTableModel = RModel::getAdminInstance('Translation_Table', array(), 'com_redcore');
1191
1192
		// If content element is empty then we delete this table from the system
1193
		if (empty($contentElement))
1194
		{
1195
			$tableObject = self::getTranslationTableByName($table);
1196
1197
			if ($tableObject)
0 ignored issues
show
Bug Best Practice introduced by
The expression $tableObject of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1198
			{
1199
				$translationTableModel->delete($tableObject->id);
0 ignored issues
show
Bug introduced by
The method delete() does not exist on RModel. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1199
				$translationTableModel->/** @scrutinizer ignore-call */ 
1200
                            delete($tableObject->id);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1200
			}
1201
1202
			unset(self::$installedTranslationTables[$table]);
1203
		}
1204
		else
1205
		{
1206
			$data = array(
1207
				'name'              => $table,
1208
				'extension_name'    => $option,
1209
				'title'             => $contentElement->name,
1210
				'version'           => $contentElement->version,
1211
				'columns'           => $contentElement->fieldsToColumns,
0 ignored issues
show
Bug introduced by
The property fieldsToColumns does not seem to exist on RTranslationContentElement.
Loading history...
1212
				'formLinks'         => $contentElement->getEditForms(),
1213
				'description'       => $contentElement->getTranslateDescription(),
1214
				'author'            => $contentElement->getTranslateAuthor(),
1215
				'copyright'         => $contentElement->getTranslateCopyright(),
1216
				'xml_path'          => RTranslationContentElement::getPathWithoutBase($contentElement->contentElementXmlPath),
1217
				'xml_hashed'        => $contentElement->xml_hashed,
1218
				'filter_query'      => implode(' AND ', (array) $contentElement->getTranslateFilter()),
1219
				'state'             => 1,
1220
				'id'                => $contentElement->tableId,
0 ignored issues
show
Bug introduced by
The property tableId does not exist on RTranslationContentElement. Did you mean table?
Loading history...
1221
				'showNotifications' => $notifications
1222
			);
1223
			$translationTableModel->save($data);
0 ignored issues
show
Bug introduced by
The method save() does not exist on RModel. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1223
			$translationTableModel->/** @scrutinizer ignore-call */ 
1224
                           save($data);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1224
		}
1225
	}
1226
}
1227