<?php
/**
 * i-doit - Documentation and CMDB solution for IT environments
 *
 * This file is part of the i-doit framework. Modify at your own risk.
 *
 * Please visit http://www.i-doit.com/license for a full copyright and license information.
 *
 * @version     1.10
 * @package     i-doit
 * @author      synetics GmbH
 * @copyright   synetics GmbH
 * @url         http://www.i-doit.com
 * @license     http://www.i-doit.com/license
 */
namespace idoit\Module\Report;

use idoit\Component\Provider\Factory;
use idoit\Module\Report\Protocol\Worker;
use idoit\Module\Report\Validate\Query;

/**
 * Report
 *
 * @package     idoit\Module\Report
 * @subpackage  Core
 * @author      Dennis Stücken <dstuecken@synetics.de>
 * @copyright   synetics GmbH
 * @license     http://www.i-doit.com/license
 * @since       1.7.1
 */
class Report
{
    use Factory;

    /**
     * @var \isys_component_dao
     */
    private $dao;

    /**
     * Report id
     *
     * @var int
     */
    private $id;

    /**
     * Report type (e.g. 'c' for custom)
     *
     * @var string
     */
    private $type;

    /**
     * @var string
     */
    private $title;

    /**
     * @var string
     */
    private $description;

    /**
     * Report SQL Query
     *
     * @var string
     */
    private $query;

    /**
     * Tenant ID
     *
     * @var int
     */
    private $tenantId;

    /**
     * @var string
     */
    private $datetime;

    /**
     * @var string
     */
    private $lastEdited;

    /**
     * @var Worker
     */
    private $worker;

    /**
     * Is report executed in html context?
     *
     * If not, don't generate any html.
     *
     * @var string
     */
    private $htmlContext = true;

