vijos/openvj

View on GitHub
src/Security/KeywordFilter.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php
/**
 * This file is part of openvj project.
 *
 * Copyright 2013-2015 openvj dev team.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace VJ\Security;

use VJ\Core\Application;

class KeywordFilter
{
    private $redis;
    private $prefix;

    /**
     * @param \Redis $redis
     * @param string $prefix
     */
    public function __construct(\Redis $redis, $prefix = 'CACHE:FILTER')
    {
        $this->redis = $redis;
        $this->prefix = $prefix;
    }

    /**
     * Naive keyword filter
     *
     * @param string $text
     * @param string $cacheKey
     * @param callable $miss
     * @return bool|string
     */
    public function contains($text, $cacheKey, callable $miss)
    {
        $value = $this->redis->hget($this->prefix, $cacheKey);
        if ($value === false) {
            // Haven't found data in the cache, we need to build the tree
            $tree = array();
            $keywords = $miss();
            foreach ($keywords as $keyword) {
                $ptr = 0;
                $len = strlen($keyword);
                assert("$len != 0");
                for ($idx = 0; $idx < $len; ++$idx) {
                    $ptr += ord($keyword[$idx]);
                    if (!isset($tree[$ptr])) {
                        if (!isset($tree[-1])) {
                            $tree[-1] = 1;
                        }
                        $tree[$ptr] = $tree[-1]++;
                    }
                    $ptr = $tree[$ptr] * 256;
                }
                $tree[$ptr] = -1;
            }
            $this->redis->hset($this->prefix, $cacheKey, serialize($tree));
        } else {
            $tree = unserialize($value);
        }

        $text = mb_strtolower($text, 'UTF-8');
        $active = array();
        $len = strlen($text);
        for ($cur = 0; $cur < $len; ++$cur) {
            $ord = ord($text[$cur]);
            $active[$cur] = 0;
            foreach ($active as $start => $ptr) {
                if (isset($tree[$ptr + $ord])) {
                    $ptr = $tree[$ptr + $ord] * 256;
                    if (isset($tree[$ptr])) {
                        return substr($text, $start, $cur - $start + 1);
                    }
                    $active[$start] = $ptr;
                } else {
                    unset($active[$start]);
                }
            }
        }

        return false;
    }

    /**
     * 测试是否包含通用关键字
     *
     * @param $text
     * @return bool|string
     */
    public static function isContainGeneric($text)
    {
        return Application::get('keyword_filter')->contains($text, 'general', function () {
            $rec = Application::coll('System')->findOne([
                '_id' => 'FilterKeyword'
            ]);
            if (!$rec) {
                return [];
            } else {
                return $rec['general'];
            }
        });
    }

    /**
     * 测试是否包含用于用户名的额外关键字
     *
     * @param $text
     * @return bool|string
     */
    public static function isContainName($text)
    {
        return Application::get('keyword_filter')->contains($text, 'name', function () {
            $rec = Application::coll('System')->findOne([
                '_id' => 'FilterKeyword'
            ]);
            if (!$rec) {
                return [];
            } else {
                return $rec['name'];
            }
        });
    }
}