<?php

/*
 * Copyright 2005 - 2025 Centreon (https://www.centreon.com/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * For more information : contact@centreon.com
 *
 */

use Adaptation\Database\Connection\Collection\QueryParameters;
use Adaptation\Database\Connection\Exception\ConnectionException;
use Adaptation\Database\Connection\ValueObject\QueryParameter;
use Core\Common\Domain\Exception\CollectionException;
use Core\Common\Domain\Exception\RepositoryException;
use Core\Common\Domain\Exception\ValueObjectException;
use enshrined\svgSanitize\Sanitizer;

if (! isset($oreon)) {
    exit();
}

function sanitizeFilename($filename)
{
    return str_replace(
        [' ', '/', '\\'],
        '_',
        $filename
    );
}

function sanitizePath($path)
{
    return str_replace(
        ['#', '/', '\\'],
        '_',
        $path
    );
}

function extractDir($zipfile, $path)
{
    if (file_exists($zipfile)) {
        $files = [];
        $zip = new ZipArchive();
        if ($zip->open($zipfile) === true) {
            return (bool) ($zip->extractTo($path) === true);
            $zip->close();
        } else {
            return false;
        }
    } else {
        return false;
    }
}

function isValidImage($filename)
{
    if (! $filename) {
        return false;
    }
    $imginfo = getimagesize($filename);

    if (isset($imginfo) && $imginfo !== false) {
        return true;
    }

    return is_gd2($filename);

    return false;
}

function is_gd2($filename)
{
    if (getimagesize($filename) !== false) {
        return false;
    }
    $gd_res = imagecreatefromgd2($filename);
    if ($gd_res) {
        imagedestroy($gd_res);

        return true;
    }

    return false;
}

function insertImg($src_dir, $src_file, $dst_dir, $dst_file, $img_comment = '')
{
    global $pearDB;
    $mediadir = './img/media/';

    if (! ($dir_id = testDirectoryExistence($dst_dir))) {
        $dir_id = insertDirectory($dst_dir);
    }

    $dst_file = sanitizeFilename($dst_file);
    $dst  = $mediadir . $dst_dir . '/' . $dst_file;
    if (is_file($dst)) {
        return false;
    } // file exists
    if (! rename($src_dir . $src_file, $dst)) {
        return false;
    } // access denied, path error

    $img_parts = explode('.', $dst_file);
    $img_name = $img_parts[0];

    $prepare = $pearDB->prepare(
        'INSERT INTO view_img (img_name, img_path, img_comment) VALUES '
        . '(:image_name, :image_path, :image_comment)'
    );
    $prepare->bindValue(':image_name', $img_name, PDO::PARAM_STR);
    $prepare->bindValue(':image_path', $dst_file, PDO::PARAM_STR);
    $prepare->bindValue(':image_comment', $img_comment, PDO::PARAM_STR);

    $prepare->execute();
    $image_id = $pearDB->lastInsertId();

    $prepare2 = $pearDB->prepare(
        'INSERT INTO view_img_dir_relation (dir_dir_parent_id, img_img_id) '
        . 'VALUES (:dir_id, :image_id)'
    );
    $prepare2->bindValue(':dir_id', $dir_id, PDO::PARAM_INT);
    $prepare2->bindValue(':image_id', $image_id, PDO::PARAM_INT);
    $prepare2->execute();

    return $image_id;
}

function deleteMultImg($images = [])
{
    foreach (array_keys($images) as $selector) {
        $id = explode('-', $selector);
        if (count($id) != 2) {
            continue;
        }
        deleteImg($id[1]);
    }
}

