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 | } |