<?php

/**
 * Phergie plugin for returning the current or last played song for a user on last.fm or libre.fm
 *
 * @link https://github.com/chrismou/phergie-irc-plugin-react-audioscrobbler for the canonical source repository
 * @copyright Copyright (c) 2016 Chris Chrisostomou (https://mou.me)
 * @license http://phergie.org/license New BSD License
 * @package Chrismou\Phergie\Plugin\Audioscrobbler
 */

namespace Chrismou\Phergie\Plugin\Audioscrobbler;

use Phergie\Irc\Bot\React\AbstractPlugin;
use Phergie\Irc\Bot\React\EventQueueInterface as Queue;
use Phergie\Irc\Plugin\React\Command\CommandEvent as Event;
use GuzzleHttp\Message\Response;
use Phergie\Plugin\Http\Request as HttpRequest;

/**
 * Plugin class.
 *
 * @category Chrismou
 * @package Chrismou\Phergie\Plugin\Audioscrobbler
 */
class Plugin extends AbstractPlugin
{
    /**
     * Populated with valid providers at runtime
     * @var array
     */
    protected $validProviders = array();

    /**
     * All providers
     * @var array
     */
    protected $supportedProviders = array(
        "lastfm" => "Chrismou\\Phergie\\Plugin\\Audioscrobbler\\Provider\\Lastfm",
        "librefm" => "Chrismou\\Phergie\\Plugin\\Audioscrobbler\\Provider\\Librefm"
    );

    /**
     * Accepts plugin configuration.
     *
     * Supported keys:
     *
     *
     *
     * @param array $config
     */
    public function __construct(array $config = array())
    {
        foreach ($this->supportedProviders as $provider => $class) {
            $providerConfig = isset($config[$provider]) ? $config[$provider] : null;
            if ($class::validateConfig($providerConfig)) {
                $this->validProviders[$provider] = new $class($providerConfig);
            }
        }
    }

    /**
     * Return array of subscribed events
     *
     * @return array
     */
    public function getSubscribedEvents()
    {
        $subscribedEvents = array();
        foreach ($this->validProviders as $provider => $class) {
            $subscribedEvents['command.' . $provider] = 'handleCommand';
            $subscribedEvents['command.' . $provider . '.help'] = 'handleCommandHelp';
        }
        return $subscribedEvents;
    }

    /**
     * Handler for the main commands
     *
     * @param \Phergie\Irc\Plugin\React\Command\CommandEvent $event
     * @param \Phergie\Irc\Bot\React\EventQueueInterface $queue
     */
    public function handleCommand(Event $event, Queue $queue)
    {
        $provider = $this->getProvider($event->getCustomCommand());
        if ($provider->validateParams($event->getCustomParams())) {
            $request = $this->getApiRequest($event, $queue);
            $this->getEventEmitter()->emit('http.request', array($request));
        } else {
            $this->handleCommandhelp($event, $queue);
        }
    }

    /**
     * Handler for the help command
     *
     * @param \Phergie\Irc\Plugin\React\Command\CommandEvent $event
     * @param \Phergie\Irc\Bot\React\EventQueueInterface $queue
     */
    public function handleCommandHelp(Event $event, Queue $queue)
    {
        $provider = $this->getProvider($event->getCustomCommand());
        $this->sendIrcResponse($event, $queue, $provider->getHelpLines());
    }

    /**
     * Set up the API request and set the callbacks
     *
     * @param \Phergie\Irc\Plugin\React\Command\CommandEvent $event
     * @param \Phergie\Irc\Bot\React\EventQueueInterface $queue
     * @return \Phergie\Plugin\Http\Request
     */
    protected function getApiRequest(Event $event, Queue $queue)
    {
        $provider = $this->getProvider($event->getCustomCommand());
        $self = $this;
        return new HttpRequest(array(
            'url' => $provider->getApiRequestUrl($event),
            'resolveCallback' => function (Response $response) use ($self, $event, $queue, $provider) {
                $self->sendIrcResponse($event, $queue, $provider->getSuccessLines($event, $response->getBody()));
            },
            'rejectCallback' => function (Response $error) use ($self, $event, $queue) {
                $self->sendIrcResponse($event, $queue, $self->getRejectLines($error->getBody()));
            }
        ));
    }

    /**
     * Send an array of response lines back to IRC
     *
     * @param \Phergie\Irc\Plugin\React\Command\CommandEvent $event
     * @param \Phergie\Irc\Bot\React\EventQueueInterface $queue
     * @param array $ircResponse
     */
    protected function sendIrcResponse(Event $event, Queue $queue, array $ircResponse)
    {
        foreach ($ircResponse as $ircResponseLine) {
            $this->sendIrcResponseLine($event, $queue, $ircResponseLine);
        }
    }

    /**
     * Send a single response line back to IRC
     *
     * @param \Phergie\Irc\Plugin\React\Command\CommandEvent $event
     * @param \Phergie\Irc\Bot\React\EventQueueInterface $queue
     * @param string $ircResponseLine
     */
    protected function sendIrcResponseLine(Event $event, Queue $queue, $ircResponseLine)
    {
        $queue->ircPrivmsg($event->getSource(), $ircResponseLine);
    }

    /**
     * Return an array of lines to send back to IRC when the request fails
     * @return array
     */
    public function getRejectLines($error)
    {
        return array('Something went wrong... ಠ_ಠ');
    }

    /**
     * Get a single provider class by command
     *
     * @param string $command
     * @return \Chrismou\Phergie\Plugin\Audioscrobbler\Provider\AudioscrobblerProviderInterface $provider|false
     */
    public function getProvider($command)
    {
        return (isset($this->validProviders[$command])) ? $this->validProviders[$command] : false;
    }
}


<?php
/**
 * package.xml parsing class, package.xml version 1.0
 *
 * PHP versions 4 and 5
 *
 * @category   pear
 * @package    PEAR
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  1997-2009 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    CVS: $Id: v1.php 313023 2011-07-06 19:17:11Z dufuz $
 * @link       http://pear.php.net/package/PEAR
 * @since      File available since Release 1.4.0a1
 */
/**
 * package.xml abstraction class
 */
require_once 'PEAR/PackageFile/v1.php';
/**
 * Parser for package.xml version 1.0
 * @category   pear
 * @package    PEAR
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  1997-2009 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    Release: @PEAR-VER@
 * @link       http://pear.php.net/package/PEAR
 * @since      Class available since Release 1.4.0a1
 */
class PEAR_PackageFile_Parser_v1
{
    var $_registry;
    var $_config;
    var $_logger;
    /**
     * BC hack to allow PEAR_Common::infoFromString() to sort of
     * work with the version 2.0 format - there's no filelist though
     * @param PEAR_PackageFile_v2
     */
    function fromV2($packagefile)
    {
        $info = $packagefile->getArray(true);
        $ret = new PEAR_PackageFile_v1;
        $ret->fromArray($info['old']);
    }

    function setConfig(&$c)
    {
        $this->_config = &$c;
        $this->_registry = &$c->getRegistry();
    }

    function setLogger(&$l)
    {
        $this->_logger = &$l;
    }

    /**
     * @param string contents of package.xml file, version 1.0
     * @return bool success of parsing
     */
    function &parse($data, $file, $archive = false)
    {
        if (!extension_loaded('xml')) {
            return PEAR::raiseError('Cannot create xml parser for parsing package.xml, no xml extension');
        }
        $xp = xml_parser_create();
        if (!$xp) {
            $a = &PEAR::raiseError('Cannot create xml parser for parsing package.xml');
            return $a;
        }
        xml_set_object($xp, $this);
        xml_set_element_handler($xp, '_element_start_1_0', '_element_end_1_0');
        xml_set_character_data_handler($xp, '_pkginfo_cdata_1_0');
        xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false);

        $this->element_stack = array();
        $this->_packageInfo = array('provides' => array());
        $this->current_element = false;
        unset($this->dir_install);
        $this->_packageInfo['filelist'] = array();
        $this->filelist =& $this->_packageInfo['filelist'];
        $this->dir_names = array();
        $this->in_changelog = false;
        $this->d_i = 0;
        $this->cdata = '';
        $this->_isValid = true;

        if (!xml_parse($xp, $data, 1)) {
            $code = xml_get_error_code($xp);
            $line = xml_get_current_line_number($xp);
            xml_parser_free($xp);
            $a = &PEAR::raiseError(sprintf("XML error: %s at line %d",
                           $str = xml_error_string($code), $line), 2);
            return $a;
        }

        xml_parser_free($xp);