function deleteImg($imageId)
{
    if (! isset($imageId)) {
        return;
    }
    $imageId = (int) $imageId;

    global $pearDB;

    $mediadir = './img/media/';
    $query = <<<'SQL'
            SELECT dir.dir_alias, img.img_path
            FROM view_img img
            INNER JOIN view_img_dir_relation rel ON img.img_id = rel.img_img_id
            INNER JOIN view_img_dir dir ON rel.dir_dir_parent_id = dir.dir_id
            WHERE img.img_id = :imageId
        SQL;

    $dbResult = $pearDB->prepare($query);
    $dbResult->bindValue(':imageId', $imageId, PDO::PARAM_INT);
    $dbResult->execute();
    foreach ($dbResult->fetchAll(PDO::FETCH_ASSOC) as $imagePath) {
        $fullpath = $mediadir . basename($imagePath['dir_alias']) . '/' . basename($imagePath['img_path']);
        if (is_file($fullpath)) {
            unlink($fullpath);
        }
    }

    $pearDB->beginTransaction();
    try {
        $deleteStatement = $pearDB->prepare('DELETE FROM view_img WHERE img_id = :imageId');
        $deleteStatement->bindValue(':imageId', $imageId, PDO::PARAM_INT);
        $deleteStatement->execute();

        $deleteRelationStatement = $pearDB->prepare('DELETE FROM view_img_dir_relation WHERE img_img_id = :imageId');
        $deleteRelationStatement->bindValue(':imageId', $imageId, PDO::PARAM_INT);
        $deleteRelationStatement->execute();
        $pearDB->commit();
    } catch (PDOException $e) {
        $pearDB->rollBack();

        throw $e;
    }
}

function moveMultImg($images, $dirName)
{
    if (count($images) > 0) {
        foreach ($images as $id) {
            moveImg($id, $dirName);
        }
    }
}

function moveImg($img_id, $dir_alias)
{
    if (! $img_id) {
        return;
    }
    global $pearDB;
    $mediadir = './img/media/';
    $prepare = $pearDB->prepare(
        'SELECT dir_id, dir_alias, img_path, img_comment '
        . 'FROM view_img, view_img_dir, view_img_dir_relation '
        . 'WHERE img_id = :image_id AND img_id = img_img_id '
        . 'AND dir_dir_parent_id = dir_id'
    );
    $prepare->bindValue(':image_id', $img_id, PDO::PARAM_INT);

    if (! $prepare->execute()) {
        return;
    }
    $img_info = $prepare->fetch(PDO::FETCH_ASSOC);
    $image_info_path = basename($img_info['img_path']);
    $image_info_dir_alias = basename($img_info['dir_alias']);
    $dir_alias = $dir_alias ? sanitizePath($dir_alias) : $image_info_dir_alias;
    if ($dir_alias != $img_info['dir_alias']) {
        $oldpath = $mediadir . $image_info_dir_alias . '/' . $image_info_path;
        $newpath = $mediadir . $dir_alias . '/' . $image_info_path;

        if (! file_exists($newpath)) {
            /**
             * Only if file doesn't already exist in the destination
             */
            if (! testDirectoryExistence($dir_alias)) {
                $dir_id = insertDirectory($dir_alias);
            } else {
                $prepare2 = $pearDB->prepare(
                    'SELECT dir_id FROM view_img_dir WHERE dir_alias = :dir_alias'
                );
                $prepare2->bindValue(':dir_alias', $dir_alias, PDO::PARAM_STR);

                if (! $prepare2->execute()) {
                    return;
                }
                $dir_info = $prepare2->fetch();
                $dir_id = $dir_info['dir_id'];
            }

            if (rename($oldpath, $newpath)) {
                $prepare2 = $pearDB->prepare(
                    'UPDATE view_img_dir_relation SET dir_dir_parent_id = :dir_id '
                    . ' WHERE img_img_id = :image_id'
                );
                $prepare2->bindValue(':dir_id', $dir_id, PDO::PARAM_INT);
                $prepare2->bindValue(':image_id', $img_id, PDO::PARAM_INT);
                $prepare2->execute();
            }
        }
    }
}

function testDirectoryCallback($name)
{
    return testDirectoryExistence($name) == 0;
}

function testDirectoryExistence($name)
{
    global $pearDB;
    $dir_id = 0;
    $prepare = $pearDB->prepare(
        'SELECT dir_name, dir_id FROM view_img_dir WHERE dir_name = :dir_name'
    );
    $prepare->bindValue(':dir_name', $name, PDO::PARAM_STR);
    $prepare->execute();
    $result = $prepare->fetch(PDO::FETCH_ASSOC);
    if (isset($result['dir_id'])) {
        $dir_id = $result['dir_id'];
    }

    return $dir_id;
}

