<?PHP

/**
 * Openbizx Framework
 *
 * LICENSE
 *
 * This source file is subject to the BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @package   openbiz.bin
 * @copyright Copyright (c) 2005-2011, Rocky Swen
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
 * @link      http://www.phpopenbiz.org/
 * @version   $Id: ClientProxy.php 5190 2013-01-19 15:57:13Z hellojixian@gmail.com $
 */

namespace Openbizx\web;

use Openbizx\Openbizx;
use Openbizx\Helpers\DeviceUtil;
use Openbizx\I18n\I18n;

/**
 * ClientProxy class
 *
 * A class that is treated as the bi-direction proxy of client. Through this class,
 * others can get client form inputs, redraw client form or call client javascript functions.
 *
 * @package   openbiz.bin
 * @author    Rocky Swen <rocky@phpopenbiz.org>
 * @copyright Copyright (c) 2005-2009, Rocky Swen
 * @access    public
 */
class ClientProxy
{

    protected $requestArguments;
    protected $formInputArray = null;
    protected $isRpc = false;

    /**
     * Associate array of HTML content to be printed
     *
     * @var array
     */
    private $_formsOutput = array(); // key-value array
    private $_otherOutput = array(); // index array

    /**
     * Associate array of Javascript and Javascript references
     *
     * @var array
     */
    private $_extraScripts = array();

    /**
     * Associate array of CSS and CSS references
     *
     * @var array
     */
    private $_extraStyles = array();

    /**
     * Check has form rendered?
     *
     * Form has rendered if _formsOutput[$formName] exist.
     * See {@link ReDrawForm} method.
     *
     * @param string $formName
     * @return boolean true if form rendered (formName exist on _formsOutput)
     */
    public function hasFormRerendered($formName)
    {
        return isset($this->_formsOutput[$formName]);
    }

    /**
     * Get form output.
     *
     * @param string $formName
     * @return string HTML output of form
     */
    public function getFormOutput($formName)
    {
        return $this->_formsOutput[$formName];
    }

    /**
     * Check has other output?
     * NOTE: NYU - not yet used
     *
     * @return boolean true if has other output
     */
    public function hasOtherOutput()
    {
        return count($this->_otherOutput) > 0;
    }

    /**
     * Check has output (other output and/or forms output)
     *
     * @return boolean true if has output
     */
    public function hasOutput()
    {
        return count($this->_otherOutput) + count($this->_formsOutput) > 0;
    }

    /**
     * Print output.
     *
     * @return void
     */
    public function printOutput()
    {
        if ($this->isRpc == true) {
            return $this->printJSONOuput();
        }
        foreach ($this->_otherOutput as $output) {
            print $output;
        }
        foreach ($this->_formsOutput as $output) {
            print $output;
        }
    }

    protected function printJSONOuput()
    {
        $outs = array();
        // output JSON
        foreach ($this->_otherOutput as $output) {
            $outs[] = $output;
        }
        foreach ($this->_formsOutput as $output) {
            $outs[] = $output;
        }
        if (function_exists("json_encode")) {
            echo json_encode($outs);
        } else {
            require_once 'Zend/Json.php';
            echo \Zend_Json::encode($outs);
        }
    }

    /**
     * Set RPC flag.
     *
     * @param boolean $flag
     * @return void
     */
    public function setRpcFlag($flag)
    {
        $this->isRpc = $flag;
    }

    /**
     * Get request parameter,
     * get the client form data passed by GET or POST
     * NOTE: NYU - not yet used
     *
     * @param string $name name of parameter
     * @return string value of paramater
     */
    public function getRequestParam($name)
    {
        $val = (isset($_REQUEST[$name]) ? $_REQUEST[$name] : "");
        return $val;
    }

    /**
     * Get form all inputs or one input if controlName is given
     *
     * @param string $controlName
     * @param boolean $toString - Convert array oriented form controls to string?
     * @return array|string
     */
    public function getFormInputs($controlName = null, $toString = TRUE)
    {
        if ($controlName) {
            if (isset($_GET[$controlName])) {
                $_POST[$controlName] = $_GET[$controlName];
            }
            if (isset($_POST[$controlName])) {
                if (is_array($_POST[$controlName]) and $toString == TRUE) {
                    $array_string = '';
                    foreach ($_POST[$controlName] as $rec) {
                        $array_string .= $rec . ",";
                    }
                    $result = substr($array_string, 0, strlen($array_string) - 1);
                } else {
                    $result = $_POST[$controlName];
                }
                if (get_magic_quotes_gpc() == 1) {
                    if (!is_array($result)) {
                        $result = stripslashes($result);
                    }
                }
                return $result;
            } else {
                if (isset($_FILES[$controlName])) {
                    $result = true;
                    return $result;
                } else {
                    return null;
                }
            }
        } else {
            return $_POST;
        }
    }

    /**
     * Update the form elements/controls on the client UI
     * Return to client browser : array or string
     *
     * @param string $formName - name of the html form on client
     * @param array $recArr - name/value pairs
     * @return void
     */
    public function updateFormElements($formName, $recArr, $rawData = false)
    {
        if ($this->isRpc) {
            if ($rawData) {
                $fieldsOutput = $recArr;
                $this->_otherOutput[] = $this->buildTargetContent($formName, $fieldsOutput);
            } else {
                foreach ($recArr as $fld => $val) {
                    $fieldsOutput[] = $this->buildTargetContent($fld, $val);
                }
                $this->_otherOutput[] = $this->buildTargetContent($formName, $fieldsOutput);
            }
        }
    }

