Seditio Source
Root |
./othercms/phpBB3/vendor/s9e/text-formatter/src/Configurator/Helpers/RulesHelper.php
<?php

/**
* @package   s9e\TextFormatter
* @copyright Copyright (c) 2010-2021 The s9e authors
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Helpers;

use
s9e\TextFormatter\Configurator\Collections\Ruleset;
use
s9e\TextFormatter\Configurator\Collections\TagCollection;

abstract class
RulesHelper
{
   
/**
    * Generate the allowedChildren and allowedDescendants bitfields for every tag and for the root context
    *
    * @param  TagCollection $tags
    * @param  Ruleset       $rootRules
    * @return array
    */
   
public static function getBitfields(TagCollection $tags, Ruleset $rootRules)
    {
       
$rules = ['*root*' => iterator_to_array($rootRules)];
        foreach (
$tags as $tagName => $tag)
        {
           
$rules[$tagName] = iterator_to_array($tag->rules);
        }

       
// Create a matrix that contains all of the tags and whether every other tag is allowed as
        // a child and as a descendant
       
$matrix = self::unrollRules($rules);

       
// Remove unusable tags from the matrix
       
self::pruneMatrix($matrix);

       
// Group together tags are allowed in the exact same contexts
       
$groupedTags = [];
        foreach (
array_keys($matrix) as $tagName)
        {
            if (
$tagName === '*root*')
            {
                continue;
            }

           
$k = '';
            foreach (
$matrix as $tagMatrix)
            {
               
$k .= $tagMatrix['allowedChildren'][$tagName];
               
$k .= $tagMatrix['allowedDescendants'][$tagName];
            }

           
$groupedTags[$k][] = $tagName;
        }

       
// Record the bit number of each tag, and the name of a tag for each bit
       
$bitTag     = [];
       
$bitNumber  = 0;
       
$tagsConfig = [];
        foreach (
$groupedTags as $tagNames)
        {
            foreach (
$tagNames as $tagName)
            {
               
$tagsConfig[$tagName]['bitNumber'] = $bitNumber;
               
$bitTag[$bitNumber] = $tagName;
            }

            ++
$bitNumber;
        }

       
// Build the bitfields of each tag, including the *root* pseudo-tag
       
foreach ($matrix as $tagName => $tagMatrix)
        {
           
$allowedChildren    = '';
           
$allowedDescendants = '';
            foreach (
$bitTag as $targetName)
            {
               
$allowedChildren    .= $tagMatrix['allowedChildren'][$targetName];
               
$allowedDescendants .= $tagMatrix['allowedDescendants'][$targetName];
            }

           
$tagsConfig[$tagName]['allowed'] = self::pack($allowedChildren, $allowedDescendants);
        }

       
// Prepare the return value
       
$return = [
           
'root' => $tagsConfig['*root*'],
           
'tags' => $tagsConfig
       
];
        unset(
$return['tags']['*root*']);

        return
$return;
    }

   
/**
    * Initialize a matrix of settings
    *
    * @param  array $rules Rules for each tag
    * @return array        Multidimensional array of [tagName => [scope => [targetName => setting]]]
    */
   
protected static function initMatrix(array $rules)
    {
       
$matrix   = [];
       
$tagNames = array_keys($rules);

        foreach (
$rules as $tagName => $tagRules)
        {
           
$matrix[$tagName]['allowedChildren']    = array_fill_keys($tagNames, 0);
           
$matrix[$tagName]['allowedDescendants'] = array_fill_keys($tagNames, 0);
        }

        return
$matrix;
    }

   
/**
    * Apply given rule from each applicable tag
    *
    * For each tag, if the rule has any target we set the corresponding value for each target in the
    * matrix
    *
    * @param  array  &$matrix   Settings matrix
    * @param  array   $rules    Rules for each tag
    * @param  string  $ruleName Rule name
    * @param  string  $key      Key in the matrix
    * @param  integer $value    Value to be set
    * @return void
    */
   
