<?php
/**
 * CodeIgniter library
 *
 * @category CodeIgniter
 * @author  Christian Koller derived from
 * @author  ExpressionEngine Dev Team
 * @copyright   Copyright (c) 2008, EllisLab, Inc.
 * @license https://codeigniter.com/user_guide/license.html MIT
 * @package libraries
 * @version $Id$
*/

if (!defined('BASEPATH')) { exit('No direct script access allowed'); }

/**
 * Add's additional functionality to the loader class
 *
 * This allows us to add some of the untermStrich functionality here
 */
#[AllowDynamicProperties]
class My_Loader extends CI_Loader
{
    /**
     * Widgets list
     */
    private $_widgets_list = false;

    const TYPE_FAULTY_PORTLET = 141658650;
    const TYPE_PORTLET = 2125837353;
    const TYPE_WIDGET_TEST_ONLY = 370415659;
    const TYPE_WIDGET = 1766461776;

    private $ignore_pdf_converter = array('.', '..', 'call_o365_helper.php', 'cloudconvert_helper.php', 'GuzzleRetryMiddleware.php');

    /**
     * Library Loader
     *
     * This function lets users load library files.
     * It does not! instantiate the class
     *
     * @param string $library The name of the class
     * @return    void
     */
    public function us_library($library)
    {
        $library = str_replace(EXT, '', trim($library, '/'));

        if (file_exists(APPPATH . 'us_libraries/' . $library . EXT)) {
            include_once(APPPATH . 'us_libraries/' . $library . EXT);

            if (strtolower($library ?? '') === 'languages') {

                $CI = CI_Controller::get_instance();
                $test_ustrich = $CI->config->item('test_ustrich');

                if ($test_ustrich == 'is_test_system26') {
                    Languages::add_for_test_system();
                }
            }

        } else {
            basic_error('Unable to load the requested library: ' . $library, My_controller::M_DIRECT, __CLASS__ . '::' . __METHOD__, 500);
        }
    }

    /**
     * Model Loader
     *
     * This function lets users load and instantiate models.
     * It passes a variable to the constructor
     *
     * @param string $model The name of the class
     * @param mixed $vars A variable to pass to the model
     * @param string $name Name for the model
     * @param boolean $db_conn Database connection
     * @return    void
     */
    public function varmodel($model, $vars, $name = '', $db_conn = false)
    {
        if (empty($name)) {
            $name = $model;
        }

        $class_name = $this->_load_model($model, $name, $db_conn);

        if ($class_name) {
            $CI = CI_Controller::get_instance();

            $CI->$name = new $class_name($vars);

            $this->_ci_models[] = $name;
        }
    }

    /**
     * Data Object Model Loader
     *
     * This function lets users load and instantiate Data_Object based models.
     * (No registry in X3 - Only fallback for portlets)
     *
     * @param string $model The name of the class
     * @param string $name Name for the model
     * @param boolean $db_conn Database connection
     * @return    void
     */
    public function registry_load_model($model, $name = '', $db_conn = false)
    {
        $this->model($model, $name, $db_conn);
    }

    /**
     * Overload a model
     *
     * This function assigns an existing model object to a CI_Controller property with the specified name.
     * It also adds the model name to the internal model tracking array.
     *
     * @param object $model The model object to overload
     * @param string $name The property name to assign the model to in the CI_Controller
     */
    function overload_model($model, $name)
    {
        $CI = CI_Controller::get_instance();

        $CI->$name = $model;
        $this->_ci_models[] = $name;
    }

    /**
     * Model Loader
     *
     * This function lets users load and instantiate models.
     *
     * @access    public
     * @param string    the name of the class
     * @param string    name for the model
     * @param bool    database connection
     * @return    void
     */
    function model($model, $name = '', $db_conn = FALSE)
    {
        if (empty($name)) {
            $name = $model;
        }

        $class_name = $this->_load_model($model, $name, $db_conn);

        if ($class_name) {
            require_once APPPATH . 'us_libraries/Factory_interface.php';

            $CI = CI_Controller::get_instance();

            $myReflection = new ReflectionClass($class_name);
            if ($myReflection->implementsInterface('Factory_interface')) {
                $CI->$name = call_user_func("$class_name::factory_build");
            } else {
                $CI->$name = new $class_name();
            }

            $this->_ci_models[] = $name;
        }
    }