    /**
     * Replace the form content with the provided html text,
     * encoded html string returns to browser,
     * it'll be processed by client javascript.
     *
     * @param string $formName - name of the html form on client
     * @param string $sHTML - html text to redraw the form
     * @return void
     */
    public function redrawForm($formName, $sHTML)
    {
        if ($this->isRpc) {
            $this->_formsOutput[$formName] = $this->buildTargetContent($formName, $sHTML);
        } else {
            $this->_formsOutput[$formName] = $sHTML;
        }
    }

    /**
     * Show popup an alert window on the client browser.
     * Encoded html string returns to browser, it'll be processed by client javascript.
     *
     * @param string $alertText
     * @return void
     */
    public function showClientAlert($alertText)
    {
        if ($this->isRpc) {
            $msg = addslashes($alertText);
            $this->_otherOutput[] = $this->callClientFunction("alert('" . $msg . "')");
        }
    }

    /**
     * Show error message.
     *
     * If its a remote call, it uses javascript
     * If it is not a remote call, it outputs to the page's html
     *
     * @todo Hard code for error form : "common.form.ErrorPopupForm"
     *       change to flexible ErrorPopupForm, save in Class variable and maybe also in configuration
     *
     * @param char $errMsg
     * @return void
     *
     */
    public function showErrorMessage($errMsg)
    {
        if (!$errMsg) {
            return;
        }
        if ($this->isRpc) {
            $_GET['ob_err_msg'] = $errMsg;
            ob_end_clean();
            $form = "common.form.ErrorPopupForm";
            $html = Openbizx::getObject($form)->render();
            $html = $this->buildTargetContent("DIALOG", $html);
            $this->_formsOutput[] = $html;
        } else {
            $this->_errorOutput($errMsg);
        }
    }

    /**
     * Show popup window
     *
     * @param string $baseForm
     * @param string $popupForm
     * @param string $ctrlName
     * @return void
     */
    public function showPopup($baseForm, $popupForm, $ctrlName = "")
    {
        if ($this->isRpc) {
            $function = $baseForm . ".ShowPopup(" . $popupForm . "," . $ctrlName . ")";
            $this->_otherOutput[] = $this->callClientFunction("CallFunction('$function','Popup')");
        }
    }

    /*
      public function runClientScript($script, $type = 'Form')
      {
      if ($this->isRPC) {
      if ($type == 'Form') {
      $this->formsOutput[] = $this->callClientFunction($script);
      } elseif ($type == 'Other') {
      $this->otherOutput[] = $this->callClientFunction($script);
      }
      } else {
      echo $script;
      }
      }
     */

    /**
     * Show HTML error message
     * This method call by {@link ShowErrorMessage} if RPC flag false.
     *
     * @param string $errMsg error message
     * @return void
     */
    private function _errorOutput($errMsg)
    {
        //ob_clean();
        //Openbizx::$app->isInitialized = false;
        if (defined('OPENBIZ_INTERNAL_ERROR_VIEW') && Openbizx::$app->isInitialized) {
            //render the view
            $_GET['ob_err_msg'] = $errMsg;
            ob_end_clean();
            Openbizx::getObject(OPENBIZ_INTERNAL_ERROR_VIEW)->render();
            exit;
        } else {
            echo $errMsg;
            echo "<input type='button' NAME='btn_back' VALUE='Go back' onClick='history.go(-1);return true;'>";
        }
    }

    /**
     * Close popup window
     *
     * @return void
     */
    public function closePopup()
    {
        if ($this->isRpc) {
            $this->_formsOutput[] = $this->callClientFunction("Openbizx.Window.closePopup()");
            $this->_otherOutput[] = $this->callClientFunction("Openbizx.Window.closePopup()");
        }
    }

    /**
     * Show popup window
     *
     * @param string $content
     * @param number $w width of window
     * @param number $h height of window
     * @return void
     */
    public function showPopupWindow($content, $w, $h)
    {
        if ($this->isRpc) {
            $this->_formsOutput[] = $this->callClientFunction("popupWindow(\"$content\", $w, $h)");
        }
    }

    /**
     * Update client element
     *
     * @param string $elementId
     * @param string $content
     * @return void
     */
    public function updateClientElement($elementId, $content)
    {
        $scriptStr = "<script>$('" . $elementId . "').innerHTML='" . $content . "';</script>";
        $this->runClientScript($scriptStr);
    }

    /**
     * Run client script
     *
     * @param string $scriptStr
     * @return void
     */
    public function runClientScript($scriptStr)
    {
        if ($this->isRpc) {
            $this->_otherOutput[] = $this->buildTargetContent("SCRIPT", $scriptStr);
        } else {
            echo $scriptStr;
        }
    }

    public function runClientFunction($scriptStr)
    {
        if ($this->isRpc) {
            $this->_otherOutput[] = $this->callClientFunction($scriptStr);
        }
    }

    /**
     * Call client function
     *
     * @param string $funcStr
     * @return string target content
     */
    private function callClientFunction($funcStr)
    {
        if ($this->isRpc) {
            return $this->buildTargetContent("FUNCTION", $funcStr);
        }
    }

    /**
     * Build target-content array with target string and content string as inputs.
     * After RPC call, this function is usually called to set the HTML text to an UI element.
     *
     * @param string $target the HTML element id, i.e. the form id
     * @param string $content the HTML text to be set as the content of the HTML element referred with the id
     * @return array
     * */
    private function buildTargetContent($target, &$content)
    {
        return array(
            'target' => $target,
            'content' => $content
        );
    }