        $pf = new PEAR_PackageFile_v1;
        $pf->setConfig($this->_config);
        if (isset($this->_logger)) {
            $pf->setLogger($this->_logger);
        }
        $pf->setPackagefile($file, $archive);
        $pf->fromArray($this->_packageInfo);
        return $pf;
    }
    // {{{ _unIndent()

    /**
     * Unindent given string
     *
     * @param string $str The string that has to be unindented.
     * @return string
     * @access private
     */
    function _unIndent($str)
    {
        // remove leading newlines
        $str = preg_replace('/^[\r\n]+/', '', $str);
        // find whitespace at the beginning of the first line
        $indent_len = strspn($str, " \t");
        $indent = substr($str, 0, $indent_len);
        $data = '';
        // remove the same amount of whitespace from following lines
        foreach (explode("\n", $str) as $line) {
            if (substr($line, 0, $indent_len) == $indent) {
                $data .= substr($line, $indent_len) . "\n";
            } elseif (trim(substr($line, 0, $indent_len))) {
                $data .= ltrim($line);
            }
        }
        return $data;
    }

    // Support for package DTD v1.0:
    // {{{ _element_start_1_0()

    /**
     * XML parser callback for ending elements.  Used for version 1.0
     * packages.
     *
     * @param resource  $xp    XML parser resource
     * @param string    $name  name of ending element
     *
     * @return void
     *
     * @access private
     */
    function _element_start_1_0($xp, $name, $attribs)
    {
        array_push($this->element_stack, $name);
        $this->current_element = $name;
        $spos = sizeof($this->element_stack) - 2;
        $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
        $this->current_attributes = $attribs;
        $this->cdata = '';
        switch ($name) {
            case 'dir':
                if ($this->in_changelog) {
                    break;
                }
                if (array_key_exists('name', $attribs) && $attribs['name'] != '/') {
                    $attribs['name'] = preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
                        $attribs['name']);
                    if (strrpos($attribs['name'], '/') === strlen($attribs['name']) - 1) {
                        $attribs['name'] = substr($attribs['name'], 0,
                            strlen($attribs['name']) - 1);
                    }
                    if (strpos($attribs['name'], '/') === 0) {
                        $attribs['name'] = substr($attribs['name'], 1);
                    }
                    $this->dir_names[] = $attribs['name'];
                }
                if (isset($attribs['baseinstalldir'])) {
                    $this->dir_install = $attribs['baseinstalldir'];
                }
                if (isset($attribs['role'])) {
                    $this->dir_role = $attribs['role'];
                }
                break;
            case 'file':
                if ($this->in_changelog) {
                    break;
                }
                if (isset($attribs['name'])) {
                    $path = '';
                    if (count($this->dir_names)) {
                        foreach ($this->dir_names as $dir) {
                            $path .= $dir . '/';
                        }
                    }
                    $path .= preg_replace(array('!\\\\+!', '!/+!'), array('/', '/'),
                        $attribs['name']);
                    unset($attribs['name']);
                    $this->current_path = $path;
                    $this->filelist[$path] = $attribs;
                    // Set the baseinstalldir only if the file don't have this attrib
                    if (!isset($this->filelist[$path]['baseinstalldir']) &&
                        isset($this->dir_install))
                    {
                        $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
                    }
                    // Set the Role
                    if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
                        $this->filelist[$path]['role'] = $this->dir_role;
                    }
                }
                break;
            case 'replace':
                if (!$this->in_changelog) {
                    $this->filelist[$this->current_path]['replacements'][] = $attribs;
                }
                break;
            case 'maintainers':
                $this->_packageInfo['maintainers'] = array();
                $this->m_i = 0; // maintainers array index
                break;
            case 'maintainer':
                // compatibility check
                if (!isset($this->_packageInfo['maintainers'])) {
                    $this->_packageInfo['maintainers'] = array();
                    $this->m_i = 0;
                }
                $this->_packageInfo['maintainers'][$this->m_i] = array();
                $this->current_maintainer =& $this->_packageInfo['maintainers'][$this->m_i];
                break;
            case 'changelog':
                $this->_packageInfo['changelog'] = array();
                $this->c_i = 0; // changelog array index
                $this->in_changelog = true;
                break;
            case 'release':
                if ($this->in_changelog) {
                    $this->_packageInfo['changelog'][$this->c_i] = array();
                    $this->current_release = &$this->_packageInfo['changelog'][$this->c_i];
                } else {
                    $this->current_release = &$this->_packageInfo;
                }
                break;
            case 'deps':
                if (!$this->in_changelog) {
                    $this->_packageInfo['release_deps'] = array();
                }
                break;
            case 'dep':
                // dependencies array index
                if (!$this->in_changelog) {
                    $this->d_i++;
                    isset($attribs['type']) ? ($attribs['type'] = strtolower($attribs['type'])) : false;
                    $this->_packageInfo['release_deps'][$this->d_i] = $attribs;
                }
                break;
            case 'configureoptions':
                if (!$this->in_changelog) {
                    $this->_packageInfo['configure_options'] = array();
                }
                break;
            case 'configureoption':
                if (!$this->in_changelog) {
                    $this->_packageInfo['configure_options'][] = $attribs;
                }
                break;
            case 'provides':
                if (empty($attribs['type']) || empty($attribs['name'])) {
                    break;
                }
                $attribs['explicit'] = true;
                $this->_packageInfo['provides']["$attribs[type];$attribs[name]"] = $attribs;
                break;
            case 'package' :
                if (isset($attribs['version'])) {
                    $this->_packageInfo['xsdversion'] = trim($attribs['version']);
                } else {
                    $this->_packageInfo['xsdversion'] = '1.0';
                }
                if (isset($attribs['packagerversion'])) {
                    $this->_packageInfo['packagerversion'] = $attribs['packagerversion'];
                }
                break;
        }
    }

    // }}}
    // {{{ _element_end_1_0()

    /**
     * XML parser callback for ending elements.  Used for version 1.0
     * packages.
     *
     * @param resource  $xp    XML parser resource
     * @param string    $name  name of ending element
     *
     * @return void
     *
     * @access private
     */
    function _element_end_1_0($xp, $name)
    {
        $data = trim($this->cdata);
        switch ($name) {
            case 'name':
                switch ($this->prev_element) {
                    case 'package':
                        $this->_packageInfo['package'] = $data;
                        break;
                    case 'maintainer':
                        $this->current_maintainer['name'] = $data;
                        break;
                }
                break;
            case 'extends' :
                $this->_packageInfo['extends'] = $data;
                break;
            case 'summary':
                $this->_packageInfo['summary'] = $data;
                break;
            case 'description':
                $data = $this->_unIndent($this->cdata);
                $this->_packageInfo['description'] = $data;
                break;
            case 'user':
                $this->current_maintainer['handle'] = $data;
                break;
            case 'email':
                $this->current_maintainer['email'] = $data;
                break;
            case 'role':
                $this->current_maintainer['role'] = $data;
                break;
            case 'version':
                if ($this->in_changelog) {
                    $this->current_release['version'] = $data;
                } else {
                    $this->_packageInfo['version'] = $data;
                }
                break;
            case 'date':
                if ($this->in_changelog) {
                    $this->current_release['release_date'] = $data;
                } else {
                    $this->_packageInfo['release_date'] = $data;
                }
                break;
            case 'notes':
                // try to "de-indent" release notes in case someone
                // has been over-indenting their xml ;-)
                // Trim only on the right side
                $data = rtrim($this->_unIndent($this->cdata));
                if ($this->in_changelog) {
                    $this->current_release['release_notes'] = $data;
                } else {
                    $this->_packageInfo['release_notes'] = $data;
                }
                break;
            case 'warnings':
                if ($this->in_changelog) {
                    $this->current_release['release_warnings'] = $data;
                } else {
                    $this->_packageInfo['release_warnings'] = $data;
                }
                break;
            case 'state':
                if ($this->in_changelog) {
                    $this->current_release['release_state'] = $data;
                } else {
                    $this->_packageInfo['release_state'] = $data;
                }
                break;
            case 'license':
                if ($this->in_changelog) {
                    $this->current_release['release_license'] = $data;
                } else {
                    $this->_packageInfo['release_license'] = $data;
                }
                break;
            case 'dep':
                if ($data && !$this->in_changelog) {
                    $this->_packageInfo['release_deps'][$this->d_i]['name'] = $data;
                }
                break;
            case 'dir':
                if ($this->in_changelog) {
                    break;
                }
                array_pop($this->dir_names);
                break;
            case 'file':
                if ($this->in_changelog) {
                    break;
                }
                if ($data) {
                    $path = '';
                    if (count($this->dir_names)) {
                        foreach ($this->dir_names as $dir) {
                            $path .= $dir . '/';
                        }
                    }
                    $path .= $data;
                    $this->filelist[$path] = $this->current_attributes;
                    // Set the baseinstalldir only if the file don't have this attrib
                    if (!isset($this->filelist[$path]['baseinstalldir']) &&
                        isset($this->dir_install))
                    {
                        $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
                    }
                    // Set the Role
                    if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
                        $this->filelist[$path]['role'] = $this->dir_role;
                    }
                }
                break;
            case 'maintainer':
                if (empty($this->_packageInfo['maintainers'][$this->m_i]['role'])) {
                    $this->_packageInfo['maintainers'][$this->m_i]['role'] = 'lead';
                }
                $this->m_i++;
                break;
            case 'release':
                if ($this->in_changelog) {
                    $this->c_i++;
                }
                break;
            case 'changelog':
                $this->in_changelog = false;
                break;
        }
        array_pop($this->element_stack);
        $spos = sizeof($this->element_stack) - 1;
        $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : '';
        $this->cdata = '';
    }

    // }}}
    // {{{ _pkginfo_cdata_1_0()

    /**
     * XML parser callback for character data.  Used for version 1.0
     * packages.
     *
     * @param resource  $xp    XML parser resource
     * @param string    $name  character data
     *
     * @return void
     *
     * @access private
     */
    function _pkginfo_cdata_1_0($xp, $data)
    {
        if (isset($this->cdata)) {
            $this->cdata .= $data;
        }
    }

    // }}}
}
?>


<?php
declare(strict_types=1);

namespace icePHP;
/**
 * 全部页面控件,调用方法:在模板中 {:静态方法名(参数表)}
 * User: Ice
 * Date: 2016/10/28
 * Time: 8:31
 */
class ViewUnit
{
    /**
     * 显示一个返回列表的按钮
     *
     * @param string $url 返回的地址
     * @param string $title 可选,按钮标题
     */
    static public function back(string $url, string $title = ''): void
    {
        display('form/return', [
            'url' => $url,
            'title' => $title
        ]);
    }

    /**
     * 显示一个新增按钮
     *
     * @param string $url 地址
     * @param string $title 按钮文字
     */
    static public function add(string $url, string $title = ''): void
    {
        display('form/add', [
            'url' => $url,
            'title' => $title
        ]);
    }

    /**
     * 显示面包屑,带任意多的参数,格式为名称=>地址 或 单独名称
     * @param $list array
     */
    static public function crumb(array $list): void
    {
        display('layout/crumb', ['list' => $list]);
    }

    /**
     * 显示一个日期控件
     *
     * @param array $config
     *            label 字段标题
     *            name 字段的名字
     *            info 整个行对象
     *            value 字段的值 ,本字段与info字段至少要提供一个,否则值为空
     *            prompt 提示信息,
     *            max 要求最大值为今天
     */
    static public function date(array $config): void
    {
        display('form/date', $config);
    }

    /**
     * 计算折扣
     *
     * @param float $now 当前价
     * @param float $old 原价
     * @param int $pres 精度,默认为小数后一位
     * @return string number
     */
    static public function discount(float $now, float $old, int $pres = 1): string
    {
        // 参数安全处理
        $now = floatval($now);
        $old = floatval($old);

        // 如果当前价无,则是0折
        if (!$now) {
            return '0';
        }

        // 如果原价无,则10折
        if (!$old) {
            return '10';
        }

        // 如果当前价大于原价,则10折
        if ($now > $old) {
            return '10';
        }

        // 否则计算折扣
        return '' . round($now * 10 / $old, $pres);
    }

    /**
     * 美化金额
     *
     * @param float $money
     * @return  float
     */
    static public function money(float $money): float
    {
        // 参数安全处理
        $money = floatval($money);

        // 如果金额无,则显示0
        if (!$money) {
            return 0;
        }

        // 也没什么别的处理了
        return round($money, 2);
    }

    /**
     * 取样式表文件路径
     */
    static public function pathCss(): string
    {
        return Template::pathCss();
    }

    /**
     * 取脚本文件路径
     */
    static public function pathJs(): string
    {
        return Template::pathJs();
    }

    /**
     * 取所有静态资源所在路径
     */
    static public function pathStatic(): string
    {
        return Template::pathStatic();
    }

    /**
     * 获取图片文件根路径
     */
    static public function pathImg(): string
    {
        return Template::pathImg();
    }

    /**
     * 返回根路径,去除最后的斜线
     */
    static public function pathRoot(): string
    {
        return Template::pathRoot();
    }

    /**
     * 隐藏手机号码中间三位
     *
     * @param string $phone
     * @return string
     */
    static public function phone(string $phone): string
    {
        return substr($phone, 0, 4) . '***' . substr($phone, -4);
    }