    /**
     * Portlet Loader
     *
     * This function lets users load portlet files.
     * Portlet files are static - so no instantiation of the class
     *
     * @param string $portlet The name of the portlet
     * @param integer $mode One of the My_controller::m_... modes
     * @param string  &$msg Optional Reference Reply message
     * @param boolean $check_lic Optional Validate the license, if it's an external portlet
     * @param string $last_update_date Optional Last update date of widget
     * @return    string  Class name
     *
     * @see  My_controller::M_XAJAX
     * @see  My_controller::M_DIRECT
     * @uses US_Smarty
     */
    public function portlet($portlet, $mode, &$msg = null, $check_lic = false, &$last_update_date = null)
    {
        $CI = CI_Controller::get_instance();
        $CI->load->helper('us_portlet_helper');
        $CI->lang->load('pages', $CI->web_settings->getLanguage());
        $CI->load->library('Portlet_size');
        $test_ustrich = $CI->config->item('test_ustrich');

        $portlet_class = ucfirst($portlet);
        $filename = strtolower($portlet);

        $external_portlet_path = $CI->config->item('portlet_packages');

        if (file_exists(APPPATH . 'portlets/' . $filename . EXT)) {

            $portlet_dir = APPPATH;
            $is_purchased = false;

            include_once(APPPATH . 'portlets/' . $filename . EXT);

            if (!class_exists($portlet_class)) {
                $msg = 'Class "' . $portlet_class . '" missing';
                return false;
            }

        } elseif (file_exists($external_portlet_path . $filename . '/portlets/' . $filename . EXT)) {

            $portlet_dir = $external_portlet_path . $filename . '/';
            $is_purchased = true;

            if ($check_lic) {
                $this->helper('crypt');

                $key = @file_get_contents($portlet_dir . 'lic.txt');
                if (empty($key)) {
                    $msg = $CI->lang->line('auth_missing_license');
                    return false;
                }

                $key = str_replace("\r", "", $key);
                $key_parts = explode("\n", $key);
                if (count($key_parts) < 2) {
                    $msg = $CI->lang->line('auth_invalid_info');
                    return false;
                }

                if ($key_parts[0] != 'OPEN' && $key_parts[0] != $CI->installation->getSerial()) {
                    $msg = $CI->lang->line('auth_invalid_license') . '1';
                    return false;
                }

                $key2 = Two_way_crypt::decode($key_parts[1], 4);
                $key2_parts = explode('|', $key2);
                if (count($key2_parts) < 3) {
                    $msg = $CI->lang->line('auth_invalid_info');
                    return false;
                }

                if ($key2_parts[1] != 'OPEN' && $key2_parts[1] != $CI->installation->getSerial()) {
                    $msg = $CI->lang->line('auth_invalid_license') . '2';
                    return false;
                }
                if ($key2_parts[2] != strtolower($portlet_class)) {
                    $msg = $CI->lang->line('auth_invalid_license') . '3';
                    return false;
                }
            }

            if (!file_exists($portlet_dir . 'info.xml')) {
                $msg = 'info.xml file is missing';
                return false;
            }
            $portlet_info = file_get_contents($portlet_dir . 'info.xml');

            $portlet_info_tok = strtok($portlet_info, '<>');
            $in_butlerversion = false;
            $in_multilang = false;
            $in_phpverset = false;
            $in_phpminver = false;
            $in_phpmaxver = false;
            $in_minver = false;
            $in_maxver = false;
            $in_date = false;
            $butlerversion = '';
            $phpverset = '';
            $phpminver = '';
            $phpmaxver = '';
            $minver = '';
            $maxver = '';
            $date = '';
            $multilang = false;
            while (($portlet_info_tok = strtok('<>')) !== false) {
                switch ($portlet_info_tok) {
                    case 'butlerversion':
                        $in_butlerversion = true;
                        break;
                    case '/butlerversion':
                        $in_butlerversion = false;
                        break;
                    case 'phpverset':
                        $in_phpverset = true;
                        break;
                    case '/phpverset':
                        $in_phpverset = false;
                        break;
                    case 'phpminver':
                        $in_phpminver = true;
                        break;
                    case '/phpminver':
                        $in_phpminver = false;
                        break;
                    case 'phpmaxver':
                        $in_phpmaxver = true;
                        break;
                    case '/phpmaxver':
                        $in_phpmaxver = false;
                        break;
                    case 'minver':
                        $in_minver = true;
                        break;
                    case '/minver':
                        $in_minver = false;
                        break;
                    case 'maxver':
                        $in_maxver = true;
                        break;
                    case '/maxver':
                        $in_maxver = false;
                        break;
                    case 'date':
                        $in_date = true;
                        break;
                    case '/date':
                        $in_date = false;
                        break;
                    case 'multilang':
                        $in_multilang = true;
                        break;
                    case '/multilang':
                        $in_multilang = false;
                        break;
                    default:
                        if ($in_butlerversion) {
                            $butlerversion .= $portlet_info_tok;
                        }
                        if ($in_phpverset) {
                            $phpverset .= $portlet_info_tok;
                        }
                        if ($in_phpminver) {
                            $phpminver .= $portlet_info_tok;
                        }
                        if ($in_phpmaxver) {
                            $phpmaxver .= $portlet_info_tok;
                        }
                        if ($in_minver) {
                            $minver .= $portlet_info_tok;
                        }
                        if ($in_maxver) {
                            $maxver .= $portlet_info_tok;
                        }
                        if ($in_date) {
                            $date .= $portlet_info_tok;
                        }
                        if ($in_multilang) {
                            $multilang .= $portlet_info_tok;
                        }
                        break;
                }
            }

            $phpminver = floatval(trim($phpminver));
            if (empty($phpminver)) {
                $phpminver = 5.2;
            }

            $phpmaxver = floatval(trim($phpmaxver));
            if (empty($phpmaxver)) {
                $phpmaxver = 5.4;
            }

            if ($test_ustrich !== 'is_test_system26' && empty($phpverset)) {
                $phpmaxver = 5.6;
            }

            if (trim($date) === '') {
                $last_update_date = null;
            } else {
                $last_update_date = new UsDateTime($date);
            }

            $multilang = (boolean)(trim($multilang));

            $maxver = get_maxversion_for_butler($portlet_class, $butlerversion, $maxver);

            if (!empty($minver) && !validate_minversion($minver, $us_version)) {
                $msg = $CI->lang->line('portlet_please_update_x1') . "\n\n" . $CI->lang->linef('portlet_please_update_x2', $minver) . "\n" . $CI->lang->linef('portlet_please_update_x3', $us_version);
                return false;
            }

            if (!empty($maxver) && !validate_maxversion($maxver, $us_version)) {
                $msg = $CI->lang->line('portlet_please_update_php2but') . "\n\n" . $CI->lang->linef('portlet_please_update_x4', $maxver) . "\n" . $CI->lang->linef('portlet_please_update_x3', $us_version);
                return false;
            }

            $php_version_3 = floatval(substr(phpversion(), 0, 3));

            if ($php_version_3 < $phpminver) {
                $msg = $CI->lang->line('portlet_please_update_php1') . "\n\n" . $CI->lang->linef('portlet_please_update_php2', $phpminver) . "\n" . $CI->lang->linef('portlet_please_update_php3', phpversion());
                return false;
            }

            if ($php_version_3 > $phpmaxver) {
                $msg = $CI->lang->linef('portlet_please_update_php1but', $phpmaxver) . "\n\n" . $CI->lang->line('portlet_please_update_php2but') . "\n" . $CI->lang->linef('portlet_please_update_php3', phpversion());
                return false;
            }

            unset($portlet_info);

            if (!$this->check_file_for_errors($external_portlet_path . $filename . '/portlets/' . $filename . EXT, false)) {
                $msg = 'Syntax error in external portlet "' . $filename . '".';
                return false;
            }
            include_once($external_portlet_path . $filename . '/portlets/' . $filename . EXT);

            if (!class_exists($portlet_class)) {
                $msg = 'External class "' . $portlet_class . '" missing.';
                return false;
            }

            $this->add_package_path($external_portlet_path . $filename);
            if ($multilang) {
                $CI->lang->load($filename, $CI->web_settings->getLanguage());
            } else {
                $CI->lang->load($filename);
            }
        } else {

            $no_err = (isset($msg)) ? true : false; //No basic error, if msg is caught
            $msg = 'Unable to load the requested portlet: ' . $external_portlet_path . $filename . '/portlets/' . $filename . EXT;
            if ($no_err) {
                return false;
            } else {
                basic_error($msg, $mode, __CLASS__ . '::' . __METHOD__, 500);
            }
        }

        $portlet_class::assign($CI, $mode, $portlet_dir, $is_purchased);

        return $portlet_class;
    }