    /**
     * Redirect page to the given url.
     * Encoded html string returns to browser, it'll be processed by client javascript.
     * NOTE: NYU - not yet used
     *
     * @param string $pageURL
     * @return void
     */
    public function redirectPage($pageURL)
    {
        if (!$this->isRpc) {
            $func = (isset($_REQUEST['F']) ? $_REQUEST['F'] : "");
            if ($func == "RPCInvoke") {
                $this->isRpc = true;
            }
        }
        if (!$this->isRpc) {
            ob_clean();
            header("Location: $pageURL");
            return;
        }
        if ($pageURL == "#back") {
            $this->_otherOutput[] = $this->callClientFunction("history.go(-1)");
        } else {
            $pageURL = str_replace("&", "&", $pageURL);
            $this->_otherOutput[] = $this->callClientFunction("Openbizx.Net.redirectPage('$pageURL')");
        }
    }

    /**
     * Redirect page to the given view.
     * Encoded html string returns to browser, it'll be processed by client javascript.
     *
     * @param string $view full name of view (with package)
     * @param string $rule
     * @return void
     */
    public function redirectView($view, $rule = null)
    {
        // get the view url form view name
        $viewParts = explode('.', $view);
        $viewMod = $viewParts[0];
        $viewName = $viewParts[count($viewParts) - 1];
        $viewName = strtolower(str_replace("View", "", $viewName));
        $url = OPENBIZ_APP_INDEX_URL . "/$viewMod/$viewName";
        $this->redirectPage($url);
        $this->printOutput();
        //Openbizx::$app->getClientProxy()->printOutput();
    }

    /**
     * Append more scripts - js include, js code
     *
     * @param mixed $scriptKey
     * @param string $scripts
     * @param boolean $isFile
     * @return void
     */
    public function appendScripts($scriptKey, $scripts, $isFile = true)
    {
        // if has the script key already, ignore
        if (isset($this->_extraScripts[$scriptKey])) {
            return;
        }
        // add the scripts
        if ($isFile) {
            $_scripts = "<script type='text/javascript' src=\"" . Openbizx::$app->getJsUrl() . "/$scripts\"></script>";
            $this->_extraScripts[$scriptKey] = $_scripts;
        } else {
            $this->_extraScripts[$scriptKey] = $scripts;
        }
    }

    /**
     * Get append scripts
     *
     * @return string
     */
    public function getAppendedScripts()
    {
        $currentView = Openbizx::$app->getCurrentViewName();
        $initScripts = "<script>var APP_URL='" . OPENBIZ_APP_URL . "'; var APP_CONTROLLER='" . OPENBIZ_APP_URL . "/bin/controller.php';</script>\n";
        $initScripts .= "<script>var APP_VIEWNAME='" . $currentView . "';</script>\n";
        $extraScripts = implode("", $this->_extraScripts);
        $extraScript_array = explode("</script>", $extraScripts);
        /* if (defined("OPENBIZ_RESOURCE_PHP")) {
          $js_scripts = OPENBIZ_RESOURCE_PHP."?f=";
          foreach ($extraScript_array as $script)
          {
          // extract src part from each line
          if (preg_match('/.+src="([^"]+)".+/', $script, $matches) > 0 && !empty($matches[1])) {
          if (substr($js_scripts, -2)=='f=') $js_scripts .= $matches[1];
          else $js_scripts .= ','.$matches[1];
          }
          }
          return $initScripts."<script type=\"text/javascript\" src=\"".$js_scripts."\"></script>";
          } */
        $cleanScript_array = array();
        foreach ($extraScript_array as $script) {
            if (in_array($script . "</script>", $cleanScript_array) == FALSE and strlen($script) != 0) {
                $cleanScript_array[] = $script . "</script>";
            }
        }
        return $initScripts . implode("\n", $cleanScript_array);
    }

    /**
     * Append more styles - include and sections
     *
     * @param <type> $scriptKey
     * @param string $styles
     * @param boolean $isFile
     * @return void
     */
    public function appendStyles($scriptKey, $styles, $isFile = true)
    {
        // if has the script key already, ignore
        if (isset($this->_extraStyles[$scriptKey])) {
            return;
        }
        // add the styles
        $css = Openbizx::$app->getCssUrl();
        if ($isFile) {
            $_styles = "<link rel=\"stylesheet\" href=\"$css/" . $styles . "\" type=\"text/css\">";
            $this->_extraStyles[$scriptKey] = $_styles;
        } else {
            $this->_extraStyles[$scriptKey] = $styles;
        }
    }

    /**
     * Get appended styles
     *
     * @return string
     */
    public function getAppendedStyles($comb = 0)
    {
        $extraStyles = implode("", $this->_extraStyles);
        $extraStyle_array = explode("type=\"text/css\">", $extraStyles);
        if (defined("OPENBIZ_RESOURCE_PHP") && $comb) {
            $css_scripts = OPENBIZ_RESOURCE_PHP . "?f=";
            $matches = array();
            foreach ($extraStyle_array as $style) {
                // extract href part from each line
                if (preg_match('/.+href="([^"]+)".+/', $style, $matches) > 0 && !empty($matches[1])) {
                    if (substr($css_scripts, -2) == 'f=') {
                        $css_scripts .= $matches[1];
                    } else {
                        $css_scripts .= ',' . $matches[1];
                    }
                }
            }
            return "<link rel=\"stylesheet\" href=\"" . $css_scripts . "\" type=\"text/css\"/>";
        }
        $cleanStyle_array = array();
        foreach ($extraStyle_array as $style) {
            if (in_array($style . "type=\"text/css\">", $cleanStyle_array) == FALSE and strlen($style) != 0) {
                $cleanStyle_array[] = $style . "type=\"text/css\">";
            }
        }
        $lang = I18n::getCurrentLangCode();
        $localization_css_file = OPENBIZ_APP_PATH . DIRECTORY_SEPARATOR
                . "languages" . DIRECTORY_SEPARATOR
                . $lang . DIRECTORY_SEPARATOR . "localization.css";
        if (is_file($localization_css_file)) {
            $cleanStyle_array[] = "<link rel=\"stylesheet\" href=\"" . OPENBIZ_APP_URL . "/languages/$lang/localization.css\" type=\"text/css\">";
        }
        return implode("\n", $cleanStyle_array);
    }