    /**
     * Query execution as a generator function
     */
    public function execute()
    {
        if (!empty($this->query))
        {
            try
            {
                if (Query::validate($this->query))
                {
                    /**
                     * Increase max execution time for this report
                     * Default is 5x of php's default (3000)
                     */
                    set_time_limit(\isys_tenantsettings::get('report.max_execution_time', 3000));

                    $queryLimit = 0;

                    if (stripos($this->query, 'LIMIT'))
                    {
                        $matches = [];
                        preg_match('/LIMIT [0-9,\s]*(?=\;)/i', trim($this->query), $matches);
                        $queryLimit = (int) filter_var($matches[0], FILTER_SANITIZE_NUMBER_INT);
                        $this->query = preg_replace('/LIMIT [0-9,\s]*(?=\;)/i', '', trim($this->query));
                    }

                    if (strpos($this->query, '{'))
                    {
                        // Remove all placeholders from query like ,'{', isys_obj__id, '}'
                        $this->query = preg_replace("/\,\s*\'\s*\{\'\,\s*[\w]+\,.\'\}\'/", '', $this->query);
                    } // if

                    // Array of key translations, so the key part is only translated in first row and not in each row
                    $translatedKey = [];
                    $memory         = \idoit\Component\Helper\Memory::instance();

                    $i = $page = 0;
                    $rows = 0;
                    while (1)
                    {
                        $portions = $queryLimit > 0 ? $queryLimit : \isys_tenantsettings::get('report.file-export.portions', 325000);
                        $offset   = $page > 0 ? $portions * $page : 0;

                        // First we append an offset to the report-query.
                        $this->query = rtrim(trim($this->query), ';');
                        $limit       = ' LIMIT ' . $offset . ', ' . $portions . ';';

                        $db       = $this->dao->get_database_component();
                        $l_result = $db->query($this->query . $limit);

                        $l_num    = $db->num_rows($l_result);
                        
                        // Break if there are no more entries available
                        if ($l_num === 0 || $rows >= $portions)
                        {
                            break;
                        } // if

                        if ($l_num > 0)
                        {
                            $l_first_row = true;
                            $l_callbacks = [];

                            while ($l_row = $db->fetch_row_assoc($l_result))
                            {
                                // Break on memory overflows
                                if ($i++ % 1000 == 0) $memory->outOfMemoryBreak();

                                if ($l_first_row)
                                {
                                    $l_first_row = false;
                                    foreach ($l_row AS $l_key => $l_row_value)
                                    {
                                        $l_origin_key = $l_key;

                                        if (strpos($l_key, 'isys_cmdb_dao_category') === 0 || strpos($l_key, 'locales') === 0)
                                        {
                                            $l_key_arr = explode('::', $l_key);
                                            $l_key     = array_pop($l_key_arr);

                                            // Add Callback only if the callback class exists
                                            // This increases performance by reducing class_exists calls for each row
                                            // .. also adding the category dao instance to the callback instead of retrieving it in each row
                                            if (class_exists($l_key_arr[0]))
                                            {
                                                $l_callbacks[$l_origin_key] = [
                                                    call_user_func(
                                                        [
                                                            $l_key_arr[0],
                                                            'instance'
                                                        ],
                                                        \isys_application::instance()->database
                                                    ),
                                                    $l_key_arr[1],
                                                    $l_key_arr[2]
                                                ];
                                            }
                                            elseif($l_key_arr[0] === 'locales') // See ID-4672
                                            {
                                                $l_callbacks[$l_origin_key] = [
                                                    \isys_application::instance()->container->locales,
                                                    $l_key_arr[1]
                                                ];
                                            }
                                        } // if

                                        if ($l_cut_to = strpos($l_key, '###'))
                                        {
                                            $l_key = substr($l_key, 0, $l_cut_to);
                                        } // if

                                        if (strpos($l_key, '#'))
                                        {
                                            $l_title_arr = explode('#', $l_key);

                                            $l_title_key = implode(
                                                ' -> ',
                                                array_reverse(
                                                    array_map(
                                                        function ($val)
                                                        {
                                                            return _L($val);
                                                        },
                                                        $l_title_arr
                                                    )
                                                )
                                            );
                                        }
                                        else
                                        {
                                            $l_title_key = _L($l_key);
                                        } // if

                                        // In case the key already exists
                                        // This closes #5069
                                        // Translation has to be checked if its already been used
                                        if (in_array($l_title_key, $translatedKey))
                                        {
                                            $i = 2;
                                            while (in_array($l_title_key, $translatedKey))
                                            {
                                                $l_title_key .= ' ' . $i;
                                                $i++;
                                            } // while
                                        } // if

                                        // Increase performance by caching translation in first row, to have it then available in all remaining rows
                                        if ($l_title_key)
                                        {
                                            $translatedKey[$l_origin_key] = $l_title_key;
                                        }
                                    } // foreach
                                } // if

                                if (isset($l_row["isys_obj__id"]))
                                {
                                    $l_row["__obj_id__"] = $l_row["isys_obj__id"];
                                } // if

                                if (isset($l_row["__id__"]) && !isset($l_row["__obj_id__"]))
                                {
                                    $l_row["__obj_id__"] = $l_row["__id__"];
                                } // if

                                if (!isset($l_row["isys_obj__id"]))
                                {
                                    $l_row["isys_obj__id"] = $l_row["__id__"] ?: $l_row["__obj_id__"];
                                } // if

                                // Fixing translations.
                                $l_fixed_row = [];

                                foreach ($l_row as $l_key => $l_value)
                                {
                                    $l_title_key = $translatedKey[$l_key];
                                    if (!$l_title_key) continue;

                                    if (isset($l_callbacks[$l_key]))
                                    {
                                        if ($l_value)
                                        {
                                            $l_callback = $l_callbacks[$l_key];

                                            // Value has to be appended with the real database field key
                                            if(isset($l_callbacks[$l_key][2]) && is_string($l_callbacks[$l_key][2]))
                                            {
                                                $l_row[$l_callbacks[$l_key][2]] = $l_value;
                                            } // if

                                            if (strpos($l_key, 'locales::') !== false)
                                            {
                                                $callbackValue = $l_value;
                                            }
                                            else
                                            {
                                                $callbackValue = $l_row;
                                            }

                                            $l_value = call_user_func(
                                                [
                                                    $l_callback[0],
                                                    $l_callback[1]
                                                ],
                                                $callbackValue
                                            );
                                        }
                                        else
                                        {
                                            $l_value = '';
                                        }
                                    }
                                    else
                                    {
                                        if (strpos($l_value, 'LC_') === 0)
                                        {
                                            $l_value = _L($l_value);
                                        }
                                    }

                                    if ($this->htmlContext)
                                    {
                                        $l_fixed_row[$l_title_key] = html_entity_decode($l_value, ENT_QUOTES, $GLOBALS['g_config']['html-encoding']);

                                        // This replaces all plain text links to clickable links. See ID-2604
                                        if (strpos($l_fixed_row[$l_key], '://') !== false || strpos($l_fixed_row[$l_key], 'www') !== false)
                                        {
                                            $l_fixed_row[$l_key] = \isys_helper_textformat::link_urls_in_string($l_fixed_row[$l_key], "'");
                                        } // if

                                        // This replaces all plain text emails to clickable links. See ID-2604
                                        if (strpos($l_fixed_row[$l_key], '@') !== false)
                                        {
                                            $l_fixed_row[$l_key] = \isys_helper_textformat::link_mailtos_in_string($l_fixed_row[$l_key], "'");
                                        } // if
                                    }
                                    else
                                    {
                                        $l_fixed_row[$l_title_key] = strip_tags(
                                            preg_replace("(<script[^>]*>([\\S\\s]*?)<\/script>)", '', $l_value)
                                        );
                                    }

                                } // foreach

                                unset($l_fixed_row['__obj_id__'], $l_fixed_row['__id__']);
                                if ($this->worker) $this->worker->work($l_fixed_row);

                                unset($l_row);
                                $rows++;
                            } // while

                            // Free memory result
                            $l_result->free_result();
                        } // if

                        $page++;
                    } // while
                } // if
            }
            catch (\Exception $e)
            {
                throw $e;
            } // try
        } // if

        //$this->worker->work($row);
        //yield $row;
    }