    /**
     * 生成SEO相关内容
     *
     * @param string $page
     * @param array $params
     * @return string
     */
    static public function seo(string $page, array $params = []): string
    {
        // 取全部SEO配置信息
        try {
            $rules = config('application', 'seo');
        } catch (ConfigException $e) {
            trigger_error('缺少SEO相关配置(application|seo)', E_USER_ERROR);
            exit;
        }
        if (!isset($rules[$page])) {
            $page = '_default';
        }

        // 取本SEO配置 信息
        $rule = $rules[$page];

        // SEO三项
        $title = $rule['title'];
        $keywords = $rule['keywords'];
        $description = $rule['description'];

        // 替换,因变量不会太多,逐个替换
        foreach ($params as $key => $value) {
            $title = str_replace('{$' . $key . '}', $value, $title);
            $keywords = str_replace('{$' . $key . '}', $value, $keywords);
            $description = str_replace('{$' . $key . '}', $value, $description);
        }

        // 构造 HTML中SEO三项的语法
        $ret = '<title>' . $title . '</title>' . "\n";
        $ret .= '<meta name="keywords" content="' . $keywords . '"/>' . "\n";
        $ret .= '<meta name="description" content="' . $description . '"/>' . "\n";

        return $ret;
    }

    /**
     * 对超长字符串进行截断
     *
     * @param string $v 源字符串
     * @param int $len 允许的最大长度
     * @return string 截断后的串
     */
    static public function short(?string $v, int $len = 20): string
    {
        if (!$v) {
            return '';
        }
        if (mb_strlen($v, 'utf-8') <= $len) {
            return $v;
        }

        $v = mb_substr($v, 0, $len - 3, 'utf-8') . '...';
        return $v;
    }

    /**
     * 显示一个不可编辑的文本
     * @param array $config
     *            label 字段标题
     *            name 字段的名字
     *            info 整个行对象
     *            value 字段的值 ,本字段与info字段至少要提供一个,否则值为空
     *            prompt 提示信息
     */
    static public function string(array $config): void
    {
        display('form/string', $config);
    }

    /**
     * 显示一个表单提交按钮
     * @param string $title
     */
    static public function submit(string $title = '提交'): void
    {
        display('form/submit', ['title' => $title]);
    }

    /**
     * 显示一个不可编辑的表格
     * @param array $config
     *            label 字段标题
     *            name 字段的名字
     *            info 整个行对象
     *            value 字段的值 ,本字段与info字段至少要提供一个,否则值为空
     *            prompt 提示信息
     */
    static public function table(array $config): void
    {
        display('form/table', $config);
    }

    /**
     * 获取当前版本号
     * @return string
     */
    static public function ver(): string
    {
        return configDefault('0', 'system', 'version');
    }

    /**
     * 按单词对长段落进行活力
     * @param string $str 原文
     * @param int $num 单词数
     * @return string 省略后的文字
     */
    static public function wordLimit(string $str, int $num = 100): string
    {
        if (strlen($str) < $num) {
            return $str;
        }

        $word = preg_split('/\s/u', $str, -1, PREG_SPLIT_NO_EMPTY);

        if (count($word) <= $num) {
            return $str;
        }

        $str = "";

        for ($i = 0; $i < $num; $i++) {
            $str .= $word[$i] . " ";
        }

        return trim($str) . '…';
    }
}


<?php

namespace Reaction\Annotations;

use Doctrine\Common\Annotations\Annotation\Attribute;
use Doctrine\Common\Annotations\Annotation\Attributes;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;

/**
 * @Annotation
 * @Target({"METHOD"})
 * @Attributes({
 *   @Attribute("path", type = "string", required = true),
 *   @Attribute("method",  type = "array"),
 * })
 *
 * Some Annotation using a constructor
 */
class CtrlAction
{
    /** @var array Possible HTTP request methods */
    const METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "PATCH", "TRACE"];

    /** @var array Http method */
    public $method = ['GET'];

    /**
     * @Required
     * @var string Action path
     */
    public $path;

    /**
     * CtrlAction constructor.
     * @param array $values
     * @throws \Exception
     */
    public function __construct($values = [])
    {
        if (count($values) === 1 && isset($values['value'])) {
            $values = $values['value'];
        }
        if (is_string($values)) {
            $this->path = $values;
        } elseif (is_array($values)) {
            if (!empty($values['method'])) {
                $this->method = $this->normalizeMethod($values['method']);
            }
            if (!empty($values['path'])) {
                $this->path = $this->normalizePath($values['path']);
            }
        }

        if (empty($this->path)) {
            throw new \Exception("Property 'path' is required");
        }
    }

    /**
     * Normalize path
     * @param string $path
     * @return string|null
     */
    protected function normalizePath($path) {
        if (!isset($path)) {
            return null;
        }
        $path = trim((string)$path);
        if (($qPos = strpos($path, '?')) !== false) {
            $path = mb_substr($path, 0, $qPos);
        }
        if ($path !== "/") {
            $path = rtrim($path, '/');
        }
        return $path;
    }

    /**
     * Normalize method
     * @param array|string $method
     * @return array
     */
    protected function normalizeMethod($method)
    {
        $method = (array)$method;
        foreach ($method as &$methodRow) {
            $methodRow = strtoupper($methodRow);
        }
        if (in_array('ANY', $method)) {
            $method = static::METHODS;
        }
        return $method;
    }
}


<?php
namespace wiggum\services\db;

use \PDO;
use \Closure;
use \InvalidArgumentException;

class Builder {

	//where($column, $operator = null, $value = null, $boolean = 'and')

	//orWhere - delete

	//whereBetween($column, array $values, $boolean = 'and', $not = false)
	//orWhereBetween - delete
	//whereNotBetween - delete
	//orWhereNotBetween - delete

	//whereNested($callback, $boolean = 'and')
	//addNestedWhereQuery - Delete

	//whereSub - hold

	//whereExists()
	//orWhereExists - delete
	//whereNotExists - delete
	//orWhereNotExists - delete

	//whereIn($column, $values, $boolean = 'and', $not = false)
	//orWhereIn - delete
	//whereNotIn - delete
	//orWhereNotIn - delete

	//whereInSub - delete

	//whereNull()
	//orWhereNull - delete
	//whereNotNull - delete
	//orWhereNotNull - delete

	//whereDate - delete
	//whereDay - delete
	//whereMonth - delete
	//whereYear - delete

	protected $type;
	protected $db;
	protected $grammar;
	protected $bindings = array (
			'select' => [],
			'join' => [],
			'where' => [],
			'having' => [],
			'order' => [],
			'inserts' => []
	);

	public $aggregate;
	public $columns;
	public $distinct = false;
	public $from;
	public $joins;
	public $wheres;
	public $groups;
	public $orders;
	public $limit;
	public $offset;
	public $lock;

	public $inserts;
	public $updates;

	public function __construct(DB $db, Grammar $grammar) {
		$this->db = $db;
		$this->grammar = $grammar;
	}

	//TODO simplify this and make a seprate method for batch inserts
	/**
	 *
	 * @param array $columns
	 * @return boolean|\wiggum\db\Builder
	 */
	public function insert(array $columns) {
		$this->type = 'insert';

		if (empty($columns)) return true;

		if (!is_array(reset($columns))) {
			$columns = [$columns];
		}

		foreach ($columns as $record) {
			foreach ($record as $column) {
				$this->addBinding($column, 'inserts');
			}
		}

		$this->inserts = $columns;

		return $this;
	}

	/**
	 *
	 * @param array $values
	 * @return \wiggum\db\Builder
	 */
	public function update(array $values) {
		$this->type = 'update';

		$this->updates = $values;

		return $this;
	}

	/**
	 *
	 * @param string $id
	 * @return \wiggum\db\Builder
	 */
	public function delete($id = null) {
		$this->type = 'delete';

		if (!is_null($id)) $this->where('id', '=', $id);

		return $this;
	}

	/**
	 * Set the columns to be selected.
	 *
	 * @param array $columns
	 * @return \wiggum\db\Builder
	 */
	public function select($columns = ['*']) {
		$this->type = 'select';

		if (!is_array($columns)) {
			$columns = [$columns];
		}

		$this->columns = $columns;

		return $this;
	}

	/**
	 * Force the query to only return distinct results.
	 *
	 * @return \wiggum\db\Builder
	 */
	public function distinct() {
		$this->distinct = true;

		return $this;
	}

	/**
	 * Set the table which the query is targeting.
	 *
	 * @param string $table
	 * @return \wiggum\db\Builder
	 */
	public function from($table) {
		$this->from = is_array($table) ? $table : [$table];

		return $this;
	}

	/**
	 * Add a join clause to the query.
	 *
	 * @param  string  $table
	 * @param  string  $firstColumn
	 * @param  string  $operator
	 * @param  string  $secondColumn
	 * @param  string  $type
	 * @param  bool    $where
	 * @return \wiggum\db\Builder
	 */
	public function join($table, $firstColumn, $operator = null, $secondColumn = null, $type = 'inner', $where = false) {

		if ($firstColumn instanceof Closure) {

			$this->joins[] = new JoinClause($type, $table);

			call_user_func($firstColumn, end($this->joins));

		} else {

			$join = new JoinClause($type, $table);

			$this->joins[] = $join->on($firstColumn, $operator, $secondColumn, 'and', $where);

		}

		return $this;
	}

	/**
	 * Add a "join where" clause to the query.
	 *
	 * @param string $table
	 * @param string $firstColumn
	 * @param string $operator
	 * @param string $secondColumn
	 * @param string $type
	 * @return \wiggum\db\Builder
	 */
	public function joinWhere($table, $firstColumn, $operator, $value, $type = 'inner') {
		return $this->join($table, $firstColumn, $operator, $value, $type, true);
	}

	/**
	 * Add a left join to the query.
	 *
	 * @param string $table
	 * @param string $firstColumn
	 * @param string $operator
	 * @param string $secondColumn
	 * @return \wiggum\db\Builder
	 */
	public function leftJoin($table, $firstColumn, $operator = null, $secondColumn = null) {
		return $this->join($table, $firstColumn, $operator, $secondColumn, 'left');
	}

	/**
	 * Add a "join where" clause to the query.
	 *
	 * @param string $table
	 * @param string $firstColumn
	 * @param string $operator
	 * @param string $secondColumn
	 * @return \wiggum\db\Builder
	 */
	public function leftJoinWhere($table, $firstColumn, $operator, $value) {
		return $this->joinWhere($table, $firstColumn, $operator, $value, 'left');
	}

	/**
	 * Add a right join to the query.
	 *
	 * @param string $table
	 * @param string $firstColumn
	 * @param string $operator
	 * @param string $secondColumn
	 * @return \wiggum\db\Builder
	 */
	public function rightJoin($table, $firstColumn, $operator = null, $secondColumn = null) {
		return $this->join($table, $firstColumn, $operator, $secondColumn, 'right');
	}

	/**
	 * Add a "right join where" clause to the query.
	 *
	 * @param string $table
	 * @param string $firstColumn
	 * @param string $operator
	 * @param string $secondColumn
	 * @return \wiggum\db\Builder
	 */
	public function rightJoinWhere($table, $firstColumn, $operator, $value) {
		return $this->joinWhere($table, $firstColumn, $operator, $value, 'right');
	}

	/**
	 * Add a join clause manually
	 *
	 * @param JoinClause $joinClause
	 * @return \wiggum\db\Builder
	 */
	public function addJoinClause(JoinClause $joinClause) {

		$this->joins[] = $joinClause;

		return $this;
	}