    /**
     * Include base client library scripts
     *
     * @return void
     */
    public function includeBaseClientScripts()
    {
        if (defined('OPENBIZ_JSLIB_BASE') && OPENBIZ_JSLIB_BASE == 'JQUERY') {
            Openbizx::$app->getClientProxy()->appendScripts("jquery", "jquery.js");
            Openbizx::$app->getClientProxy()->appendScripts("jquery_class", "jquery.class.js");
            Openbizx::$app->getClientProxy()->appendScripts("jquery_dollarj", "<script>try{var \$j=\$;}catch(e){}</script>", false);
            Openbizx::$app->getClientProxy()->appendStyles("default", "openbiz.css");
            if (DeviceUtil::$PHONE_TOUCH) {
                Openbizx::$app->getClientProxy()->appendScripts("openbiz", "openbiz.mobile.js");
                Openbizx::$app->getClientProxy()->appendScripts("jquery_mobile", "jqm/jquery.mobile-1.0.js");
                $style = "<link rel=\"stylesheet\" href=\"" . Openbizx::$app->getJsUrl() . "/jqm/jquery.mobile-1.0.css\" type=\"text/css\">";
                Openbizx::$app->getClientProxy()->appendStyles("jquery_mobile_css", $style, false);
            } else {
                Openbizx::$app->getClientProxy()->appendScripts("openbiz", "openbiz.js");
                Openbizx::$app->getClientProxy()->appendScripts("jquery_ui", "jquery-ui-1.8.16.custom.min.js");
                $style = "<link rel=\"stylesheet\" href=\"" . Openbizx::$app->getJsUrl() . "/jquery-ui/ui-lightness/jquery-ui-1.8.16.custom.css\" type=\"text/css\">";
                $style .= "<link rel=\"stylesheet\" href=\"" . Openbizx::$app->getJsUrl() . "/jquery-ui/ui-openbiz/jquery.css\" type=\"text/css\">";
                Openbizx::$app->getClientProxy()->appendStyles("jquery_ui_css", $style, false);
            }
            return;
        }
        // prototype is still the default js lib
        Openbizx::$app->getClientProxy()->appendScripts("prototype", "prototype.js");
        Openbizx::$app->getClientProxy()->appendScripts("scriptaculous", "scriptaculous.js");
        Openbizx::$app->getClientProxy()->appendScripts("effects", "effects.js");
        Openbizx::$app->getClientProxy()->appendScripts("controls", "controls.js");
        Openbizx::$app->getClientProxy()->appendScripts("cookies", "cookies.js");
        Openbizx::$app->getClientProxy()->appendScripts("openbiz", "openbiz.js");
        Openbizx::$app->getClientProxy()->appendStyles("default", "openbiz.css");
        // window lib
        Openbizx::$app->getClientProxy()->includePropWindowScripts();
        // validator lib
        //Openbizx::$app->getClientProxy()->includeValidatorScripts();
    }

    /**
     * Include calendar scripts
     *
     * @return void
     */
    public function includeCalendarScripts()
    {
        if (isset($this->_extraScripts['calendar'])) {
            return;
        }
        $style = "<link rel=\"stylesheet\" href=\"" . Openbizx::$app->getJsUrl() . "/jscalendar/calendar-system.css\" type=\"text/css\">";
        $script = "<script type='text/javascript' src='" . Openbizx::$app->getJsUrl() . "/jscalendar/calendar.js'></script>";
        $script .= "<script type='text/javascript' src='" . Openbizx::$app->getJsUrl() . "/jscalendar/lang/calendar-en.js'></script>";
        $script .= "<script type='text/javascript' src='" . Openbizx::$app->getJsUrl() . "/jscalendar/calendar-setup.js'></script>";
        $script .= "<script type='text/javascript' src='" . Openbizx::$app->getJsUrl() . "/calendar.js'></script>";
        $this->appendStyles("calendar", $style, false);
        $this->appendScripts("calendar", $script, false);
    }

    /**
     * Include calendar scripts
     *
     * @return void
     */
    public function includeColorPickerScripts()
    {
        if (isset($this->_extraScripts['colorpicker'])) {
            return;
        }
        $style = "<link rel=\"stylesheet\" href=\"" . Openbizx::$app->getJsUrl() . "/colorpicker/css/colorpicker.css\" type=\"text/css\">";
        if (OPENBIZ_JSLIB_BASE != 'JQUERY') {
            $script = "<script type=\"text/javascript\" src=\"" . Openbizx::$app->getJsUrl() . "/jquery.js\"></script>";
        }
        $script .= "<script type=\"text/javascript\" src=\"" . Openbizx::$app->getJsUrl() . "/colorpicker/js/eye.js\"></script>";
        $script .= "<script type=\"text/javascript\" src=\"" . Openbizx::$app->getJsUrl() . "/colorpicker/js/utils.js\"></script>";
        $script .= "<script type=\"text/javascript\" src=\"" . Openbizx::$app->getJsUrl() . "/colorpicker/js/colorpicker.js\"></script>";
        $this->appendStyles("colorpicker", $style, false);
        $this->appendScripts("colorpicker", $script, false);
    }