function testDirectoryIsEmpty($directoryId)
{
    if (! $directoryId) {
        return true;
    }
    global $pearDB;

    $statement = $pearDB->prepare('SELECT img_img_id FROM view_img_dir_relation WHERE dir_dir_parent_id = :directoryId');
    $statement->bindValue(':directoryId', $directoryId, PDO::PARAM_INT);
    $statement->execute();
    $empty = ($statement->fetchColumn() > 0) ? false : true;
    $statement->closeCursor();

    return $empty;
}

function insertDirectory($dir_alias, $dir_comment = '')
{
    global $pearDB;
    $mediadir = './img/media/';
    $dir_alias_safe = basename($dir_alias);
    @mkdir($mediadir . $dir_alias_safe);
    if (is_dir($mediadir . $dir_alias_safe)) {
        touch($mediadir . $dir_alias_safe . '/index.html');
        $prepare = $pearDB->prepare(
            'INSERT INTO view_img_dir (dir_name, dir_alias, dir_comment) VALUES '
            . '(:dir_alias, :dir_alias, :dir_comment)'
        );
        $prepare->bindValue(':dir_alias', $dir_alias_safe, PDO::PARAM_STR);
        $prepare->bindValue(':dir_comment', $dir_comment, PDO::PARAM_STR);
        $prepare->execute();
        $dir_id = $pearDB->lastInsertId();

        return $dir_id;
    }

    return '';
}

function deleteMultDirectory($dirs = [])
{
    foreach (array_keys($dirs) as $selector) {
        $id = explode('-', $selector);
        if (count($id) != 1) {
            continue;
        }
        deleteDirectory($id[0]);
    }
}

function deleteDirectory($directoryId)
{
    global $pearDB;
    $mediadir = './img/media/';
    $directoryId = (int) $directoryId;

    // Purge images of the directory
    $statement1 = $pearDB->prepare('SELECT img_img_id FROM view_img_dir_relation WHERE dir_dir_parent_id = :directoryId');
    $statement1->bindValue(':directoryId', $directoryId, PDO::PARAM_INT);
    $statement1->execute();
    while ($img = $statement1->fetch()) {
        deleteImg($img['img_img_id']);
    }
    $statement1->closeCursor();

    // Delete directory
    $statement2 = $pearDB->prepare('SELECT dir_alias FROM view_img_dir WHERE dir_id = :directoryId');
    $statement2->bindValue(':directoryId', $directoryId, PDO::PARAM_INT);
    $statement2->execute();
    $dirAlias = $statement2->fetch();
    $statement2->closeCursor();

    $safeDirAlias = basename($dirAlias['dir_alias']);
    $filenames = scandir($mediadir . $safeDirAlias);
    foreach ($filenames as $fileName) {
        if (is_file($mediadir . $safeDirAlias . '/' . $fileName)) {
            unlink($mediadir . $safeDirAlias . '/' . $fileName);
        }
    }
    rmdir($mediadir . $safeDirAlias);

    if (! is_dir($mediadir . $safeDirAlias)) {
        $statement3 = $pearDB->prepare('DELETE FROM view_img_dir WHERE dir_id = :directoryId');
        $statement3->bindValue(':directoryId', $directoryId, PDO::PARAM_INT);
        $statement3->execute();
    }
}

function updateDirectory($dir_id, $dir_alias, $dir_comment = '')
{
    if (! $dir_id) {
        return;
    }

    global $pearDB;
    $mediadir = './img/media/';
    $prepare = $pearDB->prepare('SELECT dir_alias FROM view_img_dir WHERE dir_id = :dir_id ');
    $prepare->bindValue(':dir_id', $dir_id, PDO::PARAM_INT);
    $prepare->execute();
    $old_dir = $prepare->fetch(PDO::FETCH_ASSOC);
    $dir_alias = sanitizePath($dir_alias);
    if (! is_dir($mediadir . $old_dir['dir_alias'])) {
        mkdir($mediadir . $dir_alias);
    } else {
        rename($mediadir . $old_dir['dir_alias'], $mediadir . $dir_alias);
    }

    if (is_dir($mediadir . $dir_alias)) {
        $prepare = $pearDB->prepare(
            'UPDATE view_img_dir SET dir_name = :dir_name, '
            . 'dir_alias = :dir_alias, dir_comment = :dir_comment '
            . 'WHERE dir_id = :dir_id'
        );
        $prepare->bindValue(':dir_name', $dir_alias, PDO::PARAM_STR);
        $prepare->bindValue(':dir_alias', $dir_alias, PDO::PARAM_STR);
        $prepare->bindValue(':dir_comment', $dir_comment, PDO::PARAM_STR);
        $prepare->bindValue(':dir_id', $dir_id, PDO::PARAM_INT);
        $prepare->execute();
    }
}