	/**
	 *
	 * @param mixed $column
	 * @param string $operator
	 * @param string $value
	 * @param string $boolean
	 * @return \wiggum\db\Builder
	 */
	public function where($column, $operator = null, $value = null, $boolean = 'and') {
		// If the column is an array, we will assume it is an array of key-value pairs
		// and can add them each as a where clause. We will maintain the boolean we
		// received when the method was called and pass it into the nested where.
		if (is_array($column)) {
			foreach ($column as $key => $value) {
				$this->where($key, '=', $value);
			}

			return $this;
		}

		// If the columns is actually a Closure instance, we will assume
		// a nested where statement which is wrapped in parenthesis.
		if ($column instanceof Closure) {
			return $this->whereNested($column, $boolean);
		}

		// If the value is "null", we will just assume the developer wants to add a
		// where null clause to the query. So, we will allow a short-cut here to
		// that method for convenience so the developer doesn't have to check.
		if (is_null($value)) {
			return $this->whereNull($column, $boolean, is_null($operator) || $operator == '=' ? false : true);
		}

		// Now that we are working with just a simple query we can put the elements
		// in our array and add the query binding to our array of bindings that
		// will be bound to each SQL statements when it is finally executed.
		$type = 'Basic';

		$this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
		$this->addBinding($value, 'where');

		return $this;
	}

		/**
	 *
	 * @param Closure $callback
	 * @param string $boolean
	 * @return \wiggum\db\Builder
	 */
	public function whereNested(Closure $callback, $boolean = 'and') {
		$query = (new static($this->db, $this->grammar))->from($this->from);

		call_user_func($callback, $query);

		return $this->addNestedWhereQuery($query, $boolean);
	}

	/**
	 *
	 * @param  \wiggum\db\Builder $query
	 * @param  string  $boolean
	 * @return $this
	 */
	public function addNestedWhereQuery($query, $boolean = 'and') {
		if (count($query->wheres)) {
			$type = 'Nested';

			$this->wheres[] = compact('type', 'query', 'boolean');

			$this->addBinding($query->getBindings(), 'where');
		}

		return $this;
	}

	/**
	 *
	 * @param string $column
	 * @param array $values
	 * @param string $boolean
	 * @param string $not
	 * @return \wiggum\db\Builder
	 */
	public function whereBetween($column, array $values, $boolean = 'and', $not = false) {
		$type = 'between';

		$this->wheres[] = compact('type', 'column', 'boolean', 'not');

		$this->addBinding($values, 'where');

		return $this;
	}

	/**
	 *
	 * @param string $column
	 * @param array $values
	 * @param string $boolean
	 * @param string $not
	 * @return \wiggum\db\Builder
	 */
	public function whereIn($column, array $values, $boolean = 'and', $not = false) {
		$type = 'In';

		$this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not');

		$this->addBinding($values, 'where');

		return $this;
	}

	/**
	 *
	 * @param string $column
	 * @param string $boolean
	 * @param string $not
	 * @return \wiggum\db\Builder
	 */
	public function whereNull($column, $boolean = 'and', $not = false) {
		$type = 'Null';

		$this->wheres[] = compact('type', 'column', 'boolean', 'not');

		return $this;
	}

	/**
	 *
	 * @param  array|string $column,...
	 * @return $this
	 */
	public function groupBy() {
		foreach (func_get_args() as $arg) {
			$this->groups = array_merge((array) $this->groups, is_array($arg) ? $arg : [$arg]);
		}

		return $this;
	}

	/**
	 *
	 * @param string $column
	 * @param string $direction
	 * @return \wiggum\db\Builder
	 */
	public function orderBy($column, $direction = 'asc') {
		$direction = strtolower($direction) == 'asc' ? 'asc' : 'desc';

		$this->orders[] = compact('column', 'direction');

		return $this;
	}

	/**
	 *
	 * @param int $value
	 * @param int $offset
	 * @return \wiggum\db\Builder
	 */
	public function limit($value, $offset = null) {
		if ($value > 0)
			$this->limit = $value;

		if (isset($offset))
			$this->offset($offset);

		return $this;
	}

	/**
	 *
	 * @param int $value
	 * @return \wiggum\db\Builder
	 */
	public function offset($value) {
		$this->offset = max(0, $value);

		return $this;
	}

	/**
	 * Get the SQL representation of the query.
	 *
	 * @return string
	 */
	public function toSql() {

		if ($this->type == 'insert') {
			return $this->grammar->compileInsert($this);
		} else if ($this->type == 'update') {
			return $this->grammar->compileUpdate($this);
		} else if ($this->type == 'delete') {
			return $this->grammar->compileDelete($this);
		} else {
			return $this->grammar->compileSelect($this);
		}

	}

	/**
	 *
	 * @param boolean $assoc
	 */
	public function fetchRow($assoc = false) {
		if ($assoc) {
			return $this->db->fetchRow($this->toSql(), $this->getBindings());
		} else {
			return $this->db->fetchRow($this->toSql(), $this->getBindings(), PDO::FETCH_OBJ);
		}
	}

	/**
	 *
	 * @param boolean $assoc
	 */
	public function fetchRows($assoc = false) {
		if ($assoc) {
			return $this->db->fetchRows($this->toSql(), $this->getBindings());
		} else {
			return $this->db->fetchRows($this->toSql(), $this->getBindings(), PDO::FETCH_OBJ);
		}
	}

	/**
	 * Fetch all values of a single column from the result set into an array.
	 *
	 * @return array
	 */
	public function fetchAllColumn() {
		return $this->db->fetchRows($this->toSql(), $this->getBindings(), PDO::FETCH_COLUMN);
	}

	/**
	 * Fetch a single value from first column in the next row of a result set.
	 *
	 * @return string
	 */
	public function fetchValue() {
		return $this->db->fetchColumn($this->toSql(), $this->getBindings());
	}

	/**
	 *
	 * @param string $columnKey
	 * @param boolean $assoc
	 */
	public function fetchRowsWithColumnKey($columnKey = 'id', $assoc = true) {

		if (!is_array($this->columns) || count($this->columns) <= 0 || $this->columns[0] == '*') {
			$this->columns = [$columnKey];
		}

		array_unshift($this->columns, $columnKey);

		if ($assoc) {
			return array_map('reset', $this->db->fetchRows($this->toSql(), $this->getBindings(), PDO::FETCH_GROUP|PDO::FETCH_ASSOC));
		} else {
			return array_map('reset', $this->db->fetchRows($this->toSql(), $this->getBindings(), PDO::FETCH_GROUP|PDO::FETCH_OBJ));
		}
	}

	/**
	 *
	 * @param string $keyColumn
	 * @param string $valueColumn
	 */
	public function fetchKeyValuePair($keyColumn, $valueColumn) {

		$this->columns = [$keyColumn, $valueColumn];

		return $this->db->fetchRows($this->toSql(), $this->getBindings(), PDO::FETCH_KEY_PAIR);
	}

	/**
	 *
	 * @param unknown $instance
	 */
	public function fetchObject($instance) {
		return $this->db->fetchObject($this->toSql(), $this->getBindings(), $instance);
	}

	/**
	 *
	 * @param unknown $instance
	 */
	public function fetchObjects($instance) {
		return $this->db->fetchObjects($this->toSql(), $this->getBindings(), $instance);
	}

/*	public function fetchObjectsWithColumnKey($instance, $columnKey = 'id') {

		if (!is_array($this->columns) || count($this->columns) <= 0 || $this->columns[0] == '*') {
			$this->columns = [$columnKey];
		}

		array_unshift($this->columns, $columnKey);

		return array_map('reset', $this->db->fetchObjects($this->toSql(), $this->getBindings(), $instance, PDO::FETCH_GROUP|PDO::FETCH_INTO));
	}
*/

	/**
	 *
	 * @param boolean $lastInsId
	 */
	public function execute($lastInsId = false) {

		if ($this->type == 'insert') {

			$sql = $this->grammar->compileInsert($this);
			return $this->db->executeQuery($sql, $this->getBindings(), $lastInsId);

		} else if ($this->type == 'update') {

			$sql = $this->grammar->compileUpdate($this);
			//bindings done here to make sure the order is correct.
			$bindings = array_values(array_merge($this->updates, $this->getBindings()));
			return $this->db->executeQuery($sql, $bindings, false);

		} else if ($this->type == 'delete') {

			$sql = $this->grammar->compileDelete($this);
			return $this->db->executeQuery($sql, $this->getBindings(), false);

		}

	}

	/**
	 *
	 * @param string $columns
	 * @return number
	 */
	public function count($columns = '*') {
		if (!is_array($columns)) {
			$columns = [$columns];
		}

		return (int) $this->aggregate('count', $columns);
	}

	public function sum($column) {
		$result = $this->aggregate('sum', [$column]);

		return $result ?: 0;
	}

	public function min($column) {
		return $this->aggregate('min', [$column]);
	}

	public function max($column) {
		return $this->aggregate('max', [$column]);
	}

	public function avg($column) {
		return $this->aggregate('avg', [$column]);
	}

	/**
	 *
	 * @param string $function
	 * @param array $columns
	 * @return unknown
	 */
	public function aggregate($function, $columns = array('*')) {
		$this->aggregate = compact('function', 'columns');

		// We will also back up the columns and select bindings since the
		// select clause will be removed when performing the aggregate function.
		$previousColumns = $this->columns;
		$previousSelectBindings = $this->bindings['select'];

		$this->bindings['select'] = [];
		$result = $this->fetchRow(true);

		// Once we have executed the query, we will reset the aggregate property so
		// that more select queries can be executed against the database.
		$this->aggregate = null;
		$this->columns = $previousColumns;
		$this->bindings['select'] = $previousSelectBindings;

		if (isset($result)) {
			return $result['aggregate'];
		}
	}

	/**
	 *
	 * @return array
	 */
	public function getColumnListing() {
		$sql = $this->grammar->compileColumnExists();

		return $this->db->fetchRows($sql, [$this->from[0]], PDO::FETCH_COLUMN);
	}

	/**
	 * Add a binding to the query.
	 *
	 * @param  mixed   $value
	 * @param  string  $type
	 * @return $this
	 *
	 * @throws \InvalidArgumentException
	 */
	public function addBinding($value, $type) {
		if (!array_key_exists($type, $this->bindings)) {
			throw new InvalidArgumentException("Invalid binding type: {$type}.");
		}

		//TODO - add bindings flat
		if (is_array($value)) {
			$this->bindings[$type] = array_values(array_merge($this->bindings[$type], $value));
		} else {
			$this->bindings[$type][] = $value;
		}

		return $this;
	}

	/**
	 *
	 * @param array $bindings
	 * @param unknown $type
	 * @throws InvalidArgumentException
	 * @return \wiggum\db\Builder
	 */
	public function setBindings(array $bindings, $type) {
		if (!array_key_exists($type, $this->bindings)) {
			throw new InvalidArgumentException("Invalid binding type: {$type}.");
		}

		$this->bindings[$type] = $bindings;

		return $this;
	}

	/**
	 *
	 * @return multitype:unknown
	 */
	public function getBindings() {
		return $this->flattenArray($this->bindings);
	}