    /**
     * Does the external portlet exists
     *
     * @param string $portlet The name of the portlet
     * @return  boolean
     */
    public function external_portlet_exists($portlet)
    {
        $CI = CI_Controller::get_instance();
        $CI->load->helper('us_portlet_helper');

        $filename = strtolower($portlet);

        $external_portlet_path = $CI->config->item('portlet_packages');

        return file_exists($external_portlet_path . $filename . '/portlets/' . $filename . EXT) ? true : false;
    }

    /**
     * Try to test a PHP file for errors
     *
     * @param string $filename File name
     * @param boolean $use_php_l Optional If no runkit_lint_file, also use php -l
     * @return  boolean
     */
    public function check_file_for_errors($filename, $use_php_l = true)
    {
        if (function_exists('runkit_lint_file')) {
            return runkit_lint_file($filename);
        } elseif ($use_php_l) {
            $test = @shell_exec('php -l ' . escapeshellarg($filename) . ' 2>&1');
            if (stripos($test, 'error:') !== false) {
                return false;
            }
            if (stripos($test, 'Errors parsing') !== false) {
                return false;
            }
            return true;
        } else {
            return true;
        }
    }

    /**
     * Get a list of all portlets
     *
     * @param boolean $merge_main_external Optional Merge main and external
     * @return  object
     */
    protected function list_portlets($merge_main_external = true)
    {
        $CI = CI_Controller::get_instance();
        $this->helper('file');

        $main_list = array();
        $main_dir = get_filenames(APPPATH . 'portlets');
        foreach ($main_dir as $main) {
            array_push($main_list, str_replace(EXT, '', $main));
        }

        $ignore = array('.', '..', 'rp_insert_staff', 'universal_import', 'b_project', 'get_update', 'key_data_dia', 'meetingmanager', 'oauth_login', 'updateinfo_view');
        $main_list = array_diff($main_list, $ignore);

        $external_portlet_path = $CI->config->item('portlet_packages');
        $external_dirs = glob($external_portlet_path . '*', GLOB_ONLYDIR | GLOB_NOSORT);
        $external_dirs = array_map('basename', $external_dirs);

        $ignore = array('.', '..', 'index.html', '.svn', '.htaccess', 'web_config.php', '.ds_store', '.DS_Store', 'build.php', 'create_lic.php', 'creator.php', 'pub_index.php', 'com_untermstrich_psp', 'com_untermstrich_cost_overview', 'com_untermstrich_nodejs', 'com_untermstrich_employee_hourly_rate');
        $external_list = array_diff($external_dirs, $ignore);

        if ($merge_main_external) {
            return array_merge($main_list, $external_list);
        } else {
            return array($main_list, $external_list);
        }
    }