/**
 * @param null|mixed $filter
 *
 * @throws RepositoryException
 * @return array<int|string,mixed>
 */
function getListDirectory($filter = null): array
{
    global $pearDB, $centreon;

    try {
        $queryParameters = [];

        if (
            $centreon->user->admin === '1'
            || $centreon->user->access->hasAccessToAllImageFolders
        ) {
            $query = <<<'SQL'
                    SELECT
                        directories.dir_id,
                        directories.dir_name
                    FROM view_img_dir AS `directories`
                SQL;
        } else {
            $query = <<<'SQL'
                    SELECT
                        directories.dir_id,
                        directories.dir_name
                    FROM view_img_dir AS `directories`
                    INNER JOIN acl_resources_image_folder_relations armdr
                        ON armdr.dir_id = directories.dir_id
                    INNER JOIN acl_resources ar
                        ON ar.acl_res_id = armdr.acl_res_id
                    INNER JOIN acl_res_group_relations argr
                        ON argr.acl_res_id = ar.acl_res_id
                    LEFT JOIN acl_group_contacts_relations gcr
                        ON gcr.acl_group_id = argr.acl_group_id
                    LEFT JOIN acl_group_contactgroups_relations gcgr
                        ON gcgr.acl_group_id = argr.acl_group_id
                    LEFT JOIN contactgroup_contact_relation cgcr
                        ON cgcr.contactgroup_cg_id = gcgr.cg_cg_id
                        AND (cgcr.contact_contact_id = :contactId OR gcr.contact_contact_id = :contactId)
                SQL;
            $queryParameters[] = QueryParameter::int('contactId', $centreon->user->user_id);
        }

        // Handling a potential filter even though I do not know where it comes from (no BC break).
        if ($filter !== null && $filter !== '') {
            $query .= <<<'SQL'
                    WHERE directories.dir_name LIKE :filter AND directories.dir_name NOT IN ('centreon-map', 'ppm', 'dashboards')
                SQL;
            $queryParameters[] = QueryParameter::string('filter', '%' . $filter . '%');
        } else {
            $query .= <<<'SQL'
                    WHERE directories.dir_name NOT IN ('centreon-map', 'ppm', 'dashboards')
                SQL;
        }

        $query .= ' GROUP BY directories.dir_id ORDER BY directories.dir_name';

        return $pearDB->fetchAllKeyValue($query, QueryParameters::create($queryParameters));
    } catch (ValueObjectException|CollectionException|ConnectionException $e) {
        throw new RepositoryException(
            message: 'Unable to retrieve the list of directories',
            context: ['filter' => $filter],
            previous: $e
        );
    }
}

/**
 * Check MIME Type of file
 *
 * @param array $file
 * @return bool
 */
