<?php
    /**
    * @class ModuleHandler
    * @author zero (zero@nzeo.com)
    * @brief Handling modules
    *
    * @remarks This class is to excute actions of modules.
    *          Constructing an instance without any parameterconstructor, it finds the target module based on Context.
    *          If there is no act on the found module, excute an action referencing action_forward.
    **/

    class ModuleHandler extends Handler {

        var $oModule = NULL; ///< Module Instance

        var $module = NULL; ///< Module
        var $act = NULL; ///< action
        var $mid = NULL; ///< Module ID
        var $document_srl = NULL; ///< Document Number
        var $module_srl = NULL; ///< Module Number

        var $module_info = NULL; ///< Module Info. Object

        var $error = NULL; ///< an error code.

        /**
         * @brief constructor
         * @remarks it prepares variables to use in moduleHandler
         **/
        function ModuleHandler($module = '', $act = '', $mid = '', $document_srl = '', $module_srl = '') {
            // If XE has not installed yet, set module as install
            if(!Context::isInstalled()) {
                $this->module = 'install';
                $this->act = Context::get('act');
                return;
            }

            // Set variables from request arguments
            if(!$module) $this->module = Context::get('module');
            else $this->module = $module;

            if(!$act) $this->act = Context::get('act');
            else $this->act = $act;

            if(!$mid) $this->mid = Context::get('mid');
            else $this->mid = $mid;

            if(!$document_srl) $this->document_srl = (int)Context::get('document_srl');
            else $this->document_srl = (int)$document_srl;

            if(!$module_srl) $this->module_srl = (int)Context::get('module_srl');
            else $this->module_srl = (int)$module_srl;

            $this->entry = Context::convertEncodingStr(Context::get('entry'));

            // Validate variables to prevent XSS
            if($this->module && !preg_match("/^([a-z0-9\_\-]+)$/i",$this->module)) die(Context::getLang("msg_invalid_request"));
            if($this->mid && !preg_match("/^([a-z0-9\_\-]+)$/i",$this->mid)) die(Context::getLang("msg_invalid_request"));
            if($this->act && !preg_match("/^([a-z0-9\_\-]+)$/i",$this->act)) die(Context::getLang("msg_invalid_request"));

            // execute addon (before module initialization)
            $called_position = 'before_module_init';
            $oAddonController = &getController('addon');
            $addon_file = $oAddonController->getCacheFilePath();
            @include($addon_file);
        }

        /**
         * @brief Initialization. It finds the target module based on module, mid, document_srl, and prepares to execute an action
         * @return true: OK, false: redirected 
         **/
        function init() {
            $oModuleModel = &getModel('module');

            $site_module_info = Context::get('site_module_info');

            if(!$this->document_srl && $this->mid && $this->entry) {
                $oDocumentModel = &getModel('document');
                $this->document_srl = $oDocumentModel->getDocumentSrlByAlias($this->mid, $this->entry);
                if($this->document_srl) Context::set('document_srl', $this->document_srl);
            }

            // Get module's information based on document_srl, if it's specified
            if($this->document_srl && !$this->module) {
                $module_info = $oModuleModel->getModuleInfoByDocumentSrl($this->document_srl);

                // If the document does not exist, remove document_srl
                if(!$module_info) {
                    unset($this->document_srl);
                } else {
                    // If it exists, compare mid based on the module information
                    // if mids are not matching, set it as the document's mid
                    if($this->mid != $module_info->mid) {
                        $this->mid = $module_info->mid;
                        Context::set('mid', $module_info->mid, true);
                    }
                }
                // if requested module is different from one of the document, remove the module information retrieved based on the document number
                if($this->module && $module_info->module != $this->module) unset($module_info);
            }

            // If module_info is not set yet, and there exists mid information, get module information based on the mid
            if(!$module_info && $this->mid) {
                $module_info = $oModuleModel->getModuleInfoByMid($this->mid, $site_module_info->site_srl);
                //if($this->module && $module_info->module != $this->module) unset($module_info);
            }

            // redirect, if module_site_srl and site_srl are different
            if(!$this->module && !$module_info && $site_module_info->site_srl == 0 && $site_module_info->module_site_srl > 0) {
                $site_info = $oModuleModel->getSiteInfo($site_module_info->module_site_srl);
                header("location:".getNotEncodedSiteUrl($site_info->domain,'mid',$site_module_info->mid));
                return false;
            }

            // If module_info is not set still, and $module does not exist, find the default module
            if(!$module_info && !$this->module) $module_info = $site_module_info;

            if(!$module_info && !$this->module && $site_module_info->module_site_srl) $module_info = $site_module_info;

            // redirect, if site_srl of module_info is different from one of site's module_info
            if($module_info && $module_info->site_srl != $site_module_info->site_srl && !isCrawler()) {
                // If the module is of virtual site
                if($module_info->site_srl) {
                    $site_info = $oModuleModel->getSiteInfo($module_info->site_srl);
                    $redirect_url = getNotEncodedSiteUrl($site_info->domain, 'mid',Context::get('mid'),'document_srl',Context::get('document_srl'),'module_srl',Context::get('module_srl'),'entry',Context::get('entry'));
                // If it's called from a virtual site, though it's not a module of the virtual site
                } else {
                    $db_info = Context::getDBInfo();
                    if(!$db_info->default_url) return Context::getLang('msg_default_url_is_not_defined');
                    else $redirect_url = getNotEncodedSiteUrl($db_info->default_url, 'mid',Context::get('mid'),'document_srl',Context::get('document_srl'),'module_srl',Context::get('module_srl'),'entry',Context::get('entry'));
                }
                header("location:".$redirect_url);
                return false;
            }

            // If module info was set, retrieve variables from the module information
            if($module_info) {
                $this->module = $module_info->module;
                $this->mid = $module_info->mid;
                $this->module_info = $module_info;
                Context::setBrowserTitle($module_info->browser_title);
                $part_config= $oModuleModel->getModulePartConfig('layout',$module_info->layout_srl);
                Context::addHtmlHeader($part_config->header_script);
            }

            // Set module and mid into module_info
            $this->module_info->module = $this->module;
            $this->module_info->mid = $this->mid;

            // Still no module? it's an error
            if(!$this->module) $this->error = 'msg_module_is_not_exists';

            // If mid exists, set mid into context
            if($this->mid) Context::set('mid', $this->mid, true);
                
            // Call a trigger after moduleHandler init
            $output = ModuleHandler::triggerCall('moduleHandler.init', 'after', $this->module_info);
            if(!$output->toBool()) {
                $this->error = $output->getMessage();
                return false;
            }

            // Set current module info into context 
            Context::set('current_module_info', $this->module_info);

            return true;
        }

        /**
         * @brief get a module instance and execute an action
         * @return executed module instance
         **/
        function procModule() {
            // If error occurred while preparation, return a message instance
            if($this->error) {
                $oMessageView = &getView('message');
                $oMessageView->setError(-1);
                $oMessageView->setMessage($this->error);
                $oMessageView->dispMessage();
                return $oMessageView;
            }

            $oModuleModel = &getModel('module');

            // Get action information with conf/action.xml 
            $xml_info = $oModuleModel->getModuleActionXml($this->module);

            // If not installed yet, modify act 
            if($this->module=="install") {
                if(!$this->act || !$xml_info->action->{$this->act}) $this->act = $xml_info->default_index_act;
            } 

            // if act exists, find type of the action, if not use default index act
            if(!$this->act) $this->act = $xml_info->default_index_act;

            // still no act means error
            if(!$this->act) {
                $this->error = 'msg_module_is_not_exists';
                return;
            }

            // get type, kind
            $type = $xml_info->action->{$this->act}->type;
            $kind = strpos(strtolower($this->act),'admin')!==false?'admin':'';
            if(!$kind && $this->module == 'admin') $kind = 'admin';

            // create a module instance
            $oModule = &$this->getModuleInstance($this->module, $type, $kind);
            if(!is_object($oModule)) {
                $this->error = 'msg_module_is_not_exists';
                return;
            }

            $oModule->setAct($this->act);

            $this->module_info->module_type = $type;
            $oModule->setModuleInfo($this->module_info, $xml_info);

            // execute the action, and if failed, set error
            if(!$oModule->proc()) $this->error = $oModule->getMessage();

            return $oModule;
        }

        /**
         * @brief display contents from executed module
         * @param[in] $oModule module instance
         * @return none
         **/
        function displayContent($oModule = NULL) {
            // If the module is not set or not an object, set error
            if(!$oModule || !is_object($oModule)) {
                $this->error = 'msg_module_is_not_exists';
            }

            // If connection to DB has a problem even though it's not install module, set error
            if($this->module != 'install' && $GLOBALS['__DB__'][Context::getDBType()]->is_connected == false) {
                $this->error = 'msg_dbconnect_failed';
            }

            // Call trigger after moduleHandler proc
            $output = ModuleHandler::triggerCall('moduleHandler.proc', 'after', $oModule);
            if(!$output->toBool()) $this->error = $output->getMessage();

            // Use message view object, if HTML call
            if(!in_array(Context::getRequestMethod(),array('XMLRPC','JSON'))) {
                // If error occurred, handle it
                if($this->error) {
                    // display content with message module instance 
                    $oMessageView = &getView('message');
                    $oMessageView->setError(-1);
                    $oMessageView->setMessage($this->error);
                    $oMessageView->dispMessage();

                    // If module was called normally, change the templates of the module into ones of the message view module
                    if($oModule) {
                        $oModule->setTemplatePath($oMessageView->getTemplatePath());
                        $oModule->setTemplateFile($oMessageView->getTemplateFile());

                    // Otherwise, set message instance as the target module
                    } else {
                        $oModule = $oMessageView;
                    }
                }

                // Check if layout_srl exists for the module
                if($oModule->module_info->layout_srl && !$oModule->getLayoutFile()) {

                    // If layout_srl exists, get information of the layout, and set the location of layout_path/ layout_file 
                    $oLayoutModel = &getModel('layout');
                    $layout_info = $oLayoutModel->getLayout($oModule->module_info->layout_srl);
                    if($layout_info) {

                        // Input extra_vars into $layout_info
                        if($layout_info->extra_var_count) {

                            foreach($layout_info->extra_var as $var_id => $val) {
                                if($val->type == 'image') {
                                    if(preg_match('/^\.\/files\/attach\/images\/(.+)/i',$val->value)) $val->value = Context::getRequestUri().substr($val->value,2);
                                }
                                $layout_info->{$var_id} = $val->value;
                            }
                        }
                        // Set menus into context
                        if($layout_info->menu_count) {
                            foreach($layout_info->menu as $menu_id => $menu) {
                                if(file_exists($menu->php_file)) @include($menu->php_file);
                                Context::set($menu_id, $menu);
                            }
                        }

                        // Set layout information into context 
                        Context::set('layout_info', $layout_info);

                        $oModule->setLayoutPath($layout_info->path);
                        $oModule->setLayoutFile('layout');

                        // If layout was modified, use the modified version
                        $edited_layout = $oLayoutModel->getUserLayoutHtml($layout_info->layout_srl);
                        if(file_exists($edited_layout)) $oModule->setEditedLayoutFile($edited_layout);
                    }
                }
            }

            // Display contents 
            $oDisplayHandler = new DisplayHandler();
            $oDisplayHandler->printContent($oModule);
        }

        /**
         * @brief returns module's path
         * @param[in] $module module name
         * @return path of the module
         **/
        function getModulePath($module) {
            return sprintf('./modules/%s/', $module);
        }

        /**
         * @brief It creates a module instance
         * @param[in] $module module name
         * @param[in] $type instance type, (e.g., view, controller, model)
         * @param[in] $kind admin or svc
         * @return module instance (if failed it returns null)
         * @remarks if there exists a module instance created before, returns it.
         **/
        function &getModuleInstance($module, $type = 'view', $kind = '') {
            $class_path = ModuleHandler::getModulePath($module);
            if(!is_dir(_XE_PATH_.$class_path)) return NULL;

            if(__DEBUG__==3) $start_time = getMicroTime();

            if($kind != 'admin') $kind = 'svc';

            // if there is no instance of the module in global variable, create a new one 
            if(!$GLOBALS['_loaded_module'][$module][$type][$kind]) {
                // Get base class name and load the file contains it 
                if(!class_exists($module)) {
                    $high_class_file = sprintf('%s%s%s.class.php', _XE_PATH_,$class_path, $module);
                    if(!file_exists($high_class_file)) return NULL;
                    require_once($high_class_file);
                }

                // Get the object's name
                switch($type) {
                    case 'controller' :
                            if($kind == 'admin') {
                                $instance_name = sprintf("%sAdmin%s",$module,"Controller");
                                $class_file = sprintf('%s%s%s.admin.%s.php', _XE_PATH_, $class_path, $module, $type);
                            } else {
                                $instance_name = sprintf("%s%s",$module,"Controller");
                                $class_file = sprintf('%s%s%s.%s.php', _XE_PATH_, $class_path, $module, $type);
                            }
                        break;
                    case 'model' :
                            if($kind == 'admin') {
                                $instance_name = sprintf("%sAdmin%s",$module,"Model");
                                $class_file = sprintf('%s%s%s.admin.%s.php', _XE_PATH_, $class_path, $module, $type);
                            } else {
                                $instance_name = sprintf("%s%s",$module,"Model");
                                $class_file = sprintf('%s%s%s.%s.php', _XE_PATH_, $class_path, $module, $type);
                            }
                        break;
                    case 'api' :
                            $instance_name = sprintf("%s%s",$module,"API");
                            $class_file = sprintf('%s%s%s.api.php', _XE_PATH_, $class_path, $module);
                        break;
                    case 'wap' :
                            $instance_name = sprintf("%s%s",$module,"WAP");
                            $class_file = sprintf('%s%s%s.wap.php', _XE_PATH_, $class_path, $module);
                        break;
                    case 'smartphone' :
                            $instance_name = sprintf("%s%s",$module,"SPhone");
                            $class_file = sprintf('%s%s%s.smartphone.php', _XE_PATH_, $class_path, $module);
                        break;
                    case 'class' :
                            $instance_name = $module;
                            $class_file = sprintf('%s%s%s.class.php', _XE_PATH_, $class_path, $module);
                        break;
                    default :
                            $type = 'view';
                            if($kind == 'admin') {
                                $instance_name = sprintf("%sAdmin%s",$module,"View");
                                $class_file = sprintf('%s%s%s.admin.view.php', _XE_PATH_, $class_path, $module, $type);
                            } else {
                                $instance_name = sprintf("%s%s",$module,"View");
                                $class_file = sprintf('%s%s%s.view.php', _XE_PATH_, $class_path, $module, $type);
                            }
                        break;
                }

                // Get the name of the class file
                if(!file_exists($class_file)) return NULL;

                // Create an instance with eval function
                require_once($class_file);
                if(!class_exists($instance_name)) return NULL;
                $eval_str = sprintf('$oModule = new %s();', $instance_name);
                @eval($eval_str);
                if(!is_object($oModule)) return NULL;

                // Load language files for the class
                Context::loadLang($class_path.'lang');

                // Set variables to the instance
                $oModule->setModule($module);
                $oModule->setModulePath($class_path);

                // If the module has a constructor, run it.
                if(!isset($GLOBALS['_called_constructor'][$instance_name])) {
                    $GLOBALS['_called_constructor'][$instance_name] = true;
                    if(@method_exists($oModule, $instance_name)) $oModule->{$instance_name}();
                }

                // Store the created instance into GLOBALS variable 
                $GLOBALS['_loaded_module'][$module][$type][$kind] = $oModule;
            }

            if(__DEBUG__==3) $GLOBALS['__elapsed_class_load__'] += getMicroTime() - $start_time;

            // return the instance 
            return $GLOBALS['_loaded_module'][$module][$type][$kind];
        }

        /**
         * @brief call a trigger
         * @param[in] $trigger_name trigger's name to call
         * @param[in] $called_position called position
         * @param[in] $obj an object as a parameter to trigger
         * @return Object
         **/
        function triggerCall($trigger_name, $called_position, &$obj) {
            // skip if not installed 
            if(!Context::isInstalled()) return new Object();

            $oModuleModel = &getModel('module');
            $triggers = $oModuleModel->getTriggers($trigger_name, $called_position);
            if(!$triggers || !count($triggers)) return new Object();

            foreach($triggers as $item) {
                $module = $item->module;
                $type = $item->type;
                $called_method = $item->called_method;

                $oModule = null;
                $oModule = &getModule($module, $type);
                if(!$oModule || !method_exists($oModule, $called_method)) continue;

                $output = $oModule->{$called_method}($obj);
                if(is_object($output) && method_exists($output, 'toBool') && !$output->toBool()) return $output;
                unset($oModule);
            }

            return new Object();
        }
    }
?>
