class FaqController in Frequently Asked Questions 8
Controller routines for FAQ routes.
Hierarchy
- class \Drupal\Core\Controller\ControllerBase implements ContainerInjectionInterface uses LoggerChannelTrait, MessengerTrait, LinkGeneratorTrait, RedirectDestinationTrait, UrlGeneratorTrait, StringTranslationTrait
- class \Drupal\faq\Controller\FaqController
Expanded class hierarchy of FaqController
File
- src/
Controller/ FaqController.php, line 25
Namespace
Drupal\faq\ControllerView source
class FaqController extends ControllerBase {
protected $database;
protected $config;
protected $renderer;
protected $entityTypeManager;
protected $languageManager;
protected $linkGenerator;
/**
* @param \Drupal\Core\Database\Connection $database
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
* @param \Drupal\Core\Render\RendererInterface $renderer
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
* @param \Drupal\Core\Utility\LinkGeneratorInterface $linkGenerator
*/
public function __construct(Connection $database, ConfigFactoryInterface $config, RendererInterface $renderer, EntityTypeManagerInterface $entityTypeManager, LanguageManagerInterface $languageManager, LinkGeneratorInterface $linkGenerator) {
$this->database = $database;
$this->config = $config;
$this->renderer = $renderer;
$this->entityTypeManager = $entityTypeManager;
$this->languageManager = $languageManager;
$this->linkGenerator = $linkGenerator;
}
/**
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* @return static
*/
public static function create(ContainerInterface $container) {
return new static($container
->get('database'), $container
->get('config.factory'), $container
->get('renderer'), $container
->get('entity_type.manager'), $container
->get('language_manager'), $container
->get('link_generator'));
}
/**
* Function to display the faq page.
*
* @param int $tid
* Default is 0, determines if the questions and answers on the page
* will be shown according to a category or non-categorized.
* @param string $faq_display
* Optional parameter to override default question layout setting.
* @param string $category_display
* Optional parameter to override default category layout setting.
*
* @return
* The page with FAQ questions and answers.
*
* @throws NotFoundHttpException
*/
public function faqPage($tid = 0, $faq_display = '', $category_display = '') {
$faq_settings = $this->config
->get('faq.settings');
$output = $output_answers = '';
$build = array();
$build['#type'] = 'markup';
$build['#attached']['library'][] = 'faq/faq-css';
$build['#title'] = $faq_settings
->get('title');
if (!$this
->moduleHandler()
->moduleExists('taxonomy')) {
$tid = 0;
}
$faq_display = $faq_settings
->get('display');
$use_categories = $faq_settings
->get('use_categories');
$category_display = $faq_settings
->get('category_display');
// If taxonomy doesn't installed, do not use categories.
if (!$this
->moduleHandler()
->moduleExists('taxonomy')) {
$use_categories = FALSE;
}
if ($use_categories && $category_display == 'hide_qa' || $faq_display == 'hide_answer') {
$build['#attached']['library'][] = 'faq/faq-scripts';
$build['#attached']['drupalSettings']['faqSettings']['hide_qa_accordion'] = $faq_settings
->get('hide_qa_accordion');
$build['#attached']['drupalSettings']['faqSettings']['category_hide_qa_accordion'] = $faq_settings
->get('category_hide_qa_accordion');
}
// Non-categorized questions and answers.
if (!$use_categories || $category_display == 'none' && empty($tid)) {
if (!empty($tid)) {
throw new NotFoundHttpException();
}
$langcode = $this->languageManager
->getCurrentLanguage()
->getId();
$default_sorting = $faq_settings
->get('default_sorting');
$query = $this->database
->select('node', 'n');
$weight_alias = $query
->leftJoin('faq_weights', 'w', '%alias.nid=n.nid');
$query
->leftJoin('node_field_data', 'd', 'd.nid=n.nid');
$db_or = new Condition('OR');
$db_or
->condition("{$weight_alias}.tid", 0)
->isNull("{$weight_alias}.tid");
$query
->fields('n', [
'nid',
])
->condition('n.type', 'faq')
->condition('d.langcode', $langcode)
->condition('d.status', 1)
->condition($db_or)
->addTag('node_access');
$default_weight = 0;
if ($default_sorting == 'ASC') {
$default_weight = 1000000;
}
// Works, but involves variable concatenation - safe though, since
// $default_weight is an integer.
$query
->addExpression("COALESCE(w.weight, {$default_weight})", 'effective_weight');
// Doesn't work in Postgres.
// $query->addExpression('COALESCE(w.weight, CAST(:default_weight as SIGNED))', 'effective_weight', array(':default_weight' => $default_weight));.
$query
->orderBy('effective_weight', 'ASC')
->orderBy('d.sticky', 'DESC');
if ($default_sorting == 'ASC') {
$query
->orderBy('d.created', 'ASC');
}
else {
$query
->orderBy('d.created', 'DESC');
}
// Only need the nid column.
$nids = $query
->execute()
->fetchCol();
$data = Node::loadMultiple($nids);
foreach ($data as $key => &$node) {
$node = $node
->hasTranslation($langcode) ? $node
->getTranslation($langcode) : $node;
}
$questions_to_render = array();
$questions_to_render['#data'] = $data;
switch ($faq_display) {
case 'questions_top':
$questions_to_render['#theme'] = 'faq_questions_top';
break;
case 'hide_answer':
$questions_to_render['#theme'] = 'faq_hide_answer';
break;
case 'questions_inline':
$questions_to_render['#theme'] = 'faq_questions_inline';
break;
case 'new_page':
$questions_to_render['#theme'] = 'faq_new_page';
break;
}
// End of switch.
$output = \Drupal::service('renderer')
->render($questions_to_render);
}
else {
$hide_child_terms = $faq_settings
->get('hide_child_terms');
// If we're viewing a specific category/term.
if (!empty($tid)) {
if ($term = Term::load($tid)) {
$title = $faq_settings
->get('title');
$build['#title'] = $title . ($title ? ' - ' : '') . $this
->t($term
->getName());
$this
->_displayFaqByCategory($faq_display, $category_display, $term, 0, $output, $output_answers);
$to_render = array(
'#theme' => 'faq_page',
'#content' => new FormattableMarkup($output, []),
'#answers' => new FormattableMarkup($output_answers, []),
);
$build['#markup'] = $this->renderer
->render($to_render);
return $build;
}
else {
throw new NotFoundHttpException();
}
}
$list_style = $faq_settings
->get('category_listing');
$vocabularies = Vocabulary::loadMultiple();
$vocab_omit = $faq_settings
->get('omit_vocabulary');
$items = array();
$vocab_items = array();
foreach ($vocabularies as $vid => $vobj) {
if (isset($vocab_omit[$vid]) && $vocab_omit[$vid] !== 0) {
continue;
}
if ($category_display == "new_page") {
$vocab_items = $this
->_getIndentedFaqTerms($vid, 0);
$items = array_merge($items, $vocab_items);
}
else {
if ($hide_child_terms && $category_display == 'hide_qa') {
$tree = $this->entityTypeManager
->getStorage('taxonomy_term')
->loadTree($vid, 0, 1, TRUE);
}
else {
$tree = $this->entityTypeManager
->getStorage('taxonomy_term')
->loadTree($vid, 0, NULL, TRUE);
}
foreach ($tree as $term) {
switch ($category_display) {
case 'hide_qa':
case 'categories_inline':
if (FaqHelper::taxonomyTermCountNodes($term
->id())) {
$this
->_displayFaqByCategory($faq_display, $category_display, $term, 1, $output, $output_answers);
}
break;
}
}
}
}
if ($category_display == "new_page") {
$output = $this
->_renderCategoriesToList($items, $list_style);
}
}
$faq_description = $faq_settings
->get('description');
$markup = array(
'#theme' => 'faq_page',
'#content' => new FormattableMarkup($output, []),
'#answers' => new FormattableMarkup($output_answers, []),
'#description' => new FormattableMarkup($faq_description, []),
);
$build['#markup'] = $this->renderer
->render($markup);
return $build;
}
/**
* Define the elements for the FAQ Settings page - order tab.
*
* @param $category
* The category id of the FAQ page to reorder.
*
* @return
* The form code, before being converted to HTML format.
*/
public function orderPage($tid = NULL) {
$faq_settings = $this->config
->get('faq.settings');
$build = array();
$build['#attached']['library'][] = 'faq/faq-scripts';
$build['#attached']['drupalSettings']['faqSettings']['hide_qa_accordion'] = $faq_settings
->get('hide_qa_accordion');
$build['#attached']['drupalSettings']['faqSettings']['category_hide_qa_accordion'] = $faq_settings
->get('category_hide_qa_accordion');
$build['#attached']['library'][] = 'faq/faq-css';
$build['faq_order'] = $this
->formBuilder()
->getForm('Drupal\\faq\\Form\\OrderForm');
return $build;
}
/**
* Renders the form for the FAQ Settings page - General tab.
*
* @return
* The form code inside the $build array.
*/
public function generalSettings() {
$build = array();
$build['faq_general_settings_form'] = $this
->formBuilder()
->getForm('Drupal\\faq\\Form\\GeneralForm');
return $build;
}
/**
* Renders the form for the FAQ Settings page - Questions tab.
*
* @return
* The form code inside the $build array.
*/
public function questionsSettings() {
$faq_settings = $this->config
->get('faq.settings');
$build = array();
$build['#attached']['library'][] = 'faq/faq-scripts';
$build['#attached']['drupalSettings']['faqSettings']['hide_qa_accordion'] = $faq_settings
->get('hide_qa_accordion');
$build['#attached']['drupalSettings']['faqSettings']['category_hide_qa_accordion'] = $faq_settings
->get('category_hide_qa_accordion');
$build['faq_questions_settings_form'] = $this
->formBuilder()
->getForm('Drupal\\faq\\Form\\QuestionsForm');
return $build;
}
/**
* Renders the form for the FAQ Settings page - Categories tab.
*
* @return
* The form code inside the $build array.
*/
public function categoriesSettings() {
$faq_settings = $this->config
->get('faq.settings');
$build = array();
$build['#attached']['library'][] = 'faq/faq-scripts';
$build['#attached']['drupalSettings']['faqSettings']['hide_qa_accordion'] = $faq_settings
->get('hide_qa_accordion');
$build['#attached']['drupalSettings']['faqSettings']['category_hide_qa_accordion'] = $faq_settings
->get('category_hide_qa_accordion');
if (!$this
->moduleHandler()
->moduleExists('taxonomy')) {
$this
->messenger()
->addError(t('Categorization of questions will not work without the "taxonomy" module being enabled.'));
}
$build['faq_categories_settings_form'] = $this
->formBuilder()
->getForm('Drupal\\faq\\Form\\CategoriesForm');
return $build;
}
/* ****************************************************************
* PRIVATE HELPER FUCTIONS
* *************************************************************** */
/**
* Display FAQ questions and answers filtered by category.
*
* @param $faq_display Define the way the FAQ is being shown; can have the values:
* 'questions top',hide answers','questions inline','new page'.
* @param $category_display The layout of categories which should be used.
* @param $term The category / term to display FAQs for.
* @param $display_header Set if the header will be shown or not.
* @param &$output Reference which holds the content of the page, HTML formatted.
* @param &$output_answer Reference which holds the answers from the FAQ, when showing questions on top.
*/
private function _displayFaqByCategory($faq_display, $category_display, $term, $display_header, &$output, &$output_answers) {
$langcode = $this->languageManager
->getCurrentLanguage()
->getId();
$default_sorting = $this->config
->get('faq.settings')
->get('default_sorting');
$term_id = $term
->id();
$query = $this->database
->select('node', 'n');
$query
->join('node_field_data', 'd', 'd.nid = n.nid');
$query
->innerJoin('taxonomy_index', 'ti', 'n.nid = ti.nid');
$query
->leftJoin('faq_weights', 'w', 'w.tid = ti.tid AND n.nid = w.nid');
$query
->fields('n', [
'nid',
])
->condition('n.type', 'faq')
->condition('d.langcode', $langcode)
->condition('d.status', 1)
->condition("ti.tid", $term_id)
->addTag('node_access');
$default_weight = 0;
if ($default_sorting == 'ASC') {
$default_weight = 1000000;
}
// Works, but involves variable concatenation - safe though, since
// $default_weight is an integer.
$query
->addExpression("COALESCE(w.weight, {$default_weight})", 'effective_weight');
// Doesn't work in Postgres.
// $query->addExpression('COALESCE(w.weight, CAST(:default_weight as SIGNED))', 'effective_weight', array(':default_weight' => $default_weight));.
$query
->orderBy('effective_weight', 'ASC')
->orderBy('d.sticky', 'DESC');
if ($default_sorting == 'ASC') {
$query
->orderBy('d.created', 'ASC');
}
else {
$query
->orderBy('d.created', 'DESC');
}
// We only want the first column, which is nid, so that we can load all
// related nodes.
$nids = $query
->execute()
->fetchCol();
$data = Node::loadMultiple($nids);
foreach ($data as $key => &$node) {
$node = $node
->hasTranslation($langcode) ? $node
->getTranslation($langcode) : $node;
}
// Handle indenting of categories.
$depth = 0;
if (!isset($term->depth)) {
$children = $this->entityTypeManager
->getStorage('taxonomy_term')
->loadChildren($term
->id());
$term->depth = count($children);
}
while ($depth < $term->depth) {
$display_header = 1;
$indent = '<div class="faq-category-indent">';
$output .= $indent;
$depth++;
}
// Set up the class name for hiding the q/a for a category if required.
$faq_class = "faq-qa";
if ($category_display == "hide_qa") {
$faq_class = "faq-qa-hide";
}
$output_render = $output_answers_render = array(
'#data' => $data,
'#display_header' => $display_header,
'#category_display' => $category_display,
'#term' => $term,
'#class' => $faq_class,
'#parent_term' => $term,
);
switch ($faq_display) {
case 'questions_top':
$output_render['#theme'] = 'faq_category_questions_top';
$output .= $this->renderer
->render($output_render);
$output_answers_render['#theme'] = 'faq_category_questions_top_answers';
$output_answers .= $this->renderer
->render($output_answers_render);
break;
case 'hide_answer':
$output_render['#theme'] = 'faq_category_hide_answer';
$output .= $this->renderer
->render($output_render);
break;
case 'questions_inline':
$output_render['#theme'] = 'faq_category_questions_inline';
$output .= $this->renderer
->render($output_render);
break;
case 'new_page':
$output_render['#theme'] = 'faq_category_new_page';
$output .= $this->renderer
->render($output_render);
break;
}
// Handle indenting of categories.
while ($depth > 0) {
$output .= '</div>';
$depth--;
}
}
/**
* Return a structured array that consists a list of terms indented according to the term depth.
*
* @param $vid
* Vocabulary id.
* @param $tid
* Term id.
*
* @return
* Return an array of a list of terms indented according to the term depth.
*/
private function _getIndentedFaqTerms($vid, $tid) {
// If ($this->moduleHandler()->moduleExists('pathauto')) {
// pathauto does't exists in D8 yet
// }.
$faq_settings = $this->config
->get('faq.settings');
$display_faq_count = $faq_settings
->get('count');
$hide_child_terms = $faq_settings
->get('hide_child_terms');
$items = array();
$tree = $this->entityTypeManager
->getStorage('taxonomy_term')
->loadTree($vid, $tid, 1, TRUE);
foreach ($tree as $term) {
$term_id = $term
->id();
$tree_count = FaqHelper::taxonomyTermCountNodes($term_id);
if ($tree_count) {
// Get term description.
$desc = '';
$term_description = $term
->getDescription();
if (!empty($term_description)) {
$desc = '<div class="faq-qa-description">';
$desc .= $term_description . "</div>";
}
$query = $this->database
->select('node', 'n');
$query
->join('node_field_data', 'd', 'n.nid = d.nid');
$query
->innerJoin('taxonomy_index', 'ti', 'n.nid = ti.nid');
$term_node_count = $query
->condition('d.status', 1)
->condition('n.type', 'faq')
->condition("ti.tid", $term_id)
->addTag('node_access')
->countQuery()
->execute()
->fetchField();
if ($term_node_count > 0) {
$path = Url::fromUserInput('/faq-page/' . $term_id);
// Pathauto is not exists in D8 yet
// if (!\Drupal::service('path.alias_manager.cached')->getPathAlias(arg(0) . '/' . $tid) && $this->moduleHandler()->moduleExists('pathauto')) {
// }.
if ($display_faq_count) {
$count = $term_node_count;
if ($hide_child_terms) {
$count = $tree_count;
}
$cur_item = $this->linkGenerator
->generate($this
->t($term
->getName()), $path) . " ({$count}) " . $desc;
}
else {
$cur_item = $this->linkGenerator
->generate($this
->t($term
->getName()), $path) . $desc;
}
}
else {
$cur_item = $this
->t($term
->getName()) . $desc;
}
if (!empty($term_image)) {
$cur_item .= '<div class="clear-block"></div>';
}
$term_items = array();
if (!$hide_child_terms) {
$term_items = $this
->_getIndentedFaqTerms($vid, $term_id);
}
$items[] = array(
"item" => $cur_item,
"children" => $term_items,
);
}
}
return $items;
}
/**
* Renders the output of getIntendedFaqTerms to HTML list.
*
* @param array $items
* The structured array made by getIntendedTerms function
* @param string $list_style
* List style type: ul or ol.
*
* @return string
* HTML formatted output.
*/
private function _renderCategoriesToList($items, $list_style) {
$list = array();
foreach ($items as $item) {
$pre = '';
if (!empty($item['children'])) {
$pre = $this
->_renderCategoriesToList($item['children'], $list_style);
}
$list[] = new FormattableMarkup($item['item'] . $pre, []);
}
$render = array(
'#theme' => 'item_list',
'#items' => $list,
'#list_style' => $list_style,
);
return $this->renderer
->render($render);
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ControllerBase:: |
protected | property | The configuration factory. | |
ControllerBase:: |
protected | property | The current user service. | 1 |
ControllerBase:: |
protected | property | The entity form builder. | |
ControllerBase:: |
protected | property | The entity manager. | |
ControllerBase:: |
protected | property | The form builder. | 2 |
ControllerBase:: |
protected | property | The key-value storage. | 1 |
ControllerBase:: |
protected | property | The module handler. | 2 |
ControllerBase:: |
protected | property | The state service. | |
ControllerBase:: |
protected | function | Returns the requested cache bin. | |
ControllerBase:: |
protected | function | Retrieves a configuration object. | |
ControllerBase:: |
private | function | Returns the service container. | |
ControllerBase:: |
protected | function | Returns the current user. | 1 |
ControllerBase:: |
protected | function | Retrieves the entity form builder. | |
ControllerBase:: |
protected | function | Retrieves the entity manager service. | |
ControllerBase:: |
protected | function | Retrieves the entity type manager. | |
ControllerBase:: |
protected | function | Returns the form builder service. | 2 |
ControllerBase:: |
protected | function | Returns a key/value storage collection. | 1 |
ControllerBase:: |
protected | function | Returns the language manager service. | 1 |
ControllerBase:: |
protected | function | Returns the module handler. | 2 |
ControllerBase:: |
protected | function |
Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait:: |
|
ControllerBase:: |
protected | function | Returns the state storage service. | |
FaqController:: |
protected | property | ||
FaqController:: |
protected | property | ||
FaqController:: |
protected | property |
The entity type manager. Overrides ControllerBase:: |
|
FaqController:: |
protected | property |
The language manager. Overrides ControllerBase:: |
|
FaqController:: |
protected | property |
The link generator. Overrides LinkGeneratorTrait:: |
|
FaqController:: |
protected | property | ||
FaqController:: |
public | function | Renders the form for the FAQ Settings page - Categories tab. | |
FaqController:: |
public static | function |
Overrides ControllerBase:: |
|
FaqController:: |
public | function | Function to display the faq page. | |
FaqController:: |
public | function | Renders the form for the FAQ Settings page - General tab. | |
FaqController:: |
public | function | Define the elements for the FAQ Settings page - order tab. | |
FaqController:: |
public | function | Renders the form for the FAQ Settings page - Questions tab. | |
FaqController:: |
private | function | Display FAQ questions and answers filtered by category. | |
FaqController:: |
private | function | Return a structured array that consists a list of terms indented according to the term depth. | |
FaqController:: |
private | function | Renders the output of getIntendedFaqTerms to HTML list. | |
FaqController:: |
public | function | ||
LinkGeneratorTrait:: |
protected | function | Returns the link generator. | |
LinkGeneratorTrait:: |
protected | function | Renders a link to a route given a route name and its parameters. | |
LinkGeneratorTrait:: |
public | function | Sets the link generator service. | |
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
RedirectDestinationTrait:: |
protected | property | The redirect destination service. | 1 |
RedirectDestinationTrait:: |
protected | function | Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url. | |
RedirectDestinationTrait:: |
protected | function | Returns the redirect destination service. | |
RedirectDestinationTrait:: |
public | function | Sets the redirect destination service. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
UrlGeneratorTrait:: |
protected | property | The url generator. | |
UrlGeneratorTrait:: |
protected | function | Returns the URL generator service. | |
UrlGeneratorTrait:: |
public | function | Sets the URL generator service. | |
UrlGeneratorTrait:: |
protected | function | Generates a URL or path for a specific route based on the given parameters. |