protected static function applyTargetedRule(array &$matrix, $rules, $ruleName, $key, $value)
    {
        foreach (
$rules as $tagName => $tagRules)
        {
            if (!isset(
$tagRules[$ruleName]))
            {
                continue;
            }

            foreach (
$tagRules[$ruleName] as $targetName)
            {
               
$matrix[$tagName][$key][$targetName] = $value;
            }
        }
    }

   
/**
    * @param  array $rules
    * @return array
    */
   
protected static function unrollRules(array $rules)
    {
       
// Initialize the matrix with default values
       
$matrix = self::initMatrix($rules);

       
// Convert ignoreTags and requireParent to denyDescendant and denyChild rules
       
$tagNames = array_keys($rules);
        foreach (
$rules as $tagName => $tagRules)
        {
            if (!empty(
$tagRules['ignoreTags']))
            {
               
$rules[$tagName]['denyChild']      = $tagNames;
               
$rules[$tagName]['denyDescendant'] = $tagNames;
            }

            if (!empty(
$tagRules['requireParent']))
            {
               
$denyParents = array_diff($tagNames, $tagRules['requireParent']);
                foreach (
$denyParents as $parentName)
                {
                   
$rules[$parentName]['denyChild'][] = $tagName;
                }
            }
        }

       
// Apply "allow" rules to grant usage, overwriting the default settings
       
self::applyTargetedRule($matrix, $rules, 'allowChild',      'allowedChildren',    1);
       
self::applyTargetedRule($matrix, $rules, 'allowDescendant', 'allowedDescendants', 1);

       
// Apply "deny" rules to remove usage
       
self::applyTargetedRule($matrix, $rules, 'denyChild',      'allowedChildren',    0);
       
self::applyTargetedRule($matrix, $rules, 'denyDescendant', 'allowedDescendants', 0);

        return
$matrix;
    }

   
/**
    * Remove unusable tags from the matrix
    *
    * @param  array &$matrix
    * @return void
    */
   
protected static function pruneMatrix(array &$matrix)
    {
       
$usableTags = ['*root*' => 1];

       
// Start from the root and keep digging
       
$parentTags = $usableTags;
        do
        {
           
$nextTags = [];
            foreach (
array_keys($parentTags) as $tagName)
            {
               
// Accumulate the names of tags that are allowed as children of our parent tags
               
$nextTags += array_filter($matrix[$tagName]['allowedChildren']);
            }

           
// Keep only the tags that are in the matrix but aren't in the usable array yet, then
            // add them to the array
           
$parentTags  = array_diff_key($nextTags, $usableTags);
           
$parentTags  = array_intersect_key($parentTags, $matrix);
           
$usableTags += $parentTags;
        }
        while (!empty(
$parentTags));

       
// Remove unusable tags from the matrix
       
$matrix = array_intersect_key($matrix, $usableTags);
        unset(
$usableTags['*root*']);

       
// Remove unusable tags from the targets
       
foreach ($matrix as $tagName => &$tagMatrix)
        {
           
$tagMatrix['allowedChildren']
                =
array_intersect_key($tagMatrix['allowedChildren'], $usableTags);

           
$tagMatrix['allowedDescendants']
                =
array_intersect_key($tagMatrix['allowedDescendants'], $usableTags);
        }
        unset(
$tagMatrix);
    }

   
/**
    * Convert a binary representation such as "101011" to an array of integer
    *
    * Each bitfield is split in groups of 8 bits, then converted to a 16-bit integer where the
    * allowedChildren bitfield occupies the least significant bits and the allowedDescendants
    * bitfield occupies the most significant bits
    *
    * @param  string    $allowedChildren
    * @param  string    $allowedDescendants
    * @return integer[]
    */
   
protected static function pack($allowedChildren, $allowedDescendants)
    {
       
$allowedChildren    = str_split($allowedChildren,    8);
       
$allowedDescendants = str_split($allowedDescendants, 8);

       
$allowed = [];
        foreach (
array_keys($allowedChildren) as $k)
        {
           
$allowed[] = bindec(sprintf(
               
'%1$08s%2$08s',
               
strrev($allowedDescendants[$k]),
               
strrev($allowedChildren[$k])
            ));
        }

        return
$allowed;
    }
}