    /**
     * Get external config portlets
     *
     * @return  array
     */
    public function list_external_config_portlets()
    {
        $CI = CI_Controller::get_instance();
        $CI->load->helper('xml_errors');

        list($main_list, $external_list) = $this->list_portlets(false);

        $external_portlet_path = $CI->config->item('portlet_packages');

        $reply = array();
        foreach ($external_list as $item) {
            if (file_exists($external_portlet_path . $item . '/external.xml')) {

                libxml_use_internal_errors(true);
                $xml = simplexml_load_file($external_portlet_path . $item . '/info.xml');

                $errors = libxml_get_errors();
                if (!empty($errors)) {
                    xml_error_basic_msg('The config is not a valid XML. ERR:2', $errors, __CLASS__ . '::' . __FUNCTION__);
                }
                if ($xml->getName() != 'info') {
                    xml_error_basic_msg('The config xml is not valid. ERR:3', $errors, __CLASS__ . '::' . __FUNCTION__);
                }

                $reply[$item] = $xml;
            }
        }

        return $reply;
    }

    /**
     * Get all widgets
     *
     * @param integer $mode One of the My_controller::m_... modes
     * @param boolean $just_removable Optional Get all widtgets or just the removable ones
     * @return  array
     */
    public function list_widgets($mode, $just_removable = true)
    {
        $this->_load_widget_details($mode);

        $widgets = array();

        foreach ($this->_widgets_list as $widget_item) {
            if ($widget_item['type'] === self::TYPE_WIDGET) {
                if ($just_removable && !$widget_item['removable']) {
                    continue; //Ignore not removable if $just_removable
                }
                if (!$widget_item['has_right']) {
                    continue;//Validate rights
                }
                array_push($widgets, $widget_item);
            }
        }

        return $widgets;
    }

    /**
     * Get all widgets as an ASSOC array
     *
     * @param integer $mode One of the My_controller::m_... modes
     * @return  array
     */
    public function list_widgets_assoc($mode)
    {
        $this->_load_widget_details($mode);

        $widgets = array();

        foreach ($this->_widgets_list as $widget_item) {
            if ($widget_item['type'] === self::TYPE_WIDGET) {
                if (!$widget_item['has_right'] || !$widget_item['removable']) {
                    continue;//Validate rights
                }
                $widgets[$widget_item['class']] = $widget_item;
            }
        }

        return $widgets;
    }

    /**
     * Get all purchased (external) widgets/portlets - faulty or not
     * Ignores test only
     *
     * @param integer $mode One of the My_controller::m_... modes
     * @return  array       Assoc array with file name as key
     */
    public function list_all_widgets_portlet($mode)
    {
        $this->_load_widget_details($mode);

        $widgets = array();

        foreach ($this->_widgets_list as $widget_item) {
            if ($widget_item['type'] === self::TYPE_WIDGET_TEST_ONLY) {
                continue; //Ignore test only
            }

            if (!$widget_item['is_purchased']) {
                log_message('info', 'Not purchased ' . $widget_item['name']);
                continue; //Ignore non purchased = external
            }
            $widgets[$widget_item['file']] = $widget_item;
        }

        return $widgets;
    }

    /**
     * Get all faulty widgets
     *
     * @param integer $mode One of the My_controller::m_... modes
     * @return  array
     */
    public function list_faulty_widgets($mode)
    {
        $this->_load_widget_details($mode);

        $widgets = array();

        foreach ($this->_widgets_list as $widget_item) {
            if ($widget_item['type'] === self::TYPE_FAULTY_PORTLET) {
                array_push($widgets, $widget_item);
            }
        }

        return $widgets;
    }

    /**
     * Ensure that the widget details are loaded
     *
     * @param integer $mode One of the My_controller::m_... modes
     * @return type
     */
    private function _load_widget_details($mode)
    {
        if ($this->_widgets_list !== false) {
            return;
        }

        $CI = CI_Controller::get_instance();

        $this->_widgets_list = $this->_widgets_read_cache($CI);
        if ($this->_widgets_list !== false) {
            return;
        }

        $test_ustrich = $CI->config->item('test_ustrich');

        $this->_widgets_list = array();
        list($main_list, $external_list) = $this->list_portlets(false);

        foreach ($main_list as $portlet) {
            array_push($this->_widgets_list, self::load_widget_details_portlet($CI, $mode, $test_ustrich, $portlet, false));
        }
        foreach ($external_list as $portlet) {
            array_push($this->_widgets_list, self::load_widget_details_portlet($CI, $mode, $test_ustrich, $portlet, true));
        }

        $this->_widgets_write_cache($CI, $this->_widgets_list);
    }