function isCorrectMIMEType(array $file): bool
{
    $mimeTypeFileExtensionConcordance = [
        'svg' => 'image/svg+xml',
        'jpg' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'gif' => 'image/gif',
        'png' => 'image/png',
        'zip' => 'application/zip',
        'gzip' => 'application/x-gzip',
    ];
    $fileExtension = end(explode('.', $file['name']));
    if (! array_key_exists($fileExtension, $mimeTypeFileExtensionConcordance)) {
        return false;
    }

    $mimeType = mime_content_type($file['tmp_name']);
    if (
        ! preg_match('/(^image\/(jpg|jpeg|svg\+xml|gif|png)$)|(^application(\/zip)|(\/x-gzip)$)/', $mimeType)
        || (preg_match('/^image\//', $mimeType) && $mimeType !== $mimeTypeFileExtensionConcordance[$fileExtension])
    ) {
        return false;
    }
    $dir = sys_get_temp_dir() . '/pendingMedia';
    switch ($mimeType) {
        // .zip archive
        case 'application/zip':
            $zip = new ZipArchive();
            if (isValidMIMETypeFromArchive($dir, $file['tmp_name'], $zip) === true) {
                return true;
            }
            // remove the pending images from tmp
            removeRecursiveTempDirectory($dir);

            return false;
            break;
            // .tgz archive
        case 'application/x-gzip':
            // Append an extension to temp file to be able to instanciate a PharData object
            $archiveNewName = $file['tmp_name'] . '.tgz';
            rename($file['tmp_name'], $archiveNewName);
            $tar = new PharData($archiveNewName);
            if (isValidMIMETypeFromArchive($dir, null, null, $tar) === true) {
                // remove the .tgz from tmp
                unlink($archiveNewName);

                return true;
            }
            // remove the .tgz and the pending images from tmp
            unlink($archiveNewName);
            removeRecursiveTempDirectory($dir);

            return false;
            break;
            // single image
        case 'image/svg+xml':
            $sanitizer = new Sanitizer();
            $uploadedSVG = file_get_contents($file['tmp_name']);
            $cleanSVG = $sanitizer->sanitize($uploadedSVG);
            file_put_contents($file['tmp_name'], $cleanSVG);

            return true;
            break;
        case 'image/jpeg':
            /**
             * use @ to avoid PHP Warning log and instead log a more suitable error in centreon-web.log
             */
            $image = @imagecreatefromjpeg($file['tmp_name']);
            if (! $image) {
                CentreonLog::create()->error(
                    CentreonLog::TYPE_BUSINESS_LOG,
                    'Unable to validate image, your image may be corrupted',
                    [
                        'mime_type' => $mimeType,
                        'filename' => $file['name'],
                        'extension' => $fileExtension,
                    ]
                );

                return false;
            }

            return true;
            break;
        case 'image/png':
            /**
             * use @ to avoid PHP Warning log and instead log a more suitable error in centreon-web.log
             */
            $image = @imagecreatefrompng($file['tmp_name']);
            if (! $image) {
                CentreonLog::create()->error(
                    CentreonLog::TYPE_BUSINESS_LOG,
                    'Unable to validate image, your image may be corrupted',
                    [
                        'mime_type' => $mimeType,
                        'filename' => $file['name'],
                        'extension' => $fileExtension,
                    ]
                );

                return false;
            }

            return true;
            break;
        case 'image/gif':
            /**
             * use @ to avoid PHP Warning log and instead log a more suitable error in centreon-web.log
             */
            $image = @imagecreatefromgif($file['tmp_name']);
            if (! $image) {
                CentreonLog::create()->error(
                    CentreonLog::TYPE_BUSINESS_LOG,
                    'Unable to validate image, your image may be corrupted',
                    [
                        'mime_type' => $mimeType,
                        'filename' => $file['name'],
                        'extension' => $fileExtension,
                    ]
                );

                return false;
            }

            return true;
            break;
        default:
            return true;
    }
}

/**
 * Remove a directory and its content
 *
 * @param string $dir
 * @return void
 */
function removeRecursiveTempDirectory(string $dir): void
{
    if (is_dir($dir)) {
        $objects = scandir($dir);
        foreach ($objects as $object) {
            if ($object !== '.' && $object !== '..') {
                if (is_dir($dir . DIRECTORY_SEPARATOR . $object) && ! is_link($dir . DIRECTORY_SEPARATOR . $object)) {
                    removeRecursiveTempDirectory($dir . DIRECTORY_SEPARATOR . $object);
                } else {
                    unlink($dir . DIRECTORY_SEPARATOR . $object);
                }
            }
        }
        rmdir($dir);
    }
}

/**
 * Extract an archive and check the MIME Type of every files
 *
 * @param string $dir
 * @param string $filename
 * @param ZipArchive $zip
 * @param PharData $tar
 * @return bool
 */