	//TODO - remove once bindings are added flat
	/**
	 *
	 * @param unknown $array
	 * @return multitype:unknown
	 */
	private function flattenArray($array) {
		$return = [];

		array_walk_recursive($array, function($x) use (&$return) { $return[] = $x; });

		return $return;
	}

}


<?php

namespace ifcanduela\db;

use ifcanduela\db\traits\ConditionBuilder;

class UpdateQuery extends Query
{
    /** @var string */
    protected $table;
    /** @var array */
    protected $set;

    use ConditionBuilder;

    public function __construct(string $table = null)
    {
        if ($table) {
            $this->table($table);
        }
    }

    /**
     * Columns and values to set.
     *
     * @param array $values
     * @return self
     */
    public function set(array $values)
    {
        $this->changed = true;
        $this->set = $values;

        return $this;
    }

    /**
     * Build the query,
     */
    protected function build()
    {
        if (!$this->table) {
            throw new \RuntimeException("No tables provided for UPDATE clause");
        }

        if (!$this->set) {
            throw new \RuntimeException("No columns provided for UPDATE clause");
        }

        $sql = ['UPDATE'];
        $sql[] = $this->table;

        $sql[] = 'SET';
        $set = [];

        foreach ($this->set as $field => $value) {
            $placeholder = $this->addPlaceholder($value);
            $set[] = "{$field} = {$placeholder}";
        }

        $sql[] = implode(', ', $set);

        if ($this->conditions) {
            $sql[] = 'WHERE';
            $sql[] = $this->buildConditions($this->conditions);
        }

        $this->sql = implode(' ', $sql);

        $this->changed = false;
    }
}


<?php

namespace Nether\Input;
use \Nether;
use \Exception;

class Filter {
/*//
this class provides a base filtering system for input data. it allows you to
define a source and then define specific filtering mechanisms for each field
if you so choose.

	$input = (new Nether\Input\Filter($_POST))
	->Email(function($t){ return filter_var($t,FILTER_VALIDATE_EMAIL); })
	->Hostname(function($t){ return preg_replace('//','',$t); });

	if(!$input->Email) {
		die('you need to supply a valid email, jerkface.');
	}

//*/

	protected
	$Dataset = null;
	/*//
	@type array
	the input source dataset that we will request.
	//*/

	protected
	$DefaultFunction = null;
	/*//
	@type callable
	the default filter that will be applied to all requests that do not have
	a specific filter set.
	//*/

	protected
	$Functions = [];
	/*//
	@type array
	the filters that are to be applied.
	//*/

	protected
	$Case = false;
	/*//
	@type bool
	if this class should be case sensitive on the input keys. by default it is
	false because we often create html url variables with lowercase but want
	to be all pascal or camel in the server side.
	//*/

	////////////////
	////////////////

	public function
	__Construct($Dataset=null,$Opt=null) {
		$Opt = new Nether\Object\Mapped($Opt,[
			'Case' => false
		]);

		$this->Case = $Opt->Case;

		if($Dataset !== null)
		$this->SetDataset($Dataset);

		return;
	}

	////////////////
	////////////////

	public function
	__Get($Key) {
	/*//
	@argv string Key
	@return mixed|false

	fetch the value from the datastore automatically checking if it exists,
	returning the value if it does. if it does not exist we will return false.
	the false however is not a valid enough test if all you care about is
	knowing that the key existed, because you could store a null in a key. use
	the Exists() method if all you care about is that.
	//*/

		if(!is_array($this->Dataset))
		throw new Exception('No dataset bound to this filter object yet.');

		$Key = $this->PrepareKey($Key);

		// return the value through the filtering method if one was defined.
		if(array_key_exists($Key,$this->Functions))
		return $this->Functions[$Key](
			(array_key_exists($Key,$this->Dataset))?
				($this->Dataset[$Key]):
				(null),
			$Key
		);

		// return the value through the default filter if one was defined.
		if(is_callable($this->DefaultFunction))
		return call_user_func(
			$this->DefaultFunction,
			(array_key_exists($Key,$this->Dataset))?
				($this->Dataset[$Key]):
				(null),
			$Key
		);

		// return the value in the end.
		return $this->Raw($Key);
	}

	public function
	__Set($Key,$Val) {
	/*//
	@argv string Key, mixed Value
	@return mixed
	handle pushing data into the datastore.
	//*/

		return $this->Dataset[$this->PrepareKey($Key)] = $Val;
	}

	public function
	__Call($Key,$Argv) {
	/*//
	@argv string Key, array Args
	//*/

		if(count($Argv) !== 1)
		throw new Exception('only expecting one argument to define a filter.');

		if(!is_callable($Argv[0]))
		throw new Exception('the filter must be callable.');

		$this->Functions[$this->PrepareKey($Key)] = $Argv[0];

		return $this;
	}

	public function
	__Invoke($Key) {
	/*//
	@argv array Input
	@return callable or false
	allow retrieval of a defined callback via the invoke syntax. returns
	boolean false if no callback was defined.

	* callable $this('Something');

	to fetch something that was defined as

	* $this->Something(callable);

	//*/

		$Key = $this->PrepareKey($Key);

		if(array_key_exists($Key,$this->Functions))
		return $this->Functions[$Key];

		return false;
	}

	////////////////
	////////////////

	public function
	Exists($Key) {
	/*//
	@argv string Key
	@return bool
	determine if the specified key exists in the original dataset.
	//*/

		return array_key_exists($this->PrepareKey($Key),$this->Dataset);
	}

	public function
	Raw($Key) {
	/*//
	@argv string Key
	@return mixed
	get the draw data without running through input filters.
	//*/

		$Key = $this->PrepareKey($Key);

		return (array_key_exists($Key,$this->Dataset))?
			($this->Dataset[$Key]):
			(null);
	}

	public function
	GetDataset() {
	/*//
	@return array
	//*/

		return $this->Dataset;
	}

	public function
	SetDataset($Input) {
	/*//
	@argv array Input
	@argv object Input
	@return $this
	//*/

		if(is_array($Input) || is_object($Input))
		$this->Dataset = $this->PrepareDataset((array)$Input);

		else
		throw new Exception('Dataset must be an array or object.');

		return $this;
	}

	public function
	GetDefaultFunction() {
	/*//
	@return callable
	//*/

		return $this->DefaultFunction;
	}

	public function
	SetDefaultFunction(callable $Func) {
	/*//
	@argv callable Function
	@return $this
	//*/

		$this->DefaultFunction = $Func;
		return $this;
	}

	////////////////
	////////////////

	protected function
	PrepareKey($Key) {
		if(!$this->Case)
		return strtolower($Key);

		return $Key;
	}

	protected function
	PrepareDataset($Data) {
		if(!$this->Case) {
			$New = [];

			foreach((array)$Data as $Key => $Val)
			$New[strtolower($Key)] = $Val;

			return $New;
		} else {
			return (array)$Data;
		}
	}

}


<?php
/**
 * This file is part of the CarteBlanche PHP framework.
 *
 * (c) Pierre Cassat <me@e-piwi.fr> and contributors
 *
 * License Apache-2.0 <http://github.com/php-carteblanche/carteblanche/blob/master/LICENSE>
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CarteBlanche\Library\AutoObject;

use \CarteBlanche\CarteBlanche;
use \CarteBlanche\Library\AutoObject\Database;
use \CarteBlanche\Library\AutoObject\Table;
use \CarteBlanche\Library\AutoObject\Field;
use \CarteBlanche\Library\AutoObject\Relation;
use \CarteBlanche\Library\AutoObject\AutoObjectMapper;
use \CarteBlanche\Model\BaseModel;
use \Patterns\Commons\Collection;

/**
 */
class AutoObject
{

    protected $object_name;
    protected $object_table_name;
    protected $object_database_name;
    protected $object_model_name;
    protected $object_structure;

    protected $object_model;
    protected $object_table;
    protected $object_fields;
    protected $object_relations;
    protected $object_fields_by_type;

    protected $editable;

    public function __construct($table, $structure, $name = null, $db = null)
    {
        $this->setTableName( $table );
        $this->setStructure( $structure );
        $this->setName( !is_null($name) ? $name : $table );
        $this->setDatabaseName( !is_null($db) ? $db : 'default' );

        $_model = $this->getStructureEntry('model');
        $this->setModelName( !is_null($_model) ? $_model : '\CarteBlanche\Model\BaseModel' );

        $this->editable = $this->getStructureEntry('editable', false);

        $this->object_fields = new Collection;
        $this->object_relations = new Collection;
    }

// ------------------
// SETTERS
// ------------------

    /**
     * Set the object name
     * @param string $name The object name to set
     * @return self Return the object for method chaining
     */
    public function setName($name = null)
    {
        if (empty($name) || !is_string($name)) {
            throw new \InvalidArgumentException(
                sprintf('Object name is invalid! (must be a string, got "%s")', var_export($name,1))
            );
        }
        $this->object_table_name = $name;
        return $this;
    }

    /**
     * Set the object structure
     * @param string $name The object structure to set
     * @return self Return the object for method chaining
     */
    public function setStructure($structure = null)
    {
        if (empty($structure) || !is_array($structure)) {
            throw new \InvalidArgumentException(
                sprintf('Object structure is invalid! (must be an array, got "%s")', gettype($structure))
            );
        }
        $this->object_structure = $structure;
        return $this;
    }

    /**
     * Set the object table name
     * @param string $name The object table name to set
     * @return self Return the object for method chaining
     */
    public function setTableName($name = null)
    {
        if (empty($name) || !is_string($name)) {
            throw new \InvalidArgumentException(
                sprintf('Object table name is invalid! (must be a string, got "%s")', var_export($name,1))
            );
        }
        $this->object_table_name = $name;
        return $this;
    }

    /**
     * Set the object database name
     * @param string $name The object database name to set
     * @return self Return the object for method chaining
     */
    public function setDatabaseName($name = null)
    {
        if (empty($name) || !is_string($name)) {
            throw new \InvalidArgumentException(
                sprintf('Object database name is invalid! (must be a string, got "%s")', var_export($name,1))
            );
        }
        $this->object_database_name = $name;
        return $this;
    }

    /**
     * Set the object model name
     * @param string $name The object model name to set
     * @return self Return the object for method chaining
     */
    public function setModelName($name = null)
    {
        if (empty($name) || !is_string($name)) {
            throw new \InvalidArgumentException(
                sprintf('Object model name is invalid! (must be a string, got "%s")', var_export($name,1))
            );
        }
        if (false===\CarteBlanche\App\Loader::classExists($name)) {
            throw new \InvalidArgumentException(
                sprintf('Object model\'s class can\'t be found! (got "%s")', var_export($name,1))
            );
        }
        $this->object_model_name = $name;
        return $this;
    }

// ------------------
// GETTERS
// ------------------

    public function getName()
    {
        return $this->object_name;
    }

    public function getTableName()
    {
        return $this->object_table_name;
    }

    public function getDatabaseName()
    {
        return $this->object_database_name;
    }

    public function getStructure()
    {
        return $this->object_structure;
    }