    /**
     * Process single portlet for _load_widget_details
     *
     * @param object $CI CI main object
     * @param integer $mode One of the My_controller::m_... modes
     * @param boolean $test_ustrich Test ustrich mode
     * @param string $portlet Portlet name
     * @param boolean $is_purchased Portlet is external (purchased)
     * @return  array
     */
    public static function load_widget_details_portlet($CI, $mode, $test_ustrich, $portlet, $is_purchased)
    {
        require_once APPPATH . 'us_libraries/Widget_interface.php';
        require_once APPPATH . 'us_libraries/React_portlet.php';

        $reply = array(
            'file' => $portlet,
            'name' => ucfirst(str_ireplace(array('com_untermstrich_', '_'), array('', ' '), $portlet)),
            'removable' => false,
            'has_right' => false,
            'is_purchased' => $is_purchased,
        );


        $msg = '';
        $last_update_date = null;
        $portlet_class = $CI->load->portlet($portlet, $mode, $msg, false, $last_update_date);
        if ($portlet_class === false) {
            $reply['type'] = self::TYPE_FAULTY_PORTLET;
            $reply['error_msg'] = $msg;
            return $reply;
        }

        $reply['class'] = $portlet_class;
        $reply['last_update_date'] = $last_update_date;

        $myReflection = new ReflectionClass($portlet_class);
        if (!$myReflection->implementsInterface('Widget_interface')) {
            $reply['type'] = self::TYPE_PORTLET;
            return $reply;
        }

        $reply['type'] = self::TYPE_WIDGET;
        $reply['is_react'] = $myReflection->isSubclassOf('React_portlet');

        $widget_information = self::_get_widget_information_with_size($portlet_class);
        $reply['widget_information'] = $widget_information;

        if (isset($widget_information->title)) {
            $widget_information->title_lang = $CI->lang->line($widget_information->title);
        }

        if ($test_ustrich !== 'is_test_system26') {
            if (isset($widget_information->test)) {
                $reply['type'] = self::TYPE_WIDGET_TEST_ONLY;
                return $reply;
            }
        }

        $widget_information->is_purchased = call_user_func($portlet_class . '::get_is_purchased');

        $reply['removable'] = $widget_information->removable;


        if (isset($widget_information->complex_right)) {

            $reply['has_right'] = $widget_information->complex_right;

        } elseif (isset($widget_information->right)) {

            $detail_level = Login_model::D_FULL;
            if (isset($widget_information->right->detail_level)) {
                $detail_level = $widget_information->right->detail_level;
            }

            if ($CI->login->has_right($widget_information->right->module, $widget_information->right->level, $detail_level) !== true) {
                return $reply;
            }

            if (
                isset($widget_information->right->no_respect_project_manager) &&
                $widget_information->right->no_respect_project_manager &&
                ($CI->login->getbool('respect_project_manager') || $CI->login->getbool('respect_division_manager'))
            ) {
                return $reply;
            }

            $reply['has_right'] = true;
        }

        return $reply;
    }

    private static function _get_widget_information_with_size($portlet_class)
    {
        $widget_information = call_user_func($portlet_class . '::get_widget_information');

        if (!isset($widget_information->size) || !($widget_information->size instanceof Portlet_size)) {
            $widget_information->size = new Portlet_size();
        }

        return $widget_information;
    }

    /**
     * Loads a page
     *
     * @param string $section The section to load
     * @param string $page Optional The page to load
     * @param integer $mode Optional One of the My_controller::m_... modes
     * @return  mixed   Either the page instance or void
     *
     * @see My_controller::M_XAJAX
     * @see My_controller::M_DIRECT
     */
    public function page($section, $page = false, $mode = null)
    {
        $CI = CI_Controller::get_instance();

        $section = str_replace(EXT, '', trim($section ?? '', '/'));
        if ((substr(strtolower($section ?? ''), 0, 11) === 'butlerpage_')) {
            $butler_page_id = intval(substr($section, 11));
            $section = substr($section, 0, 10);
        } else {
            $butler_page_id = 1; //default for main butler page
        }
        $section_file = strtolower($section ?? '');

        if (!file_exists(APPPATH . 'pages/' . $section_file . EXT)) {
            basic_error('Unable to load the requested file: application/pages/' . $section_file . EXT, $mode, __CLASS__ . '::' . __FUNCTION__, 500);
        }

        include_once(APPPATH . 'pages/' . $section_file . EXT);

        if (!class_exists($section)) {
            basic_error('Non-existent section class: ' . $section, $mode, __CLASS__ . '::' . __FUNCTION__, 500);
        }

        $this->_ci_classes[$section] = $section;

        if (strtolower($section) === 'butlerpage') {
            $thepageinstance = new $section($CI, $butler_page_id);
        } else {
            $thepageinstance = new $section($CI);
        }

        if ($page === false) {
            $CI->login->log_call_action($section, 'NOPAGE');
            return $thepageinstance;
        }

        $CI->login->log_call_action($section, $page);

        if ($section === 'Masterdatamanagement_md_external') {
            $thepageinstance->router($mode, $page);
        } else {
            if (!method_exists($thepageinstance, $page)) {
                basic_error('Unable to load the requested page: ' . $page . ' in section: ' . $section, $mode, __CLASS__ . '::' . __FUNCTION__, 500);
            }

            $thepageinstance->{$page}($mode);
        }
    }