    /**
     * Include RTE scripts
     *
     * @return void
     */
    public function includeRTEScripts()
    {
        if (isset($this->_extraScripts['rte'])) {
            return;
        }
        $script = "<script type=\"text/javascript\" src=\"" . Openbizx::$app->getJsUrl() . "/richtext.js\"></script>";
        $script .= "<script language=\"JavaScript\">initRTE('" . Openbizx::$app->getImageUrl() . "/rte/', '../pages/rte/', '', false);</script>";
        $this->appendScripts("rte", $script, false);
    }

    /**
     * Include CKEditor scripts
     *
     * @return void
     */
    public function includeCKEditorScripts()
    {
        if (isset($this->_extraScripts['ckeditor'])) {
            return;
        }

        $script = "<script type=\"text/javascript\" src=\"" . Openbizx::$app->getJsUrl() . "/ckeditor/ckeditor.js\"></script>";

        $this->appendScripts("ckeditor", $script, false);
    }

    /**
     * Include PropWindow scripts
     *
     * @return void
     */
    public function includePropWindowScripts()
    {
        $this->appendScripts("scriptaculous", "scriptaculous.js");
        $this->appendScripts("prop_window", "window.js");
        $style = "<link rel=\"stylesheet\" href=\"" . Openbizx::$app->getJsUrl() . "/window/default.css\" type=\"text/css\">";
        //$style .= "<link rel=\"stylesheet\" href=\"".Openbizx::$app->getJsUrl()."/window/spread.css\" type=\"text/css\">";
        //$style .= "<link rel=\"stylesheet\" href=\"".Openbizx::$app->getJsUrl()."/window/lighting.css\" type=\"text/css\">";
        $this->appendStyles("prop_window", $style, false);
    }

    /**
     * Include validator scripts
     *
     * @return void
     */
    public function includeValidatorScripts()
    {
        $this->appendScripts("yav", "yav/yav.js");
        $this->appendScripts("yav-cfg", "yav/yav-config.js");
        //$this->appendScripts("validator", "validator.js");
        $style = "<link rel=\"stylesheet\" href=\"" . Openbizx::$app->getCssUrl() . "/validator.css\" type=\"text/css\">";
        $this->appendStyles("yav", $style, false);
    }

}


<?php

namespace Limber\Router;

use Limber\Exceptions\MethodNotAllowedHttpException;
use Limber\Exceptions\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;

class TreeRouter extends Router
{
    /**
     * Set of indexed RouteBranches
     *
     * @var RouteBranch
     */
    protected $tree;

    /**
     * @inheritDoc
     */
    public function __construct(array $routes = null)
    {
        $this->tree = new RouteBranch;

        if( $routes ){
            foreach( $routes as $route ){
                $this->indexRoute($route);
            }
        }
    }

    /**
     * Index the route.
     *
     * @param Route $route
     * @return void
     */
    protected function indexRoute(Route $route): void
    {
        $currentBranch = $this->tree;
        $patternParts = $route->getPatternParts();

        foreach( $patternParts as $pattern ){
            $currentBranch = $currentBranch->next($pattern);
        }

        $currentBranch->addRoute($route);
    }

    /**
     * @inheritDoc
     */
    public function add($methods, string $uri, $target): Route
    {
        // Create new Route instance
        $route = new Route($methods, $uri, $target, $this->config);

        // Index the route
        $this->indexRoute($route);

        return $route;
    }

    /**
     * @inheritDoc
     */
    public function resolve(Request $request): ?Route
    {
        // Break the request path apart
        $pathParts = explode("/", trim($request->getPathInfo(), "/"));

        // Set the starting node.
        $branch = $this->tree;

        foreach( $pathParts as $part ){
            if( ($branch = $branch->findBranch($part)) === null ){
                throw new NotFoundHttpException("Route not found.");
            }
        }

        if( ($route = $branch->getRouteForMethod($request->getMethod())) === null ){
            throw new MethodNotAllowedHttpException;
        }

        // Now match against the remaining criteria.
        if( $route->matchScheme($request->getScheme()) &&
            $route->matchHostname($request->getHost()) ){

            return $route;
        }

        throw new NotFoundHttpException("Route not found.");
    }

    /**
     * @inheritDoc
     */
    public function getMethodsForUri(Request $request): array
    {
        // Break the request path apart
        $pathParts = explode("/", trim($request->getPathInfo(), "/"));

        // Set the starting node.
        $branch = $this->tree;

        foreach( $pathParts as $part ){
            if( ($branch = $branch->findBranch($part)) === null ){
                return [];
            }
        }

        return $branch->getMethods();
    }
}


<?php

namespace UWDOEM\Person;

/**
 * Container class for person and student-specific information received from Person Web
 * Service and Student Web Service
 *
 * @method static null|Student fromUWNetID() fromUWNetID(string $uwnetid)
 *                             Queries PWS/SWS to generate a Student, given a UWNetID.
 * @method static null|Student fromUWRegID() fromUWRegID(string $uwregid)
 *                             Queries PWS/SWS to generate a Student, given a UWRegID.
 *
 * @package UWDOEM\Person
 */
class Student extends Person
{

    /** @var string */
    protected static $AFFILIATION_TYPE = "student";