    /**
     * @param $bool
     *
     * @return $this
     */
    public function enableHtmlContext($bool = true)
    {
        $this->htmlContext = $bool;

        return $this;
    }

    /**
     * @return string
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param int $id
     *
     * @return $this
     */
    public function setId($id)
    {
        $this->id = $id;

        return $this;
    }

    /**
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @param string $type
     *
     * @return $this
     */
    public function setType($type)
    {
        $this->type = $type;

        return $this;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * @param string $title
     *
     * @return $this
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * @param string $description
     *
     * @return $this
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * @return int
     */
    public function getTenantId()
    {
        return $this->tenantId;
    }

    /**
     * @param int $tenantId
     *
     * @return $this
     */
    public function setTenantId($tenantId)
    {
        $this->tenantId = $tenantId;

        return $this;
    }

    /**
     * @return string
     */
    public function getDatetime()
    {
        return $this->datetime;
    }

    /**
     * @param string $datetime
     *
     * @return $this
     */
    public function setDatetime($datetime)
    {
        $this->datetime = $datetime;

        return $this;
    }

    /**
     * @return string
     */
    public function getLastEdited()
    {
        return $this->lastEdited;
    }

    /**
     * @param string $lastEdited
     *
     * @return $this
     */
    public function setLastEdited($lastEdited)
    {
        $this->lastEdited = $lastEdited;

        return $this;
    }

    /**
     * @return Worker
     */
    public function getWorker()
    {
        return $this->worker;
    }

    /**
     * @param Worker $worker
     *
     * @return $this
     */
    public function setWorker(Worker $worker)
    {
        $this->worker = $worker;

        return $this;
    }

    /**
     * Report constructor.
     *
     * @param \isys_component_dao $dao
     * @param  string             $query
     */
    public function __construct(\isys_component_dao $dao, $query, $title = null, $reportId = null, $reportType = null)
    {
        $this->dao   = $dao;
        $this->query = $query;

        $this->title = $title;
        $this->id    = $reportId;
        $this->type  = $reportType;
    }

}