    /**
     * Load but not!! init a model
     *
     * @param string $model The model
     * @param string $name Name of the model within the CI object
     * @param boolean $db_conn Database connection
     * @return  string  The name of the model or empty
     */
    private function _load_model($model, $name, $db_conn)
    {
        if (is_array($model)) {
            foreach ($model as $babe) {
                $this->model($babe, $name, $db_conn);
            }
            return;
        }

        if ($model == '') {
            return '';
        }

        if (strpos($model, '/') === false) {
            $path = '';
        } else {
            $x = explode('/', $model);
            $model = end($x);
            unset($x[count($x) - 1]);
            $path = implode('/', $x) . '/';
        }

        if (in_array($name, $this->_ci_models, true)) {
            return '';
        }

        $CI = CI_Controller::get_instance();
        if (isset($CI->$name)) {
            basic_error('The model name you are loading is the name of a resource that is already being used: ' . $name, My_controller::M_DIRECT, __CLASS__ . '::' . __METHOD__, 500);
        }

        $model = strtolower($model);

        foreach ($this->_ci_model_paths as $mod_path) {
            if (!file_exists($mod_path . 'models/' . $path . $model . EXT)) {
                continue;
            }

            if ($db_conn !== FALSE and !class_exists('CI_DB')) {
                if ($db_conn === TRUE) {
                    $db_conn = '';
                }

                $CI->load->database($db_conn, FALSE, TRUE);
            }

            if (!class_exists('CI_Model')) {
                load_class('Model', 'core');
            }

            require_once($mod_path . 'models/' . $path . $model . EXT);

            return ucfirst($model);
        }
    }

    /**
     * Load a report creator
     *
     * @param string $name Name of the creator
     * @return  void
     */
    public function report_creator($name)
    {
        $full_name = 'report_creators/' . ucfirst($name) . '_creator';
        $this->library($full_name, null, 'creator');
    }

    /**
     * Load a correspondence creator
     *
     * @param integer $type_id Optional Correspondence type ID
     * @param string $file_extension Optional Current file extension
     * @param string|null &$classvar Optional Reference Found classvar name
     * @return  void
     */
    public function correspondence_creator($type_id = false, $file_extension = false, ?string &$classvar = null)
    {
        $CI = CI_Controller::get_instance();

        $CI->load->model('Correspondence_types_model', 'correspondence_types', true);

        if ($file_extension !== false) {
            $file_extension = trim(strtolower($file_extension ?? ''));
            if ($file_extension === 'docx' || $file_extension === 'docm') {
                $type = Correspondence_types_model::TYPE_DOCX;
                $extension = $file_extension;
            } elseif ($file_extension === 'xlsx' || $file_extension === 'xlsm') {
                $type = Correspondence_types_model::TYPE_XLSX;
                $extension = $file_extension;
            } elseif ($file_extension === 'ods') {
                $type = Correspondence_types_model::TYPE_ODS;
                $extension = $file_extension;
            } elseif ($file_extension === 'odt') {
                $type = Correspondence_types_model::TYPE_ODT;
                $extension = $file_extension;
            } elseif ($file_extension === 'html' || $file_extension === 'htm') {
                $type = Correspondence_types_model::TYPE_HTML;
                $extension = $file_extension;
            } elseif ($file_extension === 'doc') {
                $type = Correspondence_types_model::TYPE_OLD;
                $extension = 'doc';
            } elseif ($file_extension === 'xls') {
                $type = Correspondence_types_model::TYPE_OLD;
                $extension = 'xls';
            } elseif ($file_extension === 'x_test_x_any' || $file_extension === 'pdf') {
                $type = Correspondence_types_model::TYPE_ANY;
                $extension = 'any';
            } elseif ($file_extension === 'x_test_x_fail') {
                $type = Correspondence_types_model::TYPE_FAIL;
                $extension = 'fail';
            } elseif ($type_id === false) {
                basic_error('Load correspondence_creator needs either type ID or a valid known file extension.', null, __CLASS__ . '::' . __METHOD__, 500);
            } else {
                $type = $CI->correspondence_types->get_corr_type($type_id, $extension);
            }
        } elseif ($type_id === false) {
            basic_error('Load correspondence_creator needs either type ID or file extension.', null, __CLASS__ . '::' . __METHOD__, 500);
        } else {
            $type = $CI->correspondence_types->get_corr_type($type_id, $extension);
        }

        switch ($type) {
            case Correspondence_types_model::TYPE_OLDER:
            case 'txm':
            case 'ust':
                $name = 'older';
                break;
            case Correspondence_types_model::TYPE_OLD:
            case Correspondence_types_model::TYPE_DOC:
            case Correspondence_types_model::TYPE_XLS:
                $name = 'old';
                break;
            case Correspondence_types_model::TYPE_FAIL:
                $name = 'fail';
                break;
            case Correspondence_types_model::TYPE_HTML:
                $name = 'html';
                break;
            case Correspondence_types_model::TYPE_DOCX:
                $name = 'docx';
                break;
            case Correspondence_types_model::TYPE_XLSX:
                $name = 'xlsx';
                break;
            case Correspondence_types_model::TYPE_ODT:
                $name = 'odt';
                break;
            case Correspondence_types_model::TYPE_ODS:
                $name = 'ods';
                break;
            case Correspondence_types_model::TYPE_ANY:
                $name = 'any';
                break;
            default:
                basic_error('Unable to locate the type you have specified: ' . $type, null, __CLASS__ . '::' . __METHOD__, 500);
        }

        if ($file_extension === false) {
            $file_extension = $extension;
        }

        if ($extension === Correspondence_types_model::TYPE_DOCX && $file_extension === Correspondence_types_model::TYPE_DOCX) {
            $name = 'docx';
        }
        if ($extension === Correspondence_types_model::TYPE_DOCM && $file_extension === Correspondence_types_model::TYPE_DOCM) {
            $name = 'docx';
        }
        if ($extension === Correspondence_types_model::TYPE_XLSX && $file_extension === Correspondence_types_model::TYPE_XLSX) {
            $name = 'xlsx';
        }
        if ($extension === Correspondence_types_model::TYPE_XLSM && $file_extension === Correspondence_types_model::TYPE_XLSM) {
            $name = 'xlsx';
        }

        $classvar = ucfirst($name) . '_creator';
        $full_name = 'correspondence_creators/' . $classvar;

        if (class_exists($classvar)) {
            $CI->corr_creator = new $classvar;
        } else {
            $this->library($full_name, null, 'corr_creator');
        }
    }