function isValidMIMETypeFromArchive(
    string $dir,
    ?string $filename = null,
    ?ZipArchive $zip = null,
    ?PharData $tar = null,
): bool {
    $files = [];

    /**
     * Remove Pending images directory to avoid images duplication problems.
     */
    if (file_exists($dir)) {
        removeRecursiveTempDirectory($dir);
    }

    $files = [];
    if (isset($zip)) {
        if ($zip->open($filename) === true && $zip->extractTo($dir) === true) {
            $files = array_diff(scandir($dir), ['..', '.']);
        } else {
            return false;
        }
    } elseif (isset($tar)) {
        if ($tar->extractTo($dir) === true) {
            $files = array_diff(scandir($dir), ['..', '.']);
        } else {
            return false;
        }
    }

    $mimeTypeFileExtensionConcordance = [
        'svg' => 'image/svg+xml',
        'jpg' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'gif' => 'image/gif',
        'png' => 'image/png',
        'zip' => 'application/zip',
        'gzip' => 'application/x-gzip',
    ];

    foreach ($files as $file) {
        $fileExtension = end(explode('.', $file));
        if (! array_key_exists($fileExtension, $mimeTypeFileExtensionConcordance)) {
            return false;
        }

        $mimeType = mime_content_type($dir . '/' . $file);
        if (
            ! preg_match('/(^image\/(jpg|jpeg|svg\+xml|gif|png)$)/', $mimeType)
            || (preg_match('/^image\//', $mimeType) && $mimeType !== $mimeTypeFileExtensionConcordance[$fileExtension])
        ) {
            return false;
        }
        switch ($mimeType) {
            case 'image/svg+xml':
                $sanitizer = new Sanitizer();
                $uploadedSVG = file_get_contents($file['tmp_name']);
                $cleanSVG = $sanitizer->sanitize($uploadedSVG);
                file_put_contents($file['tmp_name'], $cleanSVG);
                break;
            case 'image/jpeg':
                /**
                 * use @ to avoid PHP Warning log and instead log a more suitable error in centreon-web.log
                 */
                $image = @imagecreatefromjpeg($file['tmp_name']);
                if (! $image) {
                    CentreonLog::create()->error(
                        CentreonLog::TYPE_BUSINESS_LOG,
                        'Unable to validate image, your image may be corrupted',
                        [
                            'filename' => $file['name'],
                            'extension' => $fileExtension,
                        ]
                    );

                    return false;
                }
                break;
            case 'image/png':
                /**
                 * use @ to avoid PHP Warning log and instead log a more suitable error in centreon-web.log
                 */
                $image = @imagecreatefrompng($file['tmp_name']);
                if (! $image) {
                    CentreonLog::create()->error(
                        CentreonLog::TYPE_BUSINESS_LOG,
                        'Unable to validate image, your image may be corrupted',
                        [
                            'filename' => $file['name'],
                            'extension' => $fileExtension,
                        ]
                    );

                    return false;
                }
                break;
            case 'image/gif':
                /**
                 * use @ to avoid PHP Warning log and instead log a more suitable error in centreon-web.log
                 */
                $image = @imagecreatefromgif($file['tmp_name']);
                if (! $image) {
                    CentreonLog::create()->error(
                        CentreonLog::TYPE_BUSINESS_LOG,
                        'Unable to validate image, your image may be corrupted',
                        [
                            'filename' => $file['name'],
                            'extension' => $fileExtension,
                        ]
                    );

                    return false;
                }
                break;
            default:
                return true;
        }
    }

    return true;
}

/**
 * Format all the pending images as an array usable by CentreonImageManager
 *
 * @param string $tempDirectory
 * @return array
 */
function getFilesFromTempDirectory(string $tempDirectory): array
{
    $directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $tempDirectory;
    $filesInfo = [];
    if (is_dir($directory)) {
        $files = array_diff(scandir($directory), ['..', '.']);
        foreach ($files as $file) {
            $filesInfo[] = [
                'filename' => [
                    'name' => $file,
                    'tmp_name' => $file,
                    'size' => filesize($directory . DIRECTORY_SEPARATOR . $file),
                ],
            ];
        }
    }

    return $filesInfo;
}
