<?php
declare(strict_types = 1);

/**
 * Édition en masse de fichiers ou de catégories.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class MassEdit
{
	/**
	 * Édition en masse de catégories.
	 *
	 * @param array $categories_id
	 * @param array $update
	 * @param string $order_by
	 *
	 * @return int
	 *   Retourne le nombre de fichiers affectés
	 *   ou -1 en cas d'erreur.
	 */
	public static function categories(array $categories_id,
	array &$update, string $order_by = ''): int
	{
		return self::_update('cat', $categories_id, $update, $order_by);
	}

	/**
	 * Édition en masse de fichiers.
	 *
	 * @param array $items_id
	 * @param array $update
	 * @param string $order_by
	 *
	 * @return int
	 *   Retourne le nombre de fichiers affectés
	 *   ou -1 en cas d'erreur.
	 */
	public static function items(array $items_id,
	array &$update, string $order_by = ''): int
	{
		return self::_update('item', $items_id, $update, $order_by);
	}



	/**
	 * Mise à jour de la base de données.
	 *
	 * @param string $prefix
	 *   Préfixe de colonne : 'item' ou 'cat'.
	 * @param array $ids
	 *   Identifiant des fichiers ou des catégories.
	 * @param array $update
	 *   Champs à mettre à jour.
	 * @param string $order_by
	 *   Clause ORDER BY de la requête SQL servant à récupérer
	 *   les informations des fichiers. Utile pour indiquer
	 *   l'ordre dans lequel seront effectuées des modifications
	 *   avec le tag {COUNTER}.
	 *   Laisser vide pour n'indiquer aucun ordre particulier.
	 *
	 * @return int
	 *   Retourne le nombre de fichiers affectés
	 *   ou -1 en cas d'erreur.
	 */
	private static function _update(string $prefix, array $ids,
	array &$update, string $order_by): int
	{
		// Début de la transaction.
		if (!DB::beginTransaction())
		{
			return -1;
		}

		$table = $prefix == 'item' ? '{items} AS i' : '{categories} AS cat';

		// Récupération des informations des fichiers.
		$cols = $prefix == 'item' ? "item_type," : "";
		$sql = "SELECT {$prefix}_id,
					   $cols
					   {$prefix}_path,
					   {$prefix}_url,
					   {$prefix}_name,
					   {$prefix}_desc
				  FROM $table
				 WHERE {$prefix}_id IN (" . DB::inInt($ids) . ")
			  ORDER BY $order_by";
		if (!DB::execute($sql))
		{
			return DB::rollback(-1);
		}
		if (!$db_infos = DB::fetchAll($prefix . '_id'))
		{
			return DB::rollback(0);
		}

		// Colonnes avec tags communs.
		$columns =
		[
			$prefix . '_desc' => '{DESCRIPTION}',
			$prefix . '_name' => '{TITLE}',
			$prefix . '_path' => $prefix == 'item' ? '{FILENAME}' : '{DIRNAME}',
			$prefix . '_url'  => '{URL}'
		];
		foreach ($columns as $col => $tag)
		{
			if (isset($update[$col]))
			{
				if (preg_match('`\{RESET\}`i', $update[$col], $m)
				&& in_array($col, [$prefix . '_name', $prefix . '_url']))
				{
					${$col . '_reset'} = 1;
				}
				else if (preg_match('`\{COUNTER:(\d{1,20})\}`i', $update[$col], $m))
				{
					${$col . '_counter'} = (int) $m[1];
					${$col . '_counter_tag'} = '{COUNTER:' . $m[1] . '}';
				}
				else if (preg_match('`\{REPLACE:([^:{}]{1,255}):([^:{}]{0,255})\}`i',
				$update[$col], $m)
				|| preg_match('`\[REPLACE#([^#\[\]]{1,255})#([^#\[\]]{0,255})\]`i',
				$update[$col], $m))
				{
					${$col . '_search'} = $m[1];
					${$col . '_replace'} = $m[2];
				}
				else if (preg_match('`\{CASE:(LOWER|UPPER|UCFIRST|UCWORDS)\}`i',
				$update[$col], $m))
				{
					${$col . '_case'} = $m[1];
				}
				else if (strtoupper(trim($update[$col])) == $tag
				|| ($col != $prefix . '_desc' && Utility::isEmpty($update[$col]))
				|| preg_match('`^\s*\{.+\}\s*$`', $update[$col]))
				{
					unset($update[$col]);
				}
			}
		}

		// Paramètres pour la mise à jour du nom de fichier.
		if ($prefix == 'item')
		{
			$image_regexp = '`.\.(' . implode('|', Image::EXT_ARRAY) . ')$`i';
			$video_regexp = '`.\.(' . implode('|', Video::EXT_ARRAY) . ')$`i';
		}
		$files_secure = [];
		$files_rename = [];
		$params_cat_rename = [];

		// On effectue les modifications.
		$params = $db_infos;
		foreach ($params as $id => &$i)
		{
			foreach ($columns as $col => $tag)
			{
				$current_val = $i[$col];

				if (!isset($update[$col]))
				{
					continue;
				}

				$i[$col] = (string) $i[$col];

				// Pour les chemins, on ne retient que le nom de fichier ou de répertoire.
				if ($col == $prefix . '_path')
				{
					$i[$col] = basename($i[$col]);
				}

				// Tag {CASE}.
				if (isset(${$col . '_case'}))
				{
					switch (strtoupper(${$col . '_case'}))
					{
						case 'LOWER' :
							$i[$col] = mb_strtolower($i[$col]);
							break;

						case 'UPPER' :
							$i[$col] = mb_strtoupper($i[$col]);
							break;

						case 'UCFIRST' :
							$i[$col] = ucfirst($i[$col]);
							break;

						case 'UCWORDS' :
							$i[$col] = ucwords($i[$col]);
							break;
					}
				}

				// Tag {REPLACE}.
				else if (isset(${$col . '_replace'}))
				{
					$i[$col] = str_ireplace(
						${$col . '_search'}, ${$col . '_replace'}, $i[$col]
					);
				}

				// Tag {RESET}.
				else if (isset(${$col . '_reset'}))
				{
					$i[$col] = $prefix == 'item'
						? Item::getTitle(basename($i['item_path']))
						: Category::getTitle(basename($i['cat_path']));
				}

				else
				{
					// Tag du champ.
					$i[$col] = str_ireplace($tag, $i[$col], $update[$col]);

					// Tag {COUNTER}.
					if (isset(${$col . '_counter'}))
					{
						$i[$col] = str_ireplace(
							${$col . '_counter_tag'}, (string) ${$col . '_counter'}, $i[$col]
						);
						${$col . '_counter'}++;
					}
				}

				// Titre et URL.
				if ($col == $prefix . '_name' || $col == $prefix . '_url')
				{
					$i[$col] = Utility::trimAll($i[$col]);
					$i[$col] = $col == $prefix . '_url' ? App::getURLName($i[$col]) : $i[$col];
					$i[$col] = mb_substr($i[$col], 0, 255);
					if (mb_strlen($i[$col]) < 1)
					{
						$i[$col] = $current_val;
					}
				}

				// Nom de fichier/répertoire.
				if ($col == $prefix . '_path')
				{
					// Si le nom de fichier/répertoire est identique,
					// inutile d'aller plus loin.
					if (basename($current_val) === $i[$col])
					{
						$i[$col] = $current_val;
						continue;
					}

					// On vérifie l'extension.
					if ($prefix == 'item')
					{
						if ((Item::isImage($i['item_type'])
							&& !preg_match($image_regexp, $i[$col]))
						 || (Item::isVideo($i['item_type'])
							&& !preg_match($video_regexp, $i[$col])))
						{
							$i[$col] = $current_val;
							continue;
						}
					}

					// Répertoire parent.
					$dir = dirname($current_val);
					$dir = $dir == '.' ? '' : $dir;

					// On génère un nouveau nom de fichier valide.
					$new_f = File::getSecureFilename(
						$prefix == 'item'
							? App::getValidFilename($i[$col])
							: App::getValidDirname($i[$col]),
						CONF_ALBUMS_PATH . '/' . $dir,
						$files_secure
					);

					// On enregistre le nouveau nom de fichier.
					$dir = $dir == '' ? '' : $dir . '/';
					$i[$col] = $dir . $new_f;
					if ($current_val !== $i[$col])
					{
						if ($prefix == 'cat')
						{
							$params_cat_rename[] =
							[
								'new_path' => $i[$col],
								'old_path' => $current_val,
								'old_path_like' => DB::likeEscape($current_val) . '/%'
							];
						}
						$files_rename[$current_val] = $i[$col];
						$files_secure[] = $new_f;
					}
				}

				// Description.
				if ($col == $prefix . '_desc')
				{
					$i[$col] = Utility::trimAll($i[$col]);
					$i[$col] = mb_substr($i[$col], 0, 65535);
					$i[$col] = Utility::isEmpty($i[$col]) ? NULL : $i[$col];
				}
			}
		}

		// On ne conserve que les fichiers et les colonnes à mettre à jour.
		foreach ($params as $id => &$i)
		{
			if ($i === $db_infos[$id])
			{
				unset($params[$id]);
				continue;
			}
			if ($prefix == 'item')
			{
				unset($params[$id]['item_type']);
			}
		}
		if (!$params)
		{
			return DB::rollback(0);
		}

		// On met à jour les fichiers.
		$sql = "UPDATE {$table}
				   SET {$prefix}_path = :{$prefix}_path,
					   {$prefix}_url  = :{$prefix}_url,
					   {$prefix}_name = :{$prefix}_name,
					   {$prefix}_desc = :{$prefix}_desc
				 WHERE {$prefix}_id   = :{$prefix}_id";
		if (!DB::execute($sql, array_values($params)))
		{
			return DB::rollback(-1);
		}

		// Dans le cas d'un renommage de répertoires de catégories,
		// on modifie le chemin des sous-catégories et des fichiers.
		if ($params_cat_rename)
		{
			$sql = "UPDATE {categories}
					   SET cat_path = REPLACE('^'||cat_path, '^'||:old_path, :new_path)
					 WHERE cat_path LIKE :old_path_like";
			if (!DB::execute($sql, $params_cat_rename))
			{
				return DB::rollback(-1);
			}

			$sql = 'UPDATE {items}
					   SET item_path = REPLACE("^"||item_path, "^"||:old_path, :new_path)
					 WHERE item_path LIKE :old_path_like';
			if (!DB::execute($sql, $params_cat_rename))
			{
				return DB::rollback(-1);
			}
		}

		// Exécution de la transaction.
		if (!DB::commitTransaction())
		{
			return DB::rollback(-1);
		}

		// On renomme les fichiers.
		foreach ($files_rename as $old_f => &$new_f)
		{
			File::rename(CONF_ALBUMS_PATH . '/' . $old_f, CONF_ALBUMS_PATH . '/' . $new_f);
		}

		return count($params);
	}
}
?>