    /**
     * Load a transmittal form type creator
     *
     * @param string $type Correspondence type
     * @return  void
     */
    public function transmittal_form_type_creator($type)
    {
        switch (strtolower($type ?? '')) {
            case Correspondence_types_model::TYPE_FAIL:
                $name = 'fail';
                break;
            case Correspondence_types_model::TYPE_HTML:
                $name = 'html';
                break;
            case Correspondence_types_model::TYPE_DOCX:
                $name = 'docx';
                break;
            case Correspondence_types_model::TYPE_XLSX:
                $name = 'xlsx';
                break;
            case Correspondence_types_model::TYPE_ODT:
                $name = 'odt';
                break;
            case Correspondence_types_model::TYPE_ANY:
                $name = 'any';
                break;
            default:
                basic_error('Unable to locate the form type you have specified: ' . $type, My_controller::M_DIRECT, __CLASS__ . '::' . __METHOD__, 500);
        }

        $full_name = 'correspondence_creators/' . ucfirst($name) . '_creator';
        $this->library($full_name, null, 'corr_creator');
    }

    /**
     * Unload CI loaded (model, library)
     *
     * @param string $classvar Class variable name
     */
    public function unload($classvar)
    {
        $CI = CI_Controller::get_instance();
        unset($CI->$classvar);

        $key = array_search($classvar, $this->_ci_models);
        if ($key !== false) {
            unset($this->_ci_models[$key]);
        }
    }

    /**
     * Load PDF Converter
     *
     * This function loads the specified helper file.
     *
     * @param string $converter The name of the converter
     * @return    array($converter_info, $pdf_convert)
     */
    public function pdf_converter($converter)
    {
        foreach ($this->_ci_prep_filename($converter, '_helper') as $helper) {
            $ext_helper_file = $helper . EXT;
            $ext_helper = APPPATH . 'helpers/pdf_converter/' . $ext_helper_file;

            if (in_array($ext_helper_file, $this->ignore_pdf_converter)) {
                continue;
            }

            if (!file_exists($ext_helper)) {
                show_error('Unable to load the requested file: helpers/pdf_converter/' . $helper);
            }

            include($ext_helper);

            if (!isset($converter_info) && !is_callable($converter_info)) {
                show_error('The requested file: helpers/pdf_converter/' . $helper . ' is missing the method converter_info');
            }
            if (!isset($pdf_convert) && !is_callable($pdf_convert)) {
                show_error('The requested file: helpers/pdf_converter/' . $helper . ' is missing the method pdf_convert');
            }

            return array($converter_info, $pdf_convert);
        }
    }

    /**
     * List all possible converters
     *
     * @return array
     */
    public function list_pdf_converter()
    {
        $CI = CI_Controller::get_instance();
        $CI->load->helper('directory');

        $found_files = directory_map(APPPATH . 'helpers/pdf_converter', 1, false);
        if ($found_files === false || $found_files === null) {
            return array();
        }

        $found_files = array_diff($found_files, $this->ignore_pdf_converter);

        $reply = array();
        foreach ($found_files as $found_file) {
            $file_info = pathinfo($found_file);
            if ($file_info['extension'] === 'php') {
                $prep_name = str_replace('_helper', '', $file_info['filename']);

                list($converter_info, $pdf_convert) = $this->pdf_converter($prep_name);
                $converter_info_arr = $converter_info();

                if (!$converter_info_arr['is_possible']) {
                    continue;
                }

                $reply_item = new stdClass();
                $reply_item->id = $prep_name;
                $reply_item->name = $converter_info_arr['title'];
                array_push($reply, $reply_item);
            }
        }
        return $reply;
    }

