Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 19 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
| ContentModel | |
0.00% |
0 / 19 |
|
0.00% |
0 / 10 |
210 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getDomElement | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getDomNode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| initializeCategories | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
| getCategories | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| coalesceContentCategories | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| canAcceptContent | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 | |||
| hasCategory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| hasAttribute | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| hasName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace pvc\html\content_model; |
| 4 | |
| 5 | use pvc\interfaces\html\content_model\ContentCategory; |
| 6 | use pvc\interfaces\html\content_model\ContentModelInterface; |
| 7 | use pvc\interfaces\html\content_model\ContentPermission; |
| 8 | use pvc\interfaces\html\dom\DomElementInterface; |
| 9 | use pvc\interfaces\html\dom\DomNodeInterface; |
| 10 | use pvc\interfaces\html\rules\CategoryRuleInterface; |
| 11 | use pvc\interfaces\html\rules\ContentRuleInterface; |
| 12 | |
| 13 | class ContentModel implements ContentModelInterface |
| 14 | { |
| 15 | /** |
| 16 | * @var DomElementInterface |
| 17 | */ |
| 18 | protected DomElementInterface $domElement; |
| 19 | |
| 20 | /** |
| 21 | * @var DomNodeInterface |
| 22 | */ |
| 23 | protected DomNodeInterface $domNode; |
| 24 | |
| 25 | /** |
| 26 | * @var int |
| 27 | * these are the categories to which this element belongs represented as a |
| 28 | * bitmask. In other |
| 29 | * words, if a parent node can only accept Transparent among its |
| 30 | * children, then this int will need the Transparent bit flipped on |
| 31 | * if it is to be permitted as a child. |
| 32 | */ |
| 33 | protected(set) int $categories = 0; |
| 34 | |
| 35 | /** |
| 36 | * @var int |
| 37 | * these are the categories dnoting acceptable content for this node. In |
| 38 | * other words, if only Transparent is in this list, then the child must |
| 39 | * have the Transparent bit flipped on in order to be permitted |
| 40 | */ |
| 41 | protected(set) int $childCategories = 0; |
| 42 | |
| 43 | public function __construct( |
| 44 | /** |
| 45 | * @var array<ContentCategory> $initialCategories |
| 46 | * categories to which this model always belongs, e.g. before applying |
| 47 | * context-sensitive context rules which can add categories |
| 48 | */ |
| 49 | private array $initialCategories, |
| 50 | |
| 51 | /** |
| 52 | * @var array<CategoryRuleInterface> $additionalCategoryRules |
| 53 | * rules that can add additional categories to the $contentCategories |
| 54 | * array based on contextual conditions. The test method of each rule is |
| 55 | * invoked and produces an array which is merged with the initialCategories |
| 56 | * array |
| 57 | */ |
| 58 | private array $additionalCategoryRules, |
| 59 | |
| 60 | /** |
| 61 | * @var array<ContentCategory> $content |
| 62 | * this array indicates what sorts of content categories are permitted |
| 63 | * as children of this node. |
| 64 | */ |
| 65 | protected(set) array $content, |
| 66 | |
| 67 | /** |
| 68 | * @var array<ContentRuleInterface> $contentRules |
| 69 | * additional rules that might permit or deny a content model to be a child |
| 70 | * of this one |
| 71 | */ |
| 72 | private(set) array $contentRules, |
| 73 | ) { |
| 74 | } |
| 75 | |
| 76 | public function getDomElement(): DomElementInterface |
| 77 | { |
| 78 | return $this->domElement; |
| 79 | } |
| 80 | |
| 81 | public function getDomNode(): DomNodeInterface |
| 82 | { |
| 83 | return $this->domNode; |
| 84 | } |
| 85 | |
| 86 | public function initializeCategories(): void |
| 87 | { |
| 88 | $derivedCategories = []; |
| 89 | foreach ($this->additionalCategoryRules as $rule) { |
| 90 | $derivedCategories += $rule->test($this->getDomNode()); |
| 91 | } |
| 92 | /** |
| 93 | * merge initial and derived, and coalesce to an int |
| 94 | */ |
| 95 | $allCategories = self::coalesceContentCategories(array_merge($this->initialCategories, $derivedCategories)); |
| 96 | |
| 97 | /** |
| 98 | * convert permitted content categories to bitmask |
| 99 | */ |
| 100 | $this->childCategories = self::coalesceContentCategories($this->content); |
| 101 | } |
| 102 | |
| 103 | public function getCategories(): int |
| 104 | { |
| 105 | return $this->categories; |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * @param array<ContentCategory> $categories |
| 110 | * |
| 111 | * @return int |
| 112 | * callback to create a bitmask of the content categories to which this model belongs |
| 113 | */ |
| 114 | public static function coalesceContentCategories(array $categories): int |
| 115 | { |
| 116 | /** |
| 117 | * convert categories to integers |
| 118 | */ |
| 119 | $categories = array_map(fn(ContentCategory $item) => $item->value, $categories); |
| 120 | |
| 121 | /** |
| 122 | * @param int $carry |
| 123 | * @param int $category |
| 124 | * |
| 125 | * @return int |
| 126 | */ |
| 127 | |
| 128 | $reducer = function(int $carry, int $category) { |
| 129 | return $carry + $category; |
| 130 | }; |
| 131 | |
| 132 | /** |
| 133 | * make sure the categories are unique |
| 134 | */ |
| 135 | return array_reduce(array_unique($categories), $reducer, 0); |
| 136 | } |
| 137 | |
| 138 | public function canAcceptContent(DomNodeInterface $content): bool |
| 139 | { |
| 140 | /** |
| 141 | * first iterate through the content rules. |
| 142 | */ |
| 143 | foreach ($this->contentRules as $rule) { |
| 144 | if ($rule->test($content) === ContentPermission::GRANTED) return true; |
| 145 | if ($rule->test($content) === ContentPermission::DENIED) return false; |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * if the intersection of this model's content categories and the child's |
| 150 | * categories is > 0, return true else return false. This test could |
| 151 | * be a rule itself, but then you would have to insert this rule into |
| 152 | * the list of content rules for every element, which seems quite redundant. |
| 153 | * So the logic is kept in this method instead. |
| 154 | */ |
| 155 | return (($this->childCategories & $this->getCategories()) > 0); |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * the following support methods are used to define the characteristics |
| 160 | * or context of the containing element |
| 161 | */ |
| 162 | |
| 163 | /** |
| 164 | * @param ContentCategory $category |
| 165 | * |
| 166 | * @return bool |
| 167 | */ |
| 168 | public function hasCategory(ContentCategory $category): bool |
| 169 | { |
| 170 | return (0 < ($this->categories & $category->value)); |
| 171 | } |
| 172 | |
| 173 | public function hasAttribute(string $attributeName): bool |
| 174 | { |
| 175 | return $this->domElement->getAttribute($attributeName) !== null; |
| 176 | } |
| 177 | |
| 178 | public function hasName(string $name): bool |
| 179 | { |
| 180 | return $this->domElement->getName() === $name; |
| 181 | } |
| 182 | } |