    public function getStructureEntry($entry, $default = null)
    {
        return isset($this->object_structure[$entry]) ? $this->object_structure[$entry] : $default;
    }

    public function getTable()
    {
        if (empty($this->object_table))
            $this->_buildTable();
        return $this->object_table;
    }

    public function getModel($auto_populate = true, $advanced_search = false)
    {
        if (empty($this->object_model))
            $this->_buildModel($auto_populate,$advanced_search);
        return $this->object_model;
    }

    public function getFields()
    {
        if ($this->object_fields->count()==0)
            $this->_buildFields();
        return $this->object_fields->getCollection();
    }

    public function getField($field_name, $default = null)
    {
        if ($this->object_fields->count()==0)
            $this->_buildFields();
        return $this->object_fields->key_exists($field_name) ? $this->object_fields->getEntry($field_name) : $default;
    }

    public function getRelations()
    {
        if ($this->object_relations->count()==0)
            $this->_buildRelations();
        return $this->object_relations->getCollection();
    }

    public function getRelation($field_name, $default = null)
    {
        if ($this->object_relations->count()==0)
            $this->_buildRelations();
        return $this->object_relations->key_exists($field_name) ? $this->object_relations->getEntry($field_name) : $default;
    }

    public function getFieldsByType()
    {
        if (empty($this->object_fields_by_type))
            $this->_buildFieldsByType();
        return $this->object_fields_by_type;
    }

    public function isEditable()
    {
        return true===$this->editable;
    }

// ------------------
// BUILDERS
// ------------------

    /**
     * Build the object model
     * @return void
     */
    protected function _buildModel($auto_populate = true, $advanced_search = false)
    {
        if (\CarteBlanche\App\Loader::classExists($this->object_model_name)) {
            try {
                $this->object_model = new $this->object_model_name(
                    $this->getTableName(),
                    $this->getFields(),
                    $this->getStructure(),
                    $auto_populate, $advanced_search
                );
                $db = CarteBlanche::getContainer()->get('entity_manager')
                    ->getStorageEngine($this->getDatabaseName());
                $this->object_model->setStorageEngine($db);

            } catch ( \Exception $e ) {
                throw new \Exception(
                    sprintf('Model "%s" constructor seems not compliant with the "\CarteBlanche\Interfaces\ModelInterface" interface!', $this->object_model_name)
                );
            }
        } else {
            trigger_error( sprintf('Model "%s" can\'t be found!', $this->object_model_name), E_USER_ERROR);
        }
    }

    /**
     * Build the object table object
     * @return void
     */
    protected function _buildTable()
    {
        $this->object_table = new Table( $this->object_table_name, $this->object_database );
    }

    /**
     * Build an array of the object fields
     * @return void
     */
    protected function _buildFields()
    {
        $db = CarteBlanche::getContainer()->get('entity_manager')
            ->getStorageEngine($this->getDatabaseName());
        foreach ($this->getStructureEntry('structure') as $_field=>$_data)  {
            $_f = null;
            if (preg_match('/^(.*)_id$/i', $_field, $matches)) {
                if (isset($matches[1]) && is_string($matches[1])) {
                    $related_object = $matches[1];
                    if ($related_object=='parent')
                        $related_object = $this->getTableName();
                    $table = AutoObjectMapper::getAutoObject($related_object, $this->getDatabaseName());
                    $_f = new Relation(
                        $_field, $_data, $db, $related_object, $table->getModel()
                    );
                }
            } else {
                $_f = new Field( $_field, $_data, $db );
            }

            if (!is_null($_f))
                $this->object_fields->setEntry( $_field, $_f );
        }
    }

    /**
     * Build an organized array of the object fields, by type (string, numeric or relation)
     * @return void
     */
    protected function _buildFieldsByType()
    {
        $this->object_fields_by_type = array(
            'num'=>array(),
            'str'=>array(),
            'rel'=>array()
        );
        foreach ($this->getStructureEntry('structure') as $_field=>$_data) {
            if (
                preg_match('/^integer(\((.*)\))?/i', $_data['type']) ||
                preg_match('/^float(\((.*)\))?/i', $_data['type'])
            ) $this->object_fields_by_type['num'][] = $_field;
            elseif (
                preg_match('/^varchar(\((.*)\))?/i', $_data['type']) ||
                preg_match('/(.*)text$/i', $_data['type'])
            ) $this->object_fields_by_type['str'][] = $_field;
            elseif (
                preg_match('/^(.*)_id$/i', $_data['type'])
            ) $this->object_fields_by_type['rel'][] = $_field;
        }
    }

    /**
     * Build an array of the object relation fields
     * @return void
     */
    protected function _buildRelations()
    {
        $db = CarteBlanche::getContainer()->get('entity_manager')
            ->getStorageEngine($this->getDatabaseName());

        foreach ($this->getStructureEntry('structure') as $_field=>$_fdata) {
            $_relobject=$_relfield=null;

            if ($_field!='parent_id' && preg_match('/^(.*)_id$/i', $_field, $matches)) {
                if (isset($matches[1]) && is_string($matches[1]))
                    $_relobject = $matches[1];
                $_relfield = 'id';
                $_intfield = $_field;
                $_field = str_replace('_id', '', $_field);
            } elseif (!empty($_fdata['related'])) {
                $_relobject_f = $_fdata['related'];
                if (!substr_count($_relobject_f, ':')) $_relobject_f .= ':id';
                $matches = explode(':', $_relobject_f);
                $_relobject = $matches[0];
                $_relfield = $matches[1];
                $_intfield = $_field;
                $_field = 'parent';
            }

            if (isset($_relobject) && $_relobject==$this->getTableName()) {
                $this->object_relations->setEntry( $_field, array(
                    'related_table'=>$_relobject,
                    'related_field'=>$_relfield,
                    'internal_field'=>$_intfield,
                    'model'=>$this->getModel()->getModelClone(),
                    'slug_field'=>$this->getModel()->getSlugField(),
                    'self'=>true
                ));
            } elseif (isset($_relobject) && $db->table_exists($_relobject)) {
                $_relobject_structure = AutoObjectMapper::getAutoObject( $_relobject, $this->getDatabaseName() );
                if (!empty($_relobject_structure))
                    $_related = $_relobject_structure->getModel();
                $this->object_relations->setEntry( $_field, array(
                    'related_table'=>$_relobject,
                    'related_field'=>$_relfield,
                    'internal_field'=>$_intfield,
                    'model'=>$_related,
                    'slug_field'=>$_related->getSlugField(),
                ));
            }

        }

        $_relobject_f=null;
        if (!empty($this->getModel()->_has_many))
        foreach ($this->getModel()->_has_many as $_relname=>$_relobject_f) {
            $_relobject=$_relfield=null;

            if (!substr_count($_relobject_f, ':')) $_relobject_f .= ':id';
            $matches = explode(':', $_relobject_f);
            $_relobject = $matches[0];
            $_relfield = $matches[1];

            if (isset($_relobject) && $_relobject==$this->getTableName()) {
                $this->object_relations->setEntry( $_relname, array(
                    'related_table'=>$_relobject,
                    'related_field'=>$_relfield,
                    'internal_field'=>'id',
                    'model'=>$this->getModel()->getModelClone(),
                    'slug_field'=>$this->getModel()->getSlugField(),
                    'is_many'=>true,
                    'self'=>true
                ));
            } elseif (isset($_relobject) && $db->table_exists($_relobject)) {
                $_relobject_structure = AutoObjectMapper::getAutoObject( $_relobject, $this->getDatabaseName() );
                if (!empty($_relobject_structure)) {
                    $_related = $_relobject_structure->getModel();
                    $this->object_relations->setEntry( $_relname, array(
                        'related_table'=>$_relobject,
                        'related_field'=>$_relfield,
                        'internal_field'=>'id',
                        'model'=>$_related,
                        'slug_field'=>$_related->getSlugField(),
                        'is_many'=>true
                    ));
                } else {
                    throw new \RuntimeException( "Table structure for related field '$_relname' not found!");
                }
            }
        }
    }

}

// Endfile


<?php
namespace Mamoto\Api;

use Exception;

class ClientsReports extends AbstractApi
{

    private $_url = "/clients/reports";

    public function __construct($config = null)
    {
        $this->url = $this->_url;

        parent::__construct($config);
    }

    /**
     * get data from defined Id
     *
     * @throws NullPointerException
     * @return array|mixed array data
     */
    public function get()
    {
        // build url
        $url = $this->base_url . $this->url;
        // get data from server
        $data = $this->getDataFromUrl($url);
        // parse result set and get data as array
        $result = $this->parseJsonData($data);

        return $result;
    }
}


<?php

namespace Oxygen\Data\Validation\Laravel;

use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Contracts\Validation\ValidatesWhenResolved;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Factory;

class ValidationServiceProvider extends ServiceProvider {

    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot() {
        $app = $this->app;

        $this->app['validator']->resolver(function($translator, $data, $rules, $messages) use($app) {
            $validator = new Validator(
                $translator,
                $app->make(Hasher::class),
                $app['view'],
                $app['router'],
                $data,
                $rules,
                $messages
            );
            return $validator;
        });
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register() {
        $this->registerValidationResolverHook();

        $this->registerPresenceVerifier();

        $this->registerValidationFactory();
    }

    /**
     * Register the "ValidatesWhenResolved" container hook.
     *
     * @return void
     */
    protected function registerValidationResolverHook() {
        $this->app->afterResolving(function (ValidatesWhenResolved $resolved) {
            $resolved->validate();
        });
    }

    /**
     * Register the validation factory.
     *
     * @return void
     */
    protected function registerValidationFactory() {
        $this->app->singleton('validator', function ($app) {
            $validator = new Factory($app['translator'], $app);

            // The validation presence verifier is responsible for determining the existence
            // of values in a given data collection, typically a relational database or
            // other persistent data stores. And it is used to check for uniqueness.
            if(isset($app['validation.presence'])) {
                $validator->setPresenceVerifier($app['validation.presence']);
            }

            return $validator;
        });
    }

    /**
     * Register the database presence verifier.
     *
     * @return void
     */
    protected function registerPresenceVerifier() {
        $this->app->singleton('validation.presence', function ($app) {
            // The use of a closure ensures that the EntityManager is only instanstiated when the validator is actually used.
            return new DoctrinePresenceVerifier(function() use($app) {
                return $app['Doctrine\ORM\EntityManagerInterface'];
            });
        });
    }

}


<?php
/**
 * Windwork
 *
 * 一个用于快速开发高并发Web应用的轻量级PHP框架
 *
 * @copyright Copyright (c) 2008-2017 Windwork Team. (http://www.windwork.org)
 * @license   http://opensource.org/licenses/MIT
 */
namespace wf\template\adapter;

/**
 * 模板视图引擎
 * 模板引擎将模板“编译”成php脚本，每次调用视图的时候将包含 “编译”后的php脚本
 *
 * @package     wf.template.adapter
 * @author      cm <cmpan@qq.com>
 * @link        http://docs.windwork.org/manual/wf.template.html
 * @since       1.0.0
 */
class Wind implements \wf\template\EngineInterface
{
    /**
     * 存贮模板中设置及调用的变量
     *
     * @var array
     */
    protected $vars = [];