    /**
     * Save the widgets_list
     *
     * @param object $CI CI main object
     * @param array $widgets_list The widgets list
     * @return  boolean     Success
     */
    private function _widgets_write_cache($CI, $widgets_list)
    {
        $CI = CI_Controller::get_instance();
        $CI->load->library('Portlet_size');

        $cachedir = $this->_widgets_cache_ensure_dir();
        if ($cachedir === false) {
            return false;
        }
        $cache_name = $this->_widgets_cache_unique($CI);
        $cachefile = $cachedir . $cache_name . '.info';

        if (write_file($cachefile, serialize($widgets_list)) === false) {
            return false;
        }

        @chmod($cachefile, DIR_WRITE_MODE);
        return true;
    }

    /**
     * Retrieve a cached widgets list
     *
     * @param object $CI CI main object
     * @return  boolean|array       False or the widgets list
     */
    private function _widgets_read_cache($CI)
    {
        $CI = CI_Controller::get_instance();
        $CI->load->library('Portlet_size');

        $cachedir = $this->_widgets_cache_ensure_dir();
        if ($cachedir === false) {
            return false;
        }
        $cache_name = $this->_widgets_cache_unique($CI);
        $cachefile = $cachedir . $cache_name . '.info';

        try {
            if (file_exists($cachefile)) {
                $cachedata = file_get_contents($cachefile);
                log_error_unserialize();
                return unserialize($cachedata);
            }
        } catch (Exception $exc) {
            log_message('error', '_widgets_read_cache read from cache resulted in an error: ' . $exc->getMessage() . ' in row ' . $exc->getLine());
        }
        return false;
    }

    /**
     * Create the dir and return path
     *
     * @return  boolean|string      False or path
     */
    private function _widgets_cache_ensure_dir()
    {
        $dir_path = WRITEABLEPATH . 'cache/widgets/';
        if (!@is_dir($dir_path)) {
            if (!@mkdir($dir_path, DIR_WRITE_MODE)) {
                return false;
            }

            @chmod($dir_path, DIR_WRITE_MODE);
        }
        return $dir_path;
    }

    /**
     * Get the unique key for the cache
     *
     * @param object $CI CI main object
     * @param boolean $staff_id Optional staff_id
     * @return  string
     */
    private function _widgets_cache_unique($CI, $staff_id = false)
    {
        $staff_id = ($staff_id !== false) ? $staff_id : $CI->login->get_user_id();
        $unique = 'u' . $staff_id . '_';
        $unique .= $CI->login->get_rights_compressed();
        $language = $CI->web_settings->getLanguage();
        $unique .= us_substr($language, 0, 2) . us_substr($language, -1);
        $unique .= us_substr(str_replace('.', '', $CI->config->item('us_version')), 0, 9);

        $unique = str_replace('00000', 'a', $unique);
        $unique = str_replace('11111', 'b', $unique);
        $unique = str_replace('22222', 'c', $unique);
        $unique = str_replace('33333', 'd', $unique);
        $unique = str_replace('99999', 'e', $unique);
        $unique = str_replace('00', 'f', $unique);
        $unique = str_replace('11', 'g', $unique);

        return $unique;
    }

    /**
     * Delete all existing cache files
     *
     * @return  void
     */
    public function widgets_cache_delete_all()
    {
        $CI = CI_Controller::get_instance();
        $CI->load->model('sse_model', 'sse');

        $this->helper('file');

        $cachedir = $this->_widgets_cache_ensure_dir();
        if ($cachedir === false) {
            return false;
        }

        delete_files($cachedir, true);

        $this->_widgets_list = false;

        $CI->sse->add_new_butler_refresh_event();
    }

    /**
     * Delete existing cache files for unique_id
     *
     * @return  void
     */
    public function widgets_cache_delete_unique(array $staff_ids = null)
    {
        $CI = CI_Controller::get_instance();
        $CI->load->model('sse_model', 'sse');
        $this->helper('file');

        $cachedir = $this->_widgets_cache_ensure_dir();
        if ($cachedir === false) {
            return false;
        }

        if ($staff_ids === null) {
            $staff_ids = array($CI->login->get_user_id());
        }

        foreach ($staff_ids as $staff_id) {
            $cache_name = $this->_widgets_cache_unique($CI, $staff_id);
            $cachefile = $cachedir . $cache_name . '.info';

            if (file_exists($cachefile)) {
                @unlink($cachefile);
            }
        }

        if (in_array($CI->login->get_user_id(), $staff_ids)) {
            $this->_widgets_list = false;
        }

        $CI->sse->add_new_butler_refresh_event($staff_ids);
    }

    /**
     * Load a filesystem object
     *
     * @param string $path Load filesystem object by path
     * @param Filesystem_access $filesystem_access Optional access information
     * @return  object
     */
    public function filesystem($path, Filesystem_access $filesystem_access = null)
    {
        require_once(APPPATH . 'libraries/filesystem_creators/Filesystem_creator.php');

        $type = Filesystem_creator::get_type($path);
        $file_system = $type . '_filesystem_creator';
        require_once(APPPATH . 'libraries/filesystem_creators/' . $file_system . EXT);
        return new $file_system($path ?? '', $filesystem_access);
    }
}
