true] */ protected $allowed = []; /** * @var string Name of attribute used for the replacement */ protected $attrName = 'with'; /** * @var NormalizedCollection List of [word => replacement] */ protected $collection; /** * @var string Default string used to replace censored words */ protected $defaultReplacement = '****'; /** * @var array Options passed to the RegexpBuilder */ protected $regexpOptions = [ 'caseInsensitive' => true, 'specialChars' => [ '*' => '[\\pL\\pN]*', '?' => '.', ' ' => '\\s*' ] ]; /** * @var string Name of the tag used to mark censored words */ protected $tagName = 'CENSOR'; /** * Plugin's setup * * Will initialize its collection and create the plugin's tag if it does not exist */ protected function setUp() { $this->collection = new NormalizedCollection; $this->collection->onDuplicate('replace'); if (isset($this->configurator->tags[$this->tagName])) { return; } // Create a tag $tag = $this->configurator->tags->add($this->tagName); // Create the attribute and make it optional $tag->attributes->add($this->attrName)->required = false; // Ensure that censored content can't ever be used by other tags $tag->rules->ignoreTags(); // Create a template that renders censored words either as their custom replacement or as // the default replacement $tag->template = ' ' . htmlspecialchars($this->defaultReplacement, ENT_COMPAT) . ' '; } /** * Add a word to the list of uncensored words * * @param string $word Word to exclude from the censored list * @return void */ public function allow($word) { $this->allowed[$word] = true; } /** * Return an instance of s9e\TextFormatter\Plugins\Censor\Helper * * @return Helper */ public function getHelper() { $config = $this->asConfig(); if (isset($config)) { $config = ConfigHelper::filterConfig($config, 'PHP'); } else { // Use a dummy config with a regexp that doesn't match anything $config = [ 'attrName' => $this->attrName, 'regexp' => '/(?!)/', 'tagName' => $this->tagName ]; } return new Helper($config); } /** * {@inheritdoc} */ public function asConfig() { $words = $this->getWords(); if (empty($words)) { return; } // Create the config $config = [ 'attrName' => $this->attrName, 'regexp' => $this->getWordsRegexp(array_keys($words)), 'regexpHtml' => $this->getWordsRegexp(array_map('htmlspecialchars', array_keys($words))), 'tagName' => $this->tagName ]; // Add custom replacements $replacementWords = []; foreach ($words as $word => $replacement) { if (isset($replacement) && $replacement !== $this->defaultReplacement) { $replacementWords[$replacement][] = $word; } } foreach ($replacementWords as $replacement => $words) { $wordsRegexp = '/^' . RegexpBuilder::fromList($words, $this->regexpOptions) . '$/Diu'; $regexp = new Regexp($wordsRegexp); $regexp->setJS(RegexpConvertor::toJS(str_replace('[\\pL\\pN]', '[^\\s!-\\/:-?]', $wordsRegexp))); $config['replacements'][] = [$regexp, $replacement]; } // Add the whitelist if (!empty($this->allowed)) { $config['allowed'] = $this->getWordsRegexp(array_keys($this->allowed)); } return $config; } /** * {@inheritdoc} */ public function getJSHints() { $hints = [ 'CENSOR_HAS_ALLOWED' => !empty($this->allowed), 'CENSOR_HAS_REPLACEMENTS' => false ]; foreach ($this->getWords() as $replacement) { if (isset($replacement) && $replacement !== $this->defaultReplacement) { $hints['CENSOR_HAS_REPLACEMENTS'] = true; break; } } return $hints; } /** * Return a list of censored words * * @return string[] */ protected function getWords() { return array_diff_key(iterator_to_array($this->collection), $this->allowed); } /** * Generate a regexp that matches the given list of words * * @param array $words List of words * @return Regexp Regexp instance with a Unicode-free JS variant */ protected function getWordsRegexp(array $words) { $expr = RegexpBuilder::fromList($words, $this->regexpOptions); $regexp = new Regexp('/(?', '(?:', $expr); $regexp->setJS('/(?:^|\\W)' . $expr . '(?!\\w)/gi'); return $regexp; } }