    /**
     * 模板配置
     * @var array
     */
    protected $cfg = [
        // 模板目录，位于站点根目录
        'tplDir'         => 'template',

        // 渲染模板时，是否检查模板文件是否需要编译
        'compileCheck'   => true,

        // 设置模板识别id,用于区分不同国家的语言
        'compileId'      => '',

        // 编译后的模板文件保存的文件夹
        'compileDir'    => 'data/template',

        // 设置模板是否强制每次都编译
        'compileForce'   => false,

        // 编译后的模板文件是否合并成一个文件
        'compileMerge'   => true,

        // 默认模板文件，建议是"{$mod}/{$ctl}/{$act}.html"
        'defaultTpl' => '',

        // 默认备用模板文件，为空或跟默认模板文件一样，则不使用备用模板文件，建议是"{$mod}/{$ctl}/{$act}.html"
        'defaultSpareTpl' => '',
    ];

    /**
     *
     * @param array $cfg = [<pre>
     *     // 模板目录，相对于入口文件所在目录
     *     'tplDir'         => 'template',
     *
     *     // 渲染模板时，是否检查模板文件是否需要编译
     *     'compileCheck'   => true,
     *
     *     // 设置模板识别id,用于区分不同国家的语言
     *     'compileId'      => '',
     *
     *     // 编译后的模板文件保存的文件夹
     *     'compileDir'    => 'data/template',
     *
     *     // 设置模板是否强制每次都编译
     *     'compileForce'   => false,
     *
     *     // 编译后的模板文件是否合并成一个文件
     *     // 如果启用，能稍微提高性能，但页面子模板修改时程序将不能检测到，修改子模板后需要通过后台清楚模板缓存
     *     // 建议工业环境启用、开发环境停用
     *     'compileMerge'   => true,
     *
     *     // 默认模板文件，建议是"{$mod}/{$ctl}/{$act}.html"
     *     'defaultTpl' => '',
     *
     *     // 默认备用模板文件，为空或跟默认模板文件一样，则不使用备用模板文件，建议是"{$mod}/{$ctl}/{$act}.html"
     *     'defaultSpareTpl' => '',
     * <pre>];
     */
    public function __construct(array $cfg = [])
    {
        // 模板参数设置
        $this->cfg = array_replace_recursive($this->cfg, $cfg);

        $this->cfg['tplDir']     = rtrim($this->cfg['tplDir'], '/');
        $this->cfg['compileId']  = rtrim($this->cfg['compileId'], '/');
        $this->cfg['compileDir'] = rtrim($this->cfg['compileDir'], '/');
    }

    /**
     * 未定义的属性赋值给$this->vars
     *
     * @param string $var
     * @param mixed $val
     */
    public function __set($var, $val)
    {
        $this->vars[$var] = $val;
    }

    /**
     * 访问未定义的属性返回$this->vars[属性名]
     *
     * @param string $var 属性名
     * @return mixed
     */
    public function __get($var)
    {
        return isset($this->vars[$var])? $this->vars[$var] : null;
    }

    /**
     * 模板变量赋值
     *
     * @param string $k 模板变量下标
     * @param mixed $v 模板变量值
     * @return \wf\template\Engine
     */
    public function assign($k, $v)
    {
        $this->vars[$k] = $v;
        return $this;
    }

    /**
     * 获取模板变量的值
     *
     * @param string $index
     * @return mixed
     */
    public function getVar($index)
    {
        return isset($this->vars[$index]) ? $this->vars[$index] : null;
    }

    /**
     * 获取模板所有变量
     *
     * @return array
     */
    public function getVars()
    {
        return $this->vars;
    }

    /**
     *
     * @param string $file
     * @param string $spareFile
     */
    public function render($file = '', $spareFile = '')
    {
        extract($this->vars,  EXTR_SKIP);

        if (empty($file)) {
            $file = $this->cfg['defaultTpl'];
        }

        // 包含文件
        require $this->getTpl($file);
    }

    /**
     * 获取编译后的模板文件路径
     *
     * @param string $file 模板文件名
     * @return string 编译后的模板文件路径
     */
    protected function getTpl($file)
    {
        $file = $this->fixFileName($file);
        $tplFile = $this->getRealTplPath($file);

        if (!$tplFile) {
            throw new \wf\template\Exception("template file '{$file}' not exists!");
        }

        $compiledName = str_replace("/", '^', $file);
        $compiledFile = "{$this->cfg['compileDir']}/{$this->cfg['tplSubDir']}^{$this->cfg['compileId']}^{$compiledName}.php";

        // 判断是否强制编译或是否过期($compiledFile不存在时 < 成立)
        if($this->cfg['compileForce'] || !is_file($compiledFile) || ($this->cfg['compileCheck'] && @filemtime($compiledFile) < @filemtime($tplFile))) {
            $this->compile($tplFile, $compiledFile);
        }

        return $compiledFile;
    }

    /**
     * 获取模板页中的区块
     * @param $text
     * @return array
     */
    protected function getBlock($text)
    {
        $matches = [];
        $rtv = [];
        // {block ... } ... {/block}
        if (preg_match_all("/\\{block\\s+(.*?)\\}(.*?)\\{\\/block\\}/is", $text, $matches)) {
            $len = count($matches[0]);
            for ($i = 0; $i < $len; $i++) {
                $rtv[$matches[1][$i]] = $matches[2][$i];
            }
        }
        return $rtv;
    }

    /**
     * 编译模板
     *
     * @param string $tplFile
     * @param string $compiledFile
     * @return bool
     * @throws \wf\template\Exception 如果模板文件不存在则抛出异常
     */
    protected function compile($tplFile, $compiledFile)
    {
        if(false === ($template = @file_get_contents($tplFile))) {
            throw new \wf\template\Exception("'{$tplFile}' does not exists!");
        }

        $template = $this->clearCommentTag($template);
        $template = $this->parseExtend($template);

        // {tpl xx} 包含另一个模板
        $template = preg_replace_callback("/\\{tpl\\s+['\\\"]?(.*?)['\\\"]?\\}/is", [$this, 'subTpl'], $template);

        // START 处理不解析部分  ======================

        // 允许模板中使用 <?xml ... ？>标签，解决xml声明标签被当做php标识符
        $template = preg_replace_callback("/(<\\?xml(.*?)\\?>)/is", function($m) {
            $str = "<?php echo '" . str_replace("'", "\'", $m[1]) . "'?>";
            return "<![wf-hold-code[" . base64_encode($str) . "]wf-hold-code]>";
        }, $template);

            /* {# php-code #} => <?php ... ?> */
            $template = preg_replace("/\\{#(.+?)#\\}/s", "<?php \\1?>", $template);

            // 将php代码进行编码防止被当做模板标签解析
            $template = preg_replace_callback("/(<\\?.+?\\?>)/is", function($m){
                return "<![wf-hold-code[" . base64_encode($m[1]) . ']wf-hold-code]>';
            }, $template);

                // {static}不进行解析的内容{/static} => <![wf-hold-code[编码内容]wf-hold-code]>
                $template = preg_replace_callback("/\\{static\\}(.+?)\\{\\/static\\}/s", function($m){
                    return "<![wf-hold-code[" . base64_encode($m[1]). "]wf-hold-code]>";
                }, $template);

                    // END 处理不解析部分  ======================

                    // url
                    $template = preg_replace("/\\{\\{\\s*url\\s+['\"]?(.*?)['\"]?\\s*\\}\\}/is", "<?php echo url(\"\\1\");?>", $template);

                    // lang 语言标签 {{lang key}}
                    $template = preg_replace_callback("/\\{\\{\\s*lang\\s+['\\\"]?(.+?)['\\\"]?\\s*\\}\\}/is", function($m) {
                        return lang(trim($m[1]));
                    }, $template);

                        // {if 表达式}
                        $template = preg_replace_callback("/\\{if\\s+(.+?)\\}/is", function($m){
                            return $this->quote("<?php if({$m[1]}) : ?>");
                        }, $template);

                            // {elseif 表达式} {else if 表达式}
                            $template = preg_replace_callback("/\\{else\\s*if\\s+(.+?)\\}/is", function($m){
                                return $this->quote("<?php elseif({$m[1]}) : ?>");
                            }, $template);

                                // {else}
                                $template = preg_replace("/\\{else\\}/is", "<?php else : ?>", $template);

                                // {/if} => endif
                                $template = preg_replace("/\\{\\/if\\}/is", "<?php endif; ?>", $template);

                                // {for 表达式1; 表达式2; 表达式3}
                                $template = preg_replace_callback("/\\{for\\s+(.*?)\\}/is", function($m){
                                    return $this->quote("<?php for({$m[1]}) :?>");
                                }, $template);

                                    // {/for} => endfor
                                    $template = preg_replace("/\\{\\/for\\}/is", "<?php endfor; ?>", $template);

                                    // {loop $arr $v} => foreach($arr as $v)
                                    if(preg_match_all("/\\{loop\\s+?(\\S+?)\\s+?(\\S+?)\\}/s", $template, $matches)) {
                                        $search = [];
                                        $replace = [];
                                        foreach ($matches[0] as $k => $mat) {
                                            $search[$k] = $mat;
                                            $replaceStr = "<?php
                                            \$__loop__tmp__{$k} = @{$matches[1][$k]};
                                            if(!empty(\$__loop__tmp__{$k}) && !is_scalar(\$__loop__tmp__{$k})):
                                            foreach(\$__loop__tmp__{$k} as {$matches[2][$k]}) :
                                            ?>";
                                            $replace[$k] = $this->quote($replaceStr);
                                        }
                                        $template = str_replace($search, $replace, $template);
                                    }

                                    // {loop $arr $k $v} => foreach($arr as $k => $v)
                                    if(preg_match_all("/\\{loop\\s+?(\\S+?)\\s+?(\\S+)?\\s+?(\\S+?)\\}/s", $template, $matches)) {
                                        $search = [];
                                        $replace = [];
                                        foreach ($matches[0] as $k => $mat) {
                                            $search[$k] = $mat;
                                            $replaceStr = "<?php
                                            \$__loop__tmp__x_{$k} = @{$matches[1][$k]};
                                            if(!empty(\$__loop__tmp__x_{$k}) && !is_scalar(\$__loop__tmp__x_{$k})):
                                            foreach(\$__loop__tmp__x_{$k} as {$matches[2][$k]} => {$matches[3][$k]}) :
                                            ?>";
                                            $replace[$k] = $this->quote($replaceStr);
                                        }
                                        $template = str_replace($search, $replace, $template);
                                    }

                                    // {/loop} => endforeach
                                    $template = preg_replace("/\\{\\/loop\\}/", "<?php endforeach; endif; ?>", $template );

                                    // 外部变量先进行xss过滤
                                    $template = preg_replace("/\\{\\{\\s*,}(\\$\\_(GET|POST|REQUEST|COOKIE)\\[.*?\\])\\s*\\}\\}/", "{{htmlspecialchars(@$1)}}", $template);

                                    // echo 常量 {{CONST}}
                                    $template = preg_replace("/\\{\\{\\s*([A-Z_][A-Z0-9_]+)\\s*\\}\\}/", "<?php defined('\\1') && print \\1;?>", $template );

                                    // echo 变量/数组/函数/对象属性、方法/类静态属性、方法/类常量
                                    /* {{xxxx}} => <?php echo xxxx ?> */
                                    $template = preg_replace_callback("/\\{\\{\\s*([@a-z_\\$\\\\].+?)\\s*\\}\\}/i", function($m){
                                        return $this->quote("<?php echo @{$m[1]};?>");
                                    }, $template);

                                        // 还原不解析的内容<![wf-hold-code[编码内容]wf-hold-code]>
                                        $template = preg_replace_callback("/<\\!\\[wf-hold-code\\[(.+?)\\]wf-hold-code\\]>/s", function($m){
                                            return str_replace("\\\"", "\"", base64_decode($m[1]));
                                        }, $template);

                                            // 添加在模板顶部的文件说明信息
                                            $thisTplMsg = "<?php\n/**\n"
                                                . " * (Don't edit this file)\n"
                                                    . " * Windwork Template View\n"
                                                        . " * Make by Windwork template engine\n"
                                                            . " * \n"
                                                                . " * File: {$compiledFile}\n"
                                                                . " * From: {$tplFile}\n"
                                                                . " * Time: ". date('Y-m-d H:i:s') . "\n"
                                                                    . " */\n"
                                                                        . "defined('WF_IN') || die('Access Denied');\n"
                                                                            . "?>";

                                                                            // 保存“编译”后模板文件
                                                                            @file_put_contents($compiledFile, $thisTplMsg . $template);

                                                                            return true;
    }