    /**
     * Perform a registration search on SWS, per
     *     https://wiki.cac.washington.edu/display/SWS/Registration+Search+Resource+v5
     *
     * @param integer  $year             Numeric year between 1950 and 2100 inclusive.
     * @param string   $quarter          One of ["autumn", "winter", "spring", "summer"].
     * @param string[] $extraSearchTerms Associative array of search terms ex: ["course_number" => "100"].
     * @return mixed Associative array object of registration search results
     * @throws \Exception If provided with invalid year, invalid quarter, or invalid search terms.
     */
    public function registrationSearch($year, $quarter, array $extraSearchTerms = [])
    {

        if (is_numeric($year) === false || $year < 1950 || $year > 2100) {
            throw new \Exception("Please provide a numeric year between 1950 and 2100");
        }

        $validQuarters = ["autumn", "winter", "spring", "summer"];
        if (in_array("$quarter", $validQuarters) === false) {
            throw new \Exception("Quarter must be one of [" . implode(", ", $validQuarters) . "], case sensitive.");
        }

        $validSearchKeys = [
            "curriculum_abbreviation", "course_number", "section_id", "instructor_reg_id",
            "is_active", "verbose", "changed_since_date", "transcriptable_course", "grading_system"
        ];
        $invalidSearchKeys = array_diff(array_keys($extraSearchTerms), $validSearchKeys);
        if ($invalidSearchKeys !== []) {
            throw new \Exception("Invalid search keys [" . implode(", ", $invalidSearchKeys) . "]" .
                "provided as extra search terms. Only [" . implode(", ", $validSearchKeys) . "] allowed");
        }

        $defaultSearchTerms = [
            "curriculum_abbreviation"=> "",
            "course_number" => "",
            "section_id" => "",
        ];

        $searchTerms = array_merge($defaultSearchTerms, $extraSearchTerms);
        $searchTerms["reg_id"] = $this->getAttr("UWRegID");
        $searchTerms["year"] = $year;
        $searchTerms["quarter"] = $quarter;

        $resp = static::getStudentConnection()->execGET(
            "registration.json?" . http_build_query($searchTerms)
        );

        return static::parse($resp->getData())["Registrations"];
    }

    /**
     * @param string $identifierKey
     * @param string $identifierValue
     * @return null|Student
     * @throws \Exception If $identifierKey is not one of ["uwregid", "uwnetid", "employee_id",
     *                                                     "student_number", "student_system_key"].
     */
    public static function fromIdentifier($identifierKey, $identifierValue)
    {
        /** @var null|Person $student */
        $student = parent::fromIdentifier($identifierKey, $identifierValue);

        /** @var string[] $personKeyToStudentKey */
        $personKeyToStudentKey = [
            "uwregid" => "reg_id",
            "uwnetid" => "net_id",
            "student_number" => "student_number",
            "employee_id" => "employee_id",
            "student_system_key" => "student_system_key",
        ];

        /** @var string[] $validIdentifierKeys */
        $validIdentifierKeys = array_keys($personKeyToStudentKey);

        /** @var boolean $identifierKeyIsValid */
        $identifierKeyIsValid = in_array($identifierKey, $validIdentifierKeys);

        if ($identifierKeyIsValid === false) {
            throw new \Exception(
                "Identifier key '$identifierKey' must be one of [" . implode(", ", $validIdentifierKeys) . "]."
            );
        }

        $identifierKey = $personKeyToStudentKey[$identifierKey];

        $resp = static::getStudentConnection()->execGET(
            "person.json?$identifierKey=$identifierValue"
        );
        $resp = static::parse($resp->getData());

        if (array_key_exists("Persons", $resp) === true && sizeof($resp["Persons"]) > 0) {
            if ($student === null) {
                $student = new Student();
            }
            $student->attrs = array_merge($student->attrs, $resp["Persons"][0]);
        }

        return $student;
    }

    /**
     * @param Person $person
     * @param array  $attrs
     * @return Student
     */
    protected static function fill(Person $person, array $attrs)
    {
        if (array_key_exists('StudentPersonAffiliation', $attrs["PersonAffiliations"]) === true) {
            $attrs = array_merge(
                $attrs,
                $attrs["PersonAffiliations"]["StudentPersonAffiliation"],
                $attrs["PersonAffiliations"]["StudentPersonAffiliation"]["StudentWhitePages"]
            );
        }

        $student = parent::fill($person, $attrs);

        return $student;
    }

    /**
     * @param string $studentNumber
     * @return null|Student
     */
    public static function fromStudentNumber($studentNumber)
    {
        return static::fromIdentifier("student_number", $studentNumber);
    }
}


<?php

/**
 * Openbizx Framework
 *
 * LICENSE
 *
 * This source file is subject to the BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @package   openbiz.bin.service
 * @copyright Copyright (c) 2005-2011, Rocky Swen
 * @license   http://www.opensource.org/licenses/bsd-license.php
 * @link      http://www.phpopenbiz.org/
 * @version   $Id: doTriggerService.php 3836 2011-04-21 09:44:31Z jixian2003 $
 */
/*
  <PluginService Name="" Description="" Package="" Class="" BizObjectName="">
  <DOTrigger TriggerType="INSERT|UPDATE|DELETE"> *
  <TriggerCondition Expression="" ExtraSearchRule="" />
  <TriggerActions>
  <TriggerAction Action="CallService|ExecuteSQL|ExecuteShell|CreateInboxItem|SendEmail|..." Immediate="Y|N" DelayMinutes="" RepeatMinutes="">
  <ActionArgument Name="" Value="" /> *
  </TriggerAction>
  </TriggerActions>
  </DOTrigger>
  </PluginService>
 */

