3 namespace Drupal\advanced_help\Controller;
5 use Drupal\Component\Plugin\PluginManagerInterface;
6 use Drupal\Component\Utility\SafeMarkup;
7 use Drupal\Component\Utility\Xss;
8 use Drupal\Core\Controller\ControllerBase;
9 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
11 use Michelf\MarkdownExtra;
12 use Symfony\Component\DependencyInjection\ContainerInterface;
13 use Symfony\Component\HttpFoundation\Request;
14 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
17 * Class AdvancedHelpController.
19 * @package Drupal\advanced_help\Controller\AdvancedHelpController.
21 class AdvancedHelpController extends ControllerBase {
24 * The advanced help plugin manager.
26 * @var \Drupal\Component\Plugin\PluginManagerInterface
28 private $advanced_help;
31 public function __construct(PluginManagerInterface $advanced_help) {
32 $this->advanced_help = $advanced_help;
38 public static function create(ContainerInterface $container) {
40 $container->get('plugin.manager.advanced_help')
47 * @todo Implement search integration.
49 * Returns module index.
51 public function main() {
52 $topics = $this->advanced_help->getTopics();
53 $settings = $this->advanced_help->getSettings();
55 // Print a module index.
56 $modules = $this->advanced_help->getModuleList();
60 foreach ($modules as $module => $module_name) {
61 if (!empty($topics[$module]) && empty($settings[$module]['hide'])) {
62 if (isset($settings[$module]['index name'])) {
63 $name = $settings[$module]['index name'];
65 elseif (isset($settings[$module]['name'])) {
66 $name = $settings[$module]['name'];
69 $name = $this->t($module_name);
71 $items[] = $this->l($name, new Url('advanced_help.module_index', ['module' => $module]));
77 '#theme' => 'item_list',
79 '#title' => $this->t('Module help index'),
85 * Build a hierarchy for a single module's topics.
87 * @param $topics array.
90 private function getTopicHierarchy($topics) {
91 foreach ($topics as $module => $module_topics) {
92 foreach ($module_topics as $topic => $info) {
93 $parent_module = $module;
94 // We have a blank topic that we don't want parented to itself.
99 if (empty($info['parent'])) {
102 elseif (strpos($info['parent'], '%')) {
103 list($parent_module, $parent) = explode('%', $info['parent']);
104 if (empty($topics[$parent_module][$parent])) {
105 // If it doesn't exist, top level.
110 $parent = $info['parent'];
111 if (empty($module_topics[$parent])) {
112 // If it doesn't exist, top level.
117 if (!isset($topics[$parent_module][$parent]['children'])) {
118 $topics[$parent_module][$parent]['children'] = [];
120 $topics[$parent_module][$parent]['children'][] = [$module, $topic];
121 $topics[$module][$topic]['_parent'] = [$parent_module, $parent];
128 * Helper function to sort topics.
129 * @param string $id_a
130 * @param string $id_b
133 private function helpUasort($id_a, $id_b) {
134 $topics = $this->advanced_help->getTopics();
135 list($module_a, $topic_a) = $id_a;
136 $a = $topics[$module_a][$topic_a];
137 list($module_b, $topic_b) = $id_b;
138 $b = $topics[$module_b][$topic_b];
140 $a_weight = isset($a['weight']) ? $a['weight'] : 0;
141 $b_weight = isset($b['weight']) ? $b['weight'] : 0;
142 if ($a_weight != $b_weight) {
143 return ($a_weight < $b_weight) ? -1 : 1;
146 if ($a['title'] != $b['title']) {
147 return ($a['title'] < $b['title']) ? -1 : 1;
153 * Build a tree of advanced help topics.
155 * @param array $topics
157 * @param array $topic_ids
159 * @param int $max_depth
160 * Maximum depth for subtopics.
162 * Default depth for subtopics.
165 * Returns list of topics/subtopics.
167 private function getTree($topics, $topic_ids, $max_depth = -1, $depth = 0) {
168 uasort($topic_ids, [$this, 'helpUasort']);
170 foreach ($topic_ids as $info) {
171 list($module, $topic) = $info;
172 $item = $this->l($topics[$module][$topic]['title'], new Url('advanced_help.help', ['module' => $module, 'topic' => $topic]));
173 if (!empty($topics[$module][$topic]['children']) && ($max_depth == -1 || $depth < $max_depth)) {
175 '#theme' => 'item_list',
176 '#items' => advanced_help_get_tree($topics, $topics[$module][$topic]['children'], $max_depth, $depth + 1),
178 $item .= \Drupal::service('renderer')->render($link, FALSE);
187 public function moduleIndex($module) {
188 $topics = $this->advanced_help->getTopics();
190 if (empty($topics[$module])) {
191 throw new NotFoundHttpException();
194 $topics = $this->getTopicHierarchy($topics);
195 $items = $this->getTree($topics, $topics[$module]['']['children']);
199 '#theme' => 'item_list',
206 * Set the name of the module in the index page.
208 * @param string $module Module name
211 public function moduleIndexTitle($module) {
212 return $this->advanced_help->getModuleName($module) . ' help index';
215 public function topicPage(Request $request, $module, $topic) {
216 $is_modal = ($request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT) === 'drupal_modal');
217 $info = $this->advanced_help->getTopic($module, $topic);
219 throw new NotFoundHttpException();
227 while (!empty($parent['parent'])) {
228 if (strpos($parent['parent'], '%')) {
229 list($pmodule, $ptopic) = explode('%', $parent['parent']);
232 $ptopic = $parent['parent'];
235 if (!empty($checked[$pmodule][$ptopic])) {
239 $checked[$pmodule][$ptopic] = TRUE;
241 $parent = $this->advanced_help->getTopic($pmodule, $ptopic);
248 $build = $this->viewTopic($module, $topic, $is_modal);
249 if (empty($build['#markup'])) {
250 $build['#markup'] = $this->t('Missing help topic.');
252 $build['#attached']['library'][] = 'advanced_help/help';
257 * Load and render a help topic.
259 * @param string $module
260 * Name of the module.
261 * @param string $topic
263 * @todo port the drupal_alter functionality.
266 * Returns formatted topic.
268 public function viewTopic($module, $topic, $is_modal = false) {
269 $file_info = $this->advanced_help->getTopicFileInfo($module, $topic);
271 $info = $this->advanced_help->getTopic($module, $topic);
272 $file = "{$file_info['path']}/{$file_info['file']}";
277 if (!empty($info['css'])) {
278 $build['#attached']['library'][] = $info['module'] . '/' . $info['css'];
281 $build['#markup'] = file_get_contents($file);
282 if (isset($info['readme file']) && $info['readme file']) {
283 $ext = pathinfo($file, PATHINFO_EXTENSION);
285 $build['#markup'] = '<div class="advanced-help-topic">' . Xss::filterAdmin(MarkdownExtra::defaultTransform($build['#markup'])) . '</div>';
290 // Change 'topic:' to the URL for another help topic.
291 preg_match('/&topic:([^"]+)&/', $build['#markup'], $matches);
292 if (isset($matches[1]) && preg_match('/[\w\-]\/[\w\-]+/', $matches[1])) {
293 list($umodule, $utopic) = explode('/', $matches[1]);
294 $path = new Url('advanced_help.help', ['module' => $umodule, 'topic' => $utopic]);
295 $build['#markup'] = preg_replace('/&topic:([^"]+)&/', $path->toString(), $build['#markup']);
300 // Change 'path:' to the URL to the base help directory.
301 $build['#markup'] = str_replace('&path&', $base_path . $info['path'] . '/', $build['#markup']);
303 // Change 'trans_path:' to the URL to the actual help directory.
304 $build['#markup'] = str_replace('&trans_path&', $base_path . $file_info['path'] . '/', $build['#markup']);
306 // Change 'base_url:' to the URL to the site.
307 $build['#markup'] = preg_replace('/&base_url&([^"]+)"/', $base_path . '$1' . '"', $build['#markup']);
309 // Run the line break filter if requested.
310 if (!empty($info['line break'])) {
311 // Remove the header since it adds an extra <br /> to the filter.
312 $build['#markup'] = preg_replace('/^<!--[^\n]*-->\n/', '', $build['#markup']);
314 $build['#markup'] = _filter_autop($build['#markup']);
317 if (!empty($info['navigation']) && !$is_modal) {
318 $topics = $this->advanced_help->getTopics();
319 $topics = $this->getTopicHierarchy($topics);
320 if (!empty($topics[$module][$topic]['children'])) {
321 $items = $this->getTree($topics, $topics[$module][$topic]['children']);
323 '#theme' => 'item_list',
326 $build['#markup'] .= \Drupal::service('renderer')->render($links, FALSE);
329 list($parent_module, $parent_topic) = $topics[$module][$topic]['_parent'];
331 $parent = $topics[$module][$topic]['_parent'];
332 $up = new Url('advanced_help.help', ['module' => $parent[0], 'topic' => $parent[1]]);
335 $up = new Url('advanced_help.module_index', ['module' => $module]);
338 $siblings = $topics[$parent_module][$parent_topic]['children'];
339 uasort($siblings, [$this, 'helpUasort']);
340 $prev = $next = NULL;
342 foreach ($siblings as $sibling) {
343 list($sibling_module, $sibling_topic) = $sibling;
348 if ($sibling_module == $module && $sibling_topic == $topic) {
355 if ($prev || $up || $next) {
356 $navigation = '<div class="help-navigation clear-block">';
359 $navigation .= $this->l('«« ' . $topics[$prev[0]][$prev[1]]['title'], new Url('advanced_help.help', ['module' => $prev[0], 'topic' => $prev[1]], ['attributes' => ['class' => 'help-left']]));
362 $navigation .= $this->l($this->t('Up'), $up->setOption('attributes', ['class' => ($prev) ? 'help-up' : 'help-up-noleft']));
365 $navigation .= $this->l($topics[$next[0]][$next[1]]['title'] . ' »»', new Url('advanced_help.help', ['module' => $next[0], 'topic' => $next[1]], ['attributes' => ['class' => 'help-right']]));
368 $navigation .= '</div>';
369 $build['#markup'] .= $navigation;
372 $build['#markup'] = '<div class="advanced-help-topic">' . $build['#markup'] . '</div>';
373 // drupal_alter('advanced_help_topic', $output, $popup);
379 * Set the title of the topic.
384 public function topicPageTitle($module, $topic) {
385 $info = $this->advanced_help->getTopic($module, $topic);
387 throw new NotFoundHttpException();
389 return $info['title'];