    /**
     * 解析模板继承
     * @param string $template
     * @return string
     */
    private function parseExtend($template)
    {
        // {ext ...}
        if (!preg_match("/\\{ext\\s+(.*?)\\}/is", $template, $match)) {
            return $template;
        }

        $masterPath  = $this->getTplPath($match[1] . '.html');
        $masterTpl   = @file_get_contents($masterPath);
        $masterTpl   = $this->clearCommentTag($masterTpl);

        $masterBlock = $this->getBlock($masterTpl);
        $extBlock    = $this->getBlock($template);

        // 母版内容替换
        $template = $masterTpl;
        foreach ($masterBlock as $blockName => $masterContent) {
            if (empty($extBlock[$blockName])) {
                continue;
            }
            $extContent = $extBlock[$blockName];

            // 重写的区块可以用 {parent} 调出父模板的内容
            if (strpos($extContent, '{parent}') !== false) { // 有 {parent}
                $extBlock[$blockName] = str_replace('{parent}', $masterBlock[$blockName], $extContent);
            }

            // 提取 {block ... } ... {/block}
            $template = preg_replace("/\\{block\\s+{$blockName}\\}(.*?)\\{\\/block\}/is", $extBlock[$blockName], $template);
        }

        // 清除未重写的区块标签名
        $template = preg_replace("/\\{block.*?\\}(.*?)\\{\\/block\\}/is", "\\1", $template);

        // 不支持多重继承，清除继承标签 {ext ...}
        $template = preg_replace("/\\{ext(.*?)\\}/is", '', $template);

        return $template;
    }

    /**
     * 清除标签外部的注释符号
     *
     * @param unknown $template
     * @return unknown
     */
    private function clearCommentTag($template)
    {
        // 去掉<!--{}-->的<!-- -->
        $template = preg_replace("/\\<\\!\\-\\-\\s*\\{(.+?)\\}\\s*\\-\\-\\>/s", "{\\1}", $template);

        // 服务器端注释 {* .... *}，清空
        $template = preg_replace("/\\{\\*(.*?)\\*\\}/is", '', $template);

        return $template;
    }

    /**
     * 转义双引号
     *
     * @param string $var
     * @return string
     */
    protected function quote($var)
    {
        return str_replace ("\\\"", "\"", preg_replace("/\\[([a-zA-Z0-9_\\-\\.\\x7f-\\xff]+)\\]/s", "['\\1']", $var));
    }

    /**
     * 解析子模板标签
     *
     * @param array $m
     * @return string
     */
    protected function subTpl($m)
    {
        $subTpl = $m[1]; // 匹配的子模板
        if ($this->cfg['compileMerge']) {
            // 合并子模板到主模板

            $tplPath = $this->getTplPath($subTpl . '.html');
            $content = file_get_contents($tplPath);
            $content = $this->clearCommentTag($content);

            $tplDir = $this->cfg['tplDir'];
            // 多级嵌套包含，尽管支持多级包含，但应该少用多级包含
            for ($i = 0; $i < 6; $i++) {
                // {tpl xx}
                $content = preg_replace_callback("/\\{tpl\\s+['\"]?(.*?)['\"]?\\}/", function($m) use ($tplDir){
                    $tplPath = $this->getTplPath($m[1] . '.html');
                    $content = file_get_contents($tplPath);
                    $content = $this->clearCommentTag($content);
                    return $content;
                }, $content);
            }

            return $content;
        } else {
            return "<?php require \$this->getTpl('{$subTpl}.html');?>";
        }
    }

    /**
     * 文件名整理
     * @param string $file
     * @return string
     */
    private function fixFileName($file)
    {
        return trim(strtolower($file), '/ ');
    }

    /**
     * 获取模板文件路径
     * @param string $file
     * @param string $subDir = null
     * @return string
     */
    private function getTplPath($file, $subDir = null)
    {
        if ($subDir === null) {
            $subDir = $this->cfg['tplSubDir'];
        }

        $tplDir = rtrim($this->cfg['tplDir'] . '/' . $subDir, '\\/');

        if (preg_match_all("/\\{(.*?)\\}/", $tplDir, $match)) {
            // 用$file参数中的字符串替换tplDir中的替换参数
            $fileArr = explode('/', $file);
            foreach ($match[1] as $matchItem) {
                $tplDir = str_replace("{{$matchItem}}", $fileArr[$matchItem], $tplDir);
                unset($fileArr[$matchItem]);
            }
            $file = implode('/', $fileArr);
        }

        $tplFile = "{$tplDir}/{$file}";

        return $tplFile;
    }

    /**
     * 获取模板文件真是路径
     *
     * @param string $file
     * @return string|boolean
     */
    protected function getRealTplPath($file) {
        if (isset($this->cfg['tplSubDir'])) {
            $tplFile = $this->getTplPath($file, $this->cfg['tplSubDir']);

            if (is_file($tplFile)) {
                return $tplFile;
            } else {
                return false;
            }
        }

        // 第一次执行getTpl，初始化subDir
        if (!empty($this->cfg['tplMajorId']) || !empty($this->cfg['tplMinorId'])) {
            // 主模板不存在则选择备用模板
            if (is_file($tplFile = $this->getTplPath($file, $this->cfg['tplMajorId']))) {
                $this->cfg['tplSubDir'] = $this->cfg['tplMajorId'];
                return $tplFile;
            } else if (is_file($tplFile = $this->getTplPath($file, $this->cfg['tplMinorId']))) {
                $this->cfg['tplSubDir'] = $this->cfg['tplMinorId'];
                return $tplFile;
            }
        } else {
            $this->cfg['tplSubDir'] = '';
            $tplFile = $this->getTplPath($file);

            if (is_file($tplFile)) {
                return $tplFile;
            }
        }

        return false;
    }
}


<?php
/**
 * Элемент списка. Обёртка к произвольному объекту
 *
 * @version ${product.version}
 *
 * @copyright 2013, Михаил Красильников <mihalych@vsepofigu.ru>
 * @license http://www.gnu.org/licenses/gpl.txt	GPL License 3
 * @author Михаил Красильников <mihalych@vsepofigu.ru>
 *
 * Данная программа является свободным программным обеспечением. Вы
 * вправе распространять ее и/или модифицировать в соответствии с
 * условиями версии 3 либо (по вашему выбору) с условиями более поздней
 * версии Стандартной Общественной Лицензии GNU, опубликованной Free
 * Software Foundation.
 *
 * Мы распространяем эту программу в надежде на то, что она будет вам
 * полезной, однако НЕ ПРЕДОСТАВЛЯЕМ НА НЕЕ НИКАКИХ ГАРАНТИЙ, в том
 * числе ГАРАНТИИ ТОВАРНОГО СОСТОЯНИЯ ПРИ ПРОДАЖЕ и ПРИГОДНОСТИ ДЛЯ
 * ИСПОЛЬЗОВАНИЯ В КОНКРЕТНЫХ ЦЕЛЯХ. Для получения более подробной
 * информации ознакомьтесь со Стандартной Общественной Лицензией GNU.
 *
 * Вы должны были получить копию Стандартной Общественной Лицензии
 * GNU с этой программой. Если Вы ее не получили, смотрите документ на
 * <http://www.gnu.org/licenses/>
 *
 * @package UI
 */

/**
 * Элемент списка. Обёртка к произвольному объекту
 *
 * @since 2.01
 * @package UI
 */
class UI_List_Item_Object implements UI_List_Item_Interface
{
    /**
     * Объект
     * @var object
     */
    protected $object;

    /**
     * Имена свойств, могущих выступать в роли идентификатора
     * @var string[]
     * @since 2.01
     */
    protected $idNames = array('id', 'key');

    /**
     * Имена свойств, могущих выступать в роли флага активности
     * @var string[]
     * @since 2.01
     */
    protected $enabledNames = array('enabled', 'active');

    /**
     * Конструктор
     *
     * @param $object  оборачиваемый объект
     *
     * @throws InvalidArgumentException
     *
     * @since 2.01
     */
    public function __construct($object)
    {
        if (!is_object($object))
        {
            throw new InvalidArgumentException(
                get_class($this) . '::__construct() expects argument 1 to be an object!');
        }
        $this->object = $object;
    }

    /**
     * Задаёт имена свойств, могущих выступать в роли идентификатора
     *
     * @param string[] $names
     *
     * @return void
     */
    public function setIdNames(array $names)
    {
        $this->idNames = $names;
    }

    /**
     * Задаёт имена свойств, могущих выступать в роли флага активности
     *
     * @param string[] $names
     *
     * @return void
     */
    public function setEnabledNames(array $names)
    {
        $this->enabledNames = $names;
    }

    /**
     * Возвращает идентификатор элемента
     *
     * @return string|null
     *
     * @since 2.01
     */
    public function getId()
    {
        foreach ($this->idNames as $name)
        {
            if (isset($this->object->{$name}))
            {
                return $this->object->{$name};
            }
        }
        return null;
    }

    /**
     * Возвращать true если элемент включён, false — если выключен и null — если состояние неизвестно
     *
     * Используется для определения состояния ЭУ «Активность»
     *
     * @return bool|null
     *
     * @since 2.01
     */
    public function isEnabled()
    {
        foreach ($this->enabledNames as $name)
        {
            if (isset($this->object->{$name}))
            {
                return $this->object->{$name};
            }
        }
        return null;
    }

    /**
     * Прокси к свойствам объекта
     *
     * @param string $property имя свойства
     *
     * @return mixed
     *
     * @since 2.01
     */
    public function __get($property)
    {
        return $this->object->{$property};
    }
}