namespace Openbizx\Service;

use Openbizx\Core\Expression;
use Openbizx\Object\MetaObject;
use Openbizx\Object\MetaIterator;

/**
 * class doTriggerService is the plug-in service of handle DataObject trigger
 *
 * @package   openbiz.bin.service
 * @author    Rocky Swen
 * @copyright Copyright (c) 2005-2009, Rocky Swen
 * @access    public
 */
class doTriggerService extends MetaObject
{

    /**
     * Name of {@link BizDataObj}
     *
     * @var string
     */
    public $dataObjName;

    /**
     * List of DOTrigger object
     *
     * @var array array of {@link DOTrigger}
     */
    public $dOTriggerList = array();

    /**
     * Initialize chartService with xml array metadata
     *
     * @param array $xmlArr
     * @return void
     */
    function __construct(&$xmlArr)
    {
        $this->readMetadata($xmlArr);
    }

    /**
     * Read array meta data, and store to meta object
     *
     * @param array $xmlArr
     * @return void
     */
    protected function readMetadata(&$xmlArr)
    {
        parent::readMetaData($xmlArr);

        $this->dataObjName = $xmlArr["PLUGINSERVICE"]["ATTRIBUTES"]["DATAOBJECTNAME"];
        $this->dataObjName = $this->prefixPackage($this->dataObjName);
        $this->readMetaCollection($xmlArr["PLUGINSERVICE"]["DOTRIGGER"], $tmpList);
        if (!$tmpList)
            return;
        foreach ($tmpList as $triggerXml) {
            $this->dOTriggerList[] = new DOTrigger($triggerXml);
        }
    }

    /**
     * Execute trigger
     *
     * @param BizDataObj $dataObj
     * @param <type> $triggerType
     * @return void
     */
    public function execute($dataObj, $triggerType)
    {
        /* @var $doTrigger DOTrigger */
        foreach ($this->dOTriggerList as $doTrigger) {
            if ($doTrigger->triggerType == $triggerType) {
;
                $this->executeAllActions($doTrigger, $dataObj);
            }
        }
    }

    /**
     * Execute all action
     *
     * @param DOTrigger $doTrigger
     * @param BizDataObj $dataObj
     * @return void
     */
    protected function executeAllActions($doTrigger, $dataObj)
    {
        if (!$this->matchCondition($doTrigger, $dataObj))
            return;
        /* @var $triggerAction TriggerAction */
        foreach ($doTrigger->triggerActions as $triggerAction) {
            $this->executeAction($triggerAction, $dataObj);
        }
    }

    /**
     * Match condition
     *
     * @param DOTrigger $doTrigger
     * @param BizDataObj $dataObj
     * @return boolean
     */
    protected function matchCondition($doTrigger, $dataObj)
    {
        // evaluate expression
        $condExpr = $doTrigger->triggerCondition["Expression"];
        if ($condExpr) {
            $exprVal = Expression::evaluateExpression($condExpr, $dataObj);
            if ($exprVal !== true)
                return false;
        }
        // do query with extra search rule, check if there's any record returned
        $extraSearchRule = $doTrigger->triggerCondition["ExtraSearchRule"];
        if ($extraSearchRule) {
            $realSearchRule = Expression::evaluateExpression($extraSearchRule, $dataObj);
            $recordList = array();
            // get one record of the first page with additional searchrule
            $dataObj->fetchRecords($realSearchRule, $recordList, 1, 1, false);
            if (count($recordList) == 0)
                return false;
        }
        return true;
    }

    /**
     * Execute action
     *
     * @param TriggerAction $triggerAction
     * @param BizDataObj $dataObj
     * @return void
     */
    protected function executeAction($triggerAction, $dataObj)
    {
        // action method
        $methodName = $triggerAction->action;
        // action method arguments
        if (method_exists($this, $methodName)) {
            // evaluate arguments as expression support
            foreach ($triggerAction->argList as $argName => $argValue)
                $argList[$argName] = Expression::evaluateExpression($argValue, $dataObj);
            // check the immediate flag
            if ($triggerAction->immediate == "Y") // call the method if Immediate is "Y"
                $this->$methodName($argList);
            else { // put it to a passive queue
                /* $passiveQueueSvc->Push($methodName,
                  $argList,
                  $triggerAction->delayMinutes,
                  $triggerAction->repeatMinutes); */
            }
        }
    }

    /**
     * Compose action message
     *
     * @param TriggerAction $triggerAction
     * @param string $methodName
     * @param array $argList
     * @return void
     */
    private function _composeActionMessage($triggerAction, $methodName, $argList)
    {
        $actionMsg["Method"] = $methodName;
        $actionMsg["ArgList"] = $argList;
        $actionMsg["DelayMinutes"] = $triggerAction->delayMinutes;
        $actionMsg["RepeatMinutes"] = $triggerAction->repeatMinutes;
        $actionMsg["StartTime"] = strftime("%Y-%m-%d %H:%M:%S");
    }

    protected function callService($argList)
    {
        $svcobj = $argList['Service'];
        $method = $argList['Method'];
        $svcobj = Openbizx::getObject($svcobj);
        if (!method_exists($svcobj, $method)) {
            return;
        }
        unset($argList['Service']);
        unset($argList['Method']);
        return call_user_func_array(array($svcobj, $method), $argList);
    }

    /**
     * Execute shell
     *
     * @param array $argList
     * @return void
     */
    protected function executeShell($argList)
    {
        $Script = $argList["Script"];
        $Inputs = $argList["Inputs"];
        $command = "$Script $Inputs";
        //$result = exec($command, $output);
        exec($command);
    }

