| 39 | | class CategoryHelper extends AbstractHelper |
| 40 | | { |
| 41 | | |
| 42 | | |
| 43 | | |
| 44 | | public function renderCategoryTree(int $parentId = 0): string |
| 45 | | { |
| 46 | | [$categoryTree, $normalizedCategoryNumbers, $aggregatedNumbers] = $this->gatherCategoryData(); |
| 47 | | |
| 48 | | if ([] !== $categoryTree) { |
| 49 | | return sprintf('<ul class="pmf-category-overview">%s</ul>', $this->buildCategoryList( |
| 50 | | $categoryTree, |
| 51 | | $parentId, |
| 52 | | $aggregatedNumbers, |
| 53 | | $normalizedCategoryNumbers, |
| 54 | | )); |
| 55 | | } |
| 56 | | |
| 57 | | $languagesAvailable = $this->getCategory()->getCategoryLanguagesTranslated($parentId); |
| 58 | | return sprintf( |
| 59 | | '<p>%s</p><ul class="pmf-category-overview">%s</ul>', |
| 60 | | Translation::getString('msgCategoryMissingButTranslationAvailable'), |
| 61 | | $this->buildAvailableCategoryTranslationsList($languagesAvailable), |
| 62 | | ); |
| 63 | | } |
| 64 | | |
| 65 | | |
| 66 | | |
| 67 | | |
| 68 | | |
| 69 | | |
| 70 | | |
| 71 | | private function gatherCategoryData(): array |
| 72 | | { |
| 73 | | $categoryRelation = new Relation($this->getConfiguration(), $this->getCategory()); |
| 74 | | $categoryRelation->setGroups($this->getCategory()->getGroups()); |
| 75 | | |
| 76 | | $categoryTree = $this->getCategory()->getOrderedCategories(); |
| 77 | | $categoryNumbers = $categoryRelation->getCategoryWithFaqs(); |
| 78 | | $normalizedCategoryNumbers = $this->normalizeCategoryTree($categoryTree, $categoryNumbers); |
| 79 | | $aggregatedNumbers = $categoryRelation->getAggregatedFaqNumbers($normalizedCategoryNumbers); |
| 80 | | |
| 81 | | return [$categoryTree, $normalizedCategoryNumbers, $aggregatedNumbers]; |
| 82 | | } |
| 83 | | |
| 84 | | |
| 85 | | |
| 86 | | |
| 87 | | |
| 88 | | |
| 89 | | public function getCategoryTreeData(int $parentId = 0): array |
| 90 | | { |
| 91 | | [$categoryTree, $normalizedCategoryNumbers, $aggregatedNumbers] = $this->gatherCategoryData(); |
| 92 | | |
| 93 | | if ([] === $categoryTree) { |
| 94 | | return []; |
| 95 | | } |
| 96 | | |
| 97 | | return $this->buildCategoryNodes($categoryTree, $parentId, $aggregatedNumbers, $normalizedCategoryNumbers); |
| 98 | | } |
| 99 | | |
| 100 | | |
| 101 | | |
| 102 | | |
| 103 | | |
| 104 | | |
| 105 | | |
| 106 | | |
| 107 | | |
| 108 | | public function buildCategoryNodes( |
| 109 | | array $categoryTree, |
| 110 | | int $parentId = 0, |
| 111 | | array $aggregatedNumbers = [], |
| 112 | | array $categoryNumbers = [], |
| 113 | | ): array { |
| 114 | | $nodes = []; |
| 115 | | |
| 116 | | foreach ($categoryTree as $categoryId => $node) { |
| 117 | | if ((int) $node['parent_id'] !== $parentId) { |
| 118 | | continue; |
| 119 | | } |
| 120 | | |
| 121 | | $faqCount = (int) ($aggregatedNumbers[$node['id']] ?? 0); |
| 122 | | $hasFaqs = (int) ($categoryNumbers[$categoryId]['faqs'] ?? 0) > 0; |
| 123 | | |
| 124 | | $description = trim((string) ($node['description'] ?? '')); |
| 125 | | $imageFile = trim((string) ($node['image'] ?? '')); |
| 126 | | $image = $imageFile !== '' |
| 127 | | ? sprintf('%scontent/user/images/%s', $this->configuration->getDefaultUrl(), $imageFile) |
| 128 | | : null; |
| 129 | | |
| 130 | | $nodes[] = [ |
| 131 | | 'id' => (int) $node['id'], |
| 132 | | 'name' => (string) ($node['name'] ?? ''), |
| 133 | | 'description' => $description === '' ? null : $description, |
| 134 | | 'url' => sprintf( |
| 135 | | '%scategory/%d/%s.html', |
| 136 | | $this->configuration->getDefaultUrl(), |
| 137 | | (int) $node['id'], |
| 138 | | TitleSlugifier::slug((string) ($node['name'] ?? '')), |
| 139 | | ), |
| 140 | | 'image' => $image, |
| 141 | | 'faqCount' => $faqCount, |
| 142 | | 'faqCountLabel' => $this->plurals()->get(key: 'plmsgEntries', number: $faqCount), |
| 143 | | 'hasFaqs' => $hasFaqs, |
| 144 | | 'avatarColor' => sprintf('hsl(%d, 55%%, 45%%)', abs(crc32((string) $node['name'])) % 360), |
| 145 | | 'children' => $this->buildCategoryNodes( |
| 146 | | $categoryTree, |
| 147 | | (int) $node['id'], |
| 148 | | $aggregatedNumbers, |
| 149 | | $categoryNumbers, |
| 150 | | ), |
| 151 | | ]; |
| 152 | | } |
| 153 | | |
| 154 | | return $nodes; |
| 155 | | } |
| 156 | | |
| 157 | | |
| 158 | | |
| 159 | | |
| 160 | | |
| 161 | | |
| 162 | | |
| 163 | | |
| 164 | | public function buildCategoryList( |
| 165 | | array $categoryTree, |
| 166 | | int $parentId = 0, |
| 167 | | array $aggregatedNumbers = [], |
| 168 | | array $categoryNumbers = [], |
| 169 | | ): string { |
| 170 | | $html = ''; |
| 171 | | foreach ($categoryTree as $categoryId => $node) { |
| 172 | | if ((int) ($node['parent_id'] ?? 0) !== $parentId) { |
| 173 | | continue; |
| 174 | | } |
| 175 | | |
| 176 | | $nodeId = (int) ($node['id'] ?? 0); |
| 177 | | $number = $aggregatedNumbers[$nodeId] ?? 0; |
| 178 | | |
| 179 | | $name = (string) ($node['name'] ?? ''); |
| 180 | | if ((int) ($categoryNumbers[$categoryId]['faqs'] ?? 0) > 0) { |
| 181 | | $url = sprintf( |
| 182 | | '%scategory/%d/%s.html', |
| 183 | | $this->configuration->getDefaultUrl(), |
| 184 | | $nodeId, |
| 185 | | TitleSlugifier::slug($name), |
| 186 | | ); |
| 187 | | |
| 188 | | $link = new Link($url, $this->configuration); |
| 189 | | $link->setTitle((string) ($node['name'] ?? '')); |
| 190 | | $link->text = (string) ($node['name'] ?? ''); |
| 191 | | $link->tooltip = is_null($node['description'] ?? null) ? '' : (string) $node['description']; |
| 192 | | $name = $link->toHtmlAnchor(); |
| 193 | | } |
| 194 | | |
| 195 | | $description = trim((string) ($node['description'] ?? '')); |
| 196 | | $descriptionHtml = '' === $description ? '' : sprintf('<br><small>%s</small>', $description); |
| 197 | | |
| 198 | | $html .= sprintf( |
| 199 | | '<li data-category-id="%d">%s <span class="badge text-bg-primary">%s</span>%s', |
| 200 | | $nodeId, |
| 201 | | $name, |
| 202 | | $this->plurals()->get(key: 'plmsgEntries', number: $number), |
| 203 | | $descriptionHtml, |
| 204 | | ); |
| 205 | | $html .= sprintf('<ul>%s</ul>', $this->buildCategoryList( |
| 206 | | $categoryTree, |
| 207 | | $nodeId, |
| 208 | | $aggregatedNumbers, |
| 209 | | $categoryNumbers, |
| 210 | | )); |
| 211 | | $html .= '</li>'; |
| 212 | | } |
| 213 | | |
| 214 | | return $html; |
| 215 | | } |
| 216 | | |
| 217 | | |
| 218 | | |
| 219 | | |
| 220 | | |
| 221 | | |
| 222 | | public function buildAvailableCategoryTranslationsList(array $availableCategoryTranslations): string |
| 223 | | { |
| 224 | | $html = ''; |
| 225 | | |
| 226 | | foreach ($availableCategoryTranslations as $language => $category) { |
| 227 | | $url = sprintf('%sshow-categories.html?lang=%s', $this->configuration->getDefaultUrl(), $language); |
| 228 | | $link = new Link($url, $this->configuration); |
| 229 | | $link->setTitle(Strings::htmlentities($category)); |
| 230 | | $link->text = Strings::htmlentities($category); |
| 231 | | $name = $link->toHtmlAnchor(); |
| 232 | | $html .= sprintf('<li><strong>%s</strong>: %s</li>', LanguageCodes::get($language), $name); |
| 233 | | } |
| 234 | | |
| 235 | | return $html; |
| 236 | | } |
| 237 | | |
| 238 | | |
| 239 | | |
| 240 | | |
| 241 | | |
| 242 | | |
| 243 | | |
| 244 | | |
| 245 | | public function normalizeCategoryTree(array $categoryTree, array $categoryNumbers): array |
| 246 | | { |
| 247 | | $normalizedCategoryTree = []; |
| 248 | | |
| 249 | | foreach ($categoryTree as $categoryId => $category) { |
| 250 | | $normalizedCategoryTree[(int) ($category['id'] ?? 0)] = [ |
| 251 | | 'category_id' => $categoryId, |
| 252 | | 'parent_id' => (int) ($category['parent_id'] ?? 0), |
| 253 | | 'name' => (string) ($category['name'] ?? ''), |
| 254 | | 'description' => $category['description'] ?? null, |
| 255 | | 'faqs' => (int) ($categoryNumbers[$categoryId]['faqs'] ?? 0), |
| 256 | | ]; |
| 257 | | } |
| 258 | | |
| 259 | | return $normalizedCategoryTree; |
| 260 | | } |
| 261 | | |
| 262 | | |
| 263 | | |
| 264 | | |
| 265 | | |
| 266 | | |
| 267 | | |
| 268 | | |
| 269 | | public function getModerators(array $categories): array |
| 270 | | { |
| 271 | | $recipients = []; |
| 272 | | |
| 273 | | |
| 274 | | $categoryInstance = $this->Category; |
| 275 | | if (!$categoryInstance instanceof Category) { |
| 276 | | return $recipients; |
| 277 | | } |
| 278 | | |
| 279 | | $user = new User($this->configuration); |
| 280 | | |
| 281 | | |
| 282 | | $seen = []; |
| 283 | | |
| 284 | | foreach ($categories as $category) { |
| 285 | | $userId = $categoryInstance->getOwner((int) $category); |
| 286 | | $groupId = $categoryInstance->getModeratorGroupId((int) $category); |
| 287 | | |
| 288 | | $user->getUserById($userId); |
| 289 | | $emailCategoryOwner = $user->getUserData('email'); |
| 290 | | |
| 291 | | if ( |
| 292 | | is_string($emailCategoryOwner) |
| 293 | | && $emailCategoryOwner !== '' |
| 294 | | && !array_key_exists($emailCategoryOwner, $seen) |
| 295 | | ) { |
| 296 | | $recipients[] = $emailCategoryOwner; |
| 297 | | $seen[$emailCategoryOwner] = true; |
| 298 | | } |
| 299 | | |
| 300 | | if ($groupId > 0 && $user->perm instanceof MediumPermission) { |
| 301 | | $moderators = $user->perm->getGroupMembers($groupId); |
| 302 | | foreach ($moderators as $moderator) { |
| 303 | | $user->getUserById($moderator); |
| 304 | | $moderatorEmail = $user->getUserData('email'); |
| 305 | | if (!is_string($moderatorEmail) || $moderatorEmail === '') { |
| 306 | | continue; |
| 307 | | } |
| 308 | | |
| 309 | | if (array_key_exists($moderatorEmail, $seen)) { |
| 310 | | continue; |
| 311 | | } |
| 312 | | |
| 313 | | $recipients[] = $moderatorEmail; |
| 314 | | $seen[$moderatorEmail] = true; |
| 315 | | } |
| 316 | | } |
| 317 | | } |
| 318 | | |
| 319 | | return array_unique($recipients); |
| 320 | | } |
| 321 | | |
| 322 | | |
| 323 | | |
| 324 | | |
| 325 | | public function renderAvailableTranslationsOptions(int $categoryId): string |
| 326 | | { |
| 327 | | $options = ''; |
| 328 | | |
| 329 | | $categoryLanguageService = new CategoryLanguageService(); |
| 330 | | $existingTranslations = $categoryLanguageService->getExistingTranslations($this->configuration, $categoryId); |
| 331 | | |
| 332 | | foreach ($existingTranslations as $code => $displayName) { |
| 333 | | $options .= sprintf('<option value="%s">%s</option>', $code, $displayName); |
| 334 | | } |
| 335 | | |
| 336 | | return $options; |
| 337 | | } |
| 338 | | } |