    /**
     * Execute SQL
     *
     * @param array $argList
     * @return void
     */
    protected function executeSQL($argList)
    {
        $dbName = $argList["DBName"];
        if (!$dbName)
            $dbName = "Default";
        $sql = $argList["SQL"];
        $db = Openbizx::$app->getDbConnection($dbName);
        try {
            $resultSet = $db->query($sql);
        } catch (Exception $e) {
            $errorMessage = "Error in run SQL: " . $sql . ". " . $e->getMessage();
        }
    }

    /**
     * Send email
     *
     * @param array $argList
     * @return boolean|string if sending error return error messege, if success return true
     */
    protected function sendEmail($argList)
    {
        // emailService
        $emailServiceName = $argList["EmailService"];
        $emailService = Openbizx::getObject($emailServiceName);
        if ($emailService == null)
            return;

        $emailService->useAccount($argList["Account"]);
        $TOs = doTriggerService::_makeArray($argList["TOs"]);
        $CCs = doTriggerService::_makeArray($argList["CCs"]);
        $BCCs = doTriggerService::_makeArray($argList["BCCs"]);
        $Attachments = doTriggerService::_makeArray($argList["Attachments"]);
        $subject = $argList["Subject"];
        $body = $argList["Body"];
        $ok = $emailService->sendEmail($TOs, $CCs, $BCCs, $subject, $body, $Attachments);
        if ($ok == false) {
            return $emailService->getErrorMsg();
        }
        return $ok;
    }

    /**
     * Audit trail
     *
     * @param array $argList
     * @return void
     */
    protected function auditTrail($argList)
    {
        $auditServiceName = $argList["AuditService"];
        $auditService = Openbizx::getObject($auditServiceName);
        if ($auditService == null)
            return;
        $dataObjName = $argList["DataObjectName"];
        $ok = $auditService->audit($dataObjName);
        if ($ok == false) {
            // log $auditSvc->getErrorMsg();
        }
    }

    /**
     * Make array from string with semi colon delimited
     *
     * @param string $string string with ";" (semi colon) delimited
     * @return array
     */
    static private function _makeArray($string)
    {
        if (!$string)
            return null;
        $arr = explode(";", $string);
        $size = count($arr);
        for ($i = 0; $i < $size; $i ++)
            $arr[$i] = trim($arr[$i]);
        return $arr;
    }

    /**
     * Create inbox item
     *
     * @param array $argList
     * @return void
     */
    protected function createInboxItem($argList)
    {    // call inbox service
    }

}

/**
 * DOTrigger class
 *
 * @package   openbiz.bin.service
 * @author    Rocky Swen
 * @copyright Copyright (c) 2005-2009, Rocky Swen
 * @access    public
 */
class DOTrigger
{

    public $triggerType;

    /**
     * Trigger condition
     *
     * @var array
     */
    public $triggerCondition = array();

    /**
     * Iterator of TriggerAction
     *
     * @var MetaIterator
     */
    public $triggerActions;

    /**
     * Initialize DOTrigger with xml array metadata
     *
     * @param array $xmlArr
     * @return void
     */
    public function __construct($xmlArr)
    {
        $this->triggerType = $xmlArr["ATTRIBUTES"]["TRIGGERTYPE"];
        // read in trigger condition
        $this->triggerCondition["Expression"] = $xmlArr["TRIGGERCONDITION"]["ATTRIBUTES"]["EXPRESSION"];
        $this->triggerCondition["ExtraSearchRule"] = $xmlArr["TRIGGERCONDITION"]["ATTRIBUTES"]["EXTRASEARCHRULE"];
        if ($xmlArr["TRIGGERACTIONS"]["TRIGGERACTION"][0]) {
            foreach ($xmlArr["TRIGGERACTIONS"]["TRIGGERACTION"] as $key => $value) {
                $this->triggerActions[] = new TriggerAction($xmlArr["TRIGGERACTIONS"]["TRIGGERACTION"][$key]);
            }
        } else {
            $this->triggerActions = new MetaIterator($xmlArr["TRIGGERACTIONS"]["TRIGGERACTION"], "TriggerAction");
        }
    }

}

/**
 * TriggerAction class
 *
 * @package   openbiz.bin.service
 * @author    Rocky Swen
 * @copyright Copyright (c) 2005-2009, Rocky Swen
 * @access    public
 */
class TriggerAction extends MetaObject
{

    public $objectName;
    public $action;
    public $immediate;
    public $delayMinutes;
    public $repeatMinutes;
    public $argList = array();

    /**
     * Initialize TriggerAction with xml array metadata
     *
     * @param array $xmlArr
     * @return void
     */
    public function __construct($xmlArr)
    {
        $this->objectName = $xmlArr["ATTRIBUTES"]["NAME"];
        $this->action = $xmlArr["ATTRIBUTES"]["ACTION"];
        $this->immediate = $xmlArr["ATTRIBUTES"]["IMMEDIATE"];
        $this->delayMinutes = $xmlArr["ATTRIBUTES"]["DELAYMINUTES"];
        $this->repeatMinutes = $xmlArr["ATTRIBUTES"]["REPEATMINUTES"];
        $this->readMetaCollection($xmlArr["ACTIONARGUMENT"], $tmpList);
        if (!$tmpList)
            return;
        foreach ($tmpList as $arg) {
            $this->argList[$arg["ATTRIBUTES"]["NAME"]] = $arg["ATTRIBUTES"]["VALUE"];
        }
    }

}