Skip to content
Snippets Groups Projects
Commit deb2d0e9 authored by Peter Rotich's avatar Peter Rotich
Browse files

Add ability to nest Knowledgebase Categories

Introduce ability to create sub-categories to knowledge base categories
parent f4fef2fc
No related branches found
No related tags found
No related merge requests found
...@@ -21,9 +21,16 @@ class Category extends VerySimpleModel { ...@@ -21,9 +21,16 @@ class Category extends VerySimpleModel {
'pk' => array('category_id'), 'pk' => array('category_id'),
'ordering' => array('name'), 'ordering' => array('name'),
'joins' => array( 'joins' => array(
'parent' => array(
'constraint' => array('category_pid' => 'Category.category_id'),
'null' => true,
),
'children' => array(
'reverse' => 'Category.parent',
),
'faqs' => array( 'faqs' => array(
'reverse' => 'FAQ.category' 'reverse' => 'FAQ.category'
), ),
), ),
); );
...@@ -36,6 +43,9 @@ class Category extends VerySimpleModel { ...@@ -36,6 +43,9 @@ class Category extends VerySimpleModel {
/* ------------------> Getter methods <--------------------- */ /* ------------------> Getter methods <--------------------- */
function getId() { return $this->category_id; } function getId() { return $this->category_id; }
function getName() { return $this->name; } function getName() { return $this->name; }
function getFullName() {
return self::getNameById($this->category_id) ?: $this->getLocalName();
}
function getNumFAQs() { return $this->faqs->count(); } function getNumFAQs() { return $this->faqs->count(); }
function getDescription() { return $this->description; } function getDescription() { return $this->description; }
function getDescriptionWithImages() { return Format::viewableImages($this->description); } function getDescriptionWithImages() { return Format::viewableImages($this->description); }
...@@ -109,6 +119,29 @@ class Category extends VerySimpleModel { ...@@ -109,6 +119,29 @@ class Category extends VerySimpleModel {
->limit(5); ->limit(5);
} }
function getPublicSubCategories() {
return $this->getSubCategories(array('public' => true));
}
function getSubCategories($criteria=array()) {
$categories = self::objects()
->filter(array('category_pid' => $this->getId()));
if (isset($criteria['public']) && $categories) {
$categories
->exclude(
Q::any(array(
'ispublic'=>Category::VISIBILITY_PRIVATE,
'faqs__ispublished'=>FAQ::VISIBILITY_PRIVATE,
)))
->annotate(array(
'faq_count'=>SqlAggregate::COUNT('faqs')))
->filter(array('faq_count__gt'=>0));
}
return $categories;
}
/* ------------------> Setter methods <--------------------- */ /* ------------------> Setter methods <--------------------- */
function setName($name) { $this->name=$name; } function setName($name) { $this->name=$name; }
function setNotes($notes) { $this->notes=$notes; } function setNotes($notes) { $this->notes=$notes; }
...@@ -128,7 +161,7 @@ class Category extends VerySimpleModel { ...@@ -128,7 +161,7 @@ class Category extends VerySimpleModel {
$errors['name'] = __('Category name is required'); $errors['name'] = __('Category name is required');
elseif (strlen($vars['name']) < 3) elseif (strlen($vars['name']) < 3)
$errors['name'] = __('Name is too short. 3 chars minimum'); $errors['name'] = __('Name is too short. 3 chars minimum');
elseif (($cid=self::findIdByName($vars['name'])) && $cid != $vars['id']) elseif (($cid=self::findIdByName($vars['name'], $vars['pid'])) && $cid != $vars['id'])
$errors['name'] = __('Category already exists'); $errors['name'] = __('Category already exists');
if (!$vars['description']) if (!$vars['description'])
...@@ -143,6 +176,7 @@ class Category extends VerySimpleModel { ...@@ -143,6 +176,7 @@ class Category extends VerySimpleModel {
$this->ispublic = $vars['ispublic']; $this->ispublic = $vars['ispublic'];
$this->name = $vars['name']; $this->name = $vars['name'];
$this->category_pid = $vars['pid'] ?: 0;
$this->description = Format::sanitize($vars['description']); $this->description = Format::sanitize($vars['description']);
$this->notes = Format::sanitize($vars['notes']); $this->notes = Format::sanitize($vars['notes']);
...@@ -223,26 +257,72 @@ class Category extends VerySimpleModel { ...@@ -223,26 +257,72 @@ class Category extends VerySimpleModel {
/* ------------------> Static methods <--------------------- */ /* ------------------> Static methods <--------------------- */
static function findIdByName($name) { static function findIdByName($name, $pid=null) {
$row = self::objects()->filter(array( $row = self::objects()->filter(array(
'name'=>$name 'name'=>$name,
'category_pid' => $pid ?: null
))->values_flat('category_id')->first(); ))->values_flat('category_id')->first();
return ($row) ? $row[0] : null; return ($row) ? $row[0] : null;
} }
static function findByName($name) { static function findByName($name, $pid=null) {
return self::objects()->filter(array( return self::objects()->filter(array(
'name'=>$name 'name'=>$name,
'category_pid' => $pid ?: null
))->one(); ))->one();
} }
static function getNameById($id) {
$names = static::getCategories();
return $names[$id] ?: '';
}
static function getFeatured() { static function getFeatured() {
return self::objects()->filter(array( return self::objects()->filter(array(
'ispublic'=>self::VISIBILITY_FEATURED 'ispublic'=>self::VISIBILITY_FEATURED
)); ));
} }
static function getCategories($criteria=null, $localize=true) {
static $categories = null;
if (!isset($categories) || $criteria) {
$categories = array();
$query = self::objects();
$query->order_by('name')
->values('category_id', 'category_pid', 'name', 'parent');
foreach ($query as $row)
$categories[$row['category_id']] = $row;
// Resolve parent names
$names = array();
foreach ($categories as $id=>$info) {
$name = $info['name'];
$loop = array($id=>true);
$parent = false;
while ($info['category_pid'] && ($info = $categories[$info['category_pid']])) {
$name = sprintf('%s / %s', $info['name'], $name);
if (isset($loop[$info['category_pid']]))
break;
$loop[$info['category_pid']] = true;
$parent = $info;
}
// TODO: localize category names
$names[$id] = $name;
}
asort($names);
if ($criteria)
return $names;
$categories = $names;
}
return $categories;
}
static function create($vars=false) { static function create($vars=false) {
$category = new static($vars); $category = new static($vars);
$category->created = SqlFunction::NOW(); $category->created = SqlFunction::NOW();
......
<?php <?php
if(!defined('OSTCLIENTINC') || !$category || !$category->isPublic()) die('Access Denied'); if(!defined('OSTCLIENTINC') || !$category || !$category->isPublic()) die('Access Denied');
?> ?>
<div class="row"> <div class="row">
<div class="span8"> <div class="span8">
<h1><?php echo __('Frequently Asked Questions');?></h1> <h1><?php echo $category->getFullName(); ?></h1>
<h2><strong><?php echo $category->getLocalName() ?></strong></h2>
<p> <p>
<?php echo Format::safe_html($category->getLocalDescriptionWithImages()); ?> <?php echo Format::safe_html($category->getLocalDescriptionWithImages()); ?>
</p> </p>
<?php
if (($subs=$category->getSubCategories(array('public' => true)))) {
echo '<div>';
foreach ($subs as $c) {
echo sprintf('<div><i class="icon-folder-open-alt"></i>
<a href="faq.php?cid=%d">%s (%d)</a></div>',
$c->getId(),
$c->getLocalName(),
$c->getNumFAQs()
);
}
echo '</div>';
} ?>
<hr> <hr>
<?php <?php
$faqs = FAQ::objects() $faqs = FAQ::objects()
...@@ -22,7 +34,7 @@ $faqs = FAQ::objects() ...@@ -22,7 +34,7 @@ $faqs = FAQ::objects()
if ($faqs->exists(true)) { if ($faqs->exists(true)) {
echo ' echo '
<h2>'.__('Further Articles').'</h2> <h2>'.__('Frequently Asked Questions').'</h2>
<div id="faq"> <div id="faq">
<ol>'; <ol>';
foreach ($faqs as $F) { foreach ($faqs as $F) {
...@@ -33,7 +45,7 @@ foreach ($faqs as $F) { ...@@ -33,7 +45,7 @@ foreach ($faqs as $F) {
} }
echo ' </ol> echo ' </ol>
</div>'; </div>';
}else { } elseif (!$category->children) {
echo '<strong>'.__('This category does not have any FAQs.').' <a href="index.php">'.__('Back To Index').'</a></strong>'; echo '<strong>'.__('This category does not have any FAQs.').' <a href="index.php">'.__('Back To Index').'</a></strong>';
} }
?> ?>
......
...@@ -7,10 +7,11 @@ $category=$faq->getCategory(); ...@@ -7,10 +7,11 @@ $category=$faq->getCategory();
<div class="row"> <div class="row">
<div class="span8"> <div class="span8">
<h1><?php echo __('Frequently Asked Questions');?></h1> <h1><?php echo __('Frequently Asked Question');?></h1>
<div id="breadcrumbs"> <div id="breadcrumbs" style="padding-top:2px;">
<a href="index.php"><?php echo __('All Categories');?></a> <a href="index.php"><?php echo __('All Categories');?></a>
&raquo; <a href="faq.php?cid=<?php echo $category->getId(); ?>"><?php echo $category->getName(); ?></a> &raquo; <a href="faq.php?cid=<?php echo $category->getId(); ?>"><?php
echo $category->getFullName(); ?></a>
</div> </div>
<div class="faq-content"> <div class="faq-content">
......
...@@ -4,23 +4,57 @@ ...@@ -4,23 +4,57 @@
$categories = Category::objects() $categories = Category::objects()
->exclude(Q::any(array( ->exclude(Q::any(array(
'ispublic'=>Category::VISIBILITY_PRIVATE, 'ispublic'=>Category::VISIBILITY_PRIVATE,
'faqs__ispublished'=>FAQ::VISIBILITY_PRIVATE, Q::all(array(
'faqs__ispublished'=>FAQ::VISIBILITY_PRIVATE,
'children__ispublic' => Category::VISIBILITY_PRIVATE,
'children__faqs__ispublished'=>FAQ::VISIBILITY_PRIVATE,
))
))) )))
->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs'))) //->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs__ispublished')));
->filter(array('faq_count__gt'=>0)); ->annotate(array('faq_count' => SqlAggregate::COUNT(
SqlCase::N()
->when(array(
'faqs__ispublished__gt'=> FAQ::VISIBILITY_PRIVATE), 1)
->otherwise(null)
)));
// ->filter(array('faq_count__gt' => 0));
if ($categories->exists(true)) { ?> if ($categories->exists(true)) { ?>
<div><?php echo __('Click on the category to browse FAQs.'); ?></div> <div><?php echo __('Click on the category to browse FAQs.'); ?></div>
<ul id="kb"> <ul id="kb">
<?php <?php
foreach ($categories as $C) { ?> foreach ($categories as $C) {
// Don't show subcategories with parents.
if (($p=$C->parent)
&& ($categories->findFirst(array(
'category_id' => $p->getId()))))
continue;
?>
<li><i></i> <li><i></i>
<div style="margin-left:45px"> <div style="margin-left:45px">
<h4><?php echo sprintf('<a href="faq.php?cid=%d">%s (%d)</a>', <h4><?php echo sprintf('<a href="faq.php?cid=%d">%s %s</a>',
$C->getId(), Format::htmlchars($C->getLocalName()), $C->faq_count); ?></h4> $C->getId(), Format::htmlchars($C->getFullName()),
$C->faq_count ? "({$C->faq_count})": ''
); ?></h4>
<div class="faded" style="margin:10px 0"> <div class="faded" style="margin:10px 0">
<?php echo Format::safe_html($C->getLocalDescriptionWithImages()); ?> <?php echo Format::safe_html($C->getLocalDescriptionWithImages()); ?>
</div> </div>
<?php foreach ($C->faqs <?php
if (($subs=$C->getPublicSubCategories())) {
echo '<p/><div style="padding-bottom:15px;">';
foreach ($subs as $c) {
echo sprintf('<div><i class="icon-folder-open"></i>
<a href="faq.php?cid=%d">%s (%d)</a></div>',
$c->getId(),
$c->getLocalName(),
$c->getNumFAQs()
);
}
echo '</div>';
}
foreach ($C->faqs
->exclude(array('ispublished'=>FAQ::VISIBILITY_PRIVATE)) ->exclude(array('ispublished'=>FAQ::VISIBILITY_PRIVATE))
->limit(5) as $F) { ?> ->limit(5) as $F) { ?>
<div class="popular-faq"><i class="icon-file-alt"></i> <div class="popular-faq"><i class="icon-file-alt"></i>
......
...@@ -95,7 +95,7 @@ $pageNav->paginate($categories); ...@@ -95,7 +95,7 @@ $pageNav->paginate($categories);
<?php echo $sel?'checked="checked"':''; ?>> <?php echo $sel?'checked="checked"':''; ?>>
</td> </td>
<td><a class="truncate" style="width:500px" href="categories.php?id=<?php echo $C->getId(); ?>"><?php <td><a class="truncate" style="width:500px" href="categories.php?id=<?php echo $C->getId(); ?>"><?php
echo $C->getLocalName(); ?></a></td> echo Category::getNamebyId($C->getId()); ?></a></td>
<td><?php echo $C->getVisibilityDescription(); ?></td> <td><?php echo $C->getVisibilityDescription(); ?></td>
<td style="text-align:right;padding-right:25px;"><?php echo $faqs; ?></td> <td style="text-align:right;padding-right:25px;"><?php echo $faqs; ?></td>
<td>&nbsp;<?php echo Format::datetime($C->updated); ?></td> <td>&nbsp;<?php echo Format::datetime($C->updated); ?></td>
......
...@@ -107,6 +107,24 @@ if (count($langs) > 1) { ?> ...@@ -107,6 +107,24 @@ if (count($langs) > 1) { ?>
?>" id="lang-<?php echo $tag; ?>" ?>" id="lang-<?php echo $tag; ?>"
<?php if ($i['direction'] == 'rtl') echo 'dir="rtl" class="rtl"'; ?> <?php if ($i['direction'] == 'rtl') echo 'dir="rtl" class="rtl"'; ?>
> >
<div style="padding-bottom:8px;">
<b><?php echo __('Parent');?></b>:
<div class="faded"><?php echo __('Parent Category');?></div>
</div>
<div style="padding-bottom:8px;">
<select name="pid">
<option value="">&mdash; <?php echo __('Top-Level Category'); ?> &mdash;</option>
<?php
foreach (Category::getCategories() as $id=>$name) {
if ($info['id'] && $id == $info['id'])
continue; ?>
<option value="<?php echo $id; ?>" <?php
if ($info['category_pid'] == $id) echo 'selected="selected"';
?>><?php echo $name; ?></option>
<?php
} ?>
</select>
</div>
<div style="padding-bottom:8px;"> <div style="padding-bottom:8px;">
<b><?php echo __('Category Name');?></b>: <b><?php echo __('Category Name');?></b>:
<span class="error">*</span> <span class="error">*</span>
......
...@@ -55,7 +55,7 @@ foreach ($categories as $C) { ...@@ -55,7 +55,7 @@ foreach ($categories as $C) {
<i class="icon-fixed-width <?php <i class="icon-fixed-width <?php
if ($active) echo 'icon-hand-right'; ?>"></i> if ($active) echo 'icon-hand-right'; ?>"></i>
<?php echo sprintf('%s (%d)', <?php echo sprintf('%s (%d)',
Format::htmlchars($C->getLocalName()), Format::htmlchars($C->getFullName()),
$C->faq_count); ?></a> $C->faq_count); ?></a>
</li> <?php </li> <?php
} ?> } ?>
...@@ -140,7 +140,9 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. ...@@ -140,7 +140,9 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search.
} }
} else { //Category Listing. } else { //Category Listing.
$categories = Category::objects() $categories = Category::objects()
->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs'))); ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs')))
->filter(array('category_pid__isnull' => true));
if (count($categories)) { if (count($categories)) {
$categories->sort(function($a) { return $a->getLocalName(); }); $categories->sort(function($a) { return $a->getLocalName(); });
...@@ -150,11 +152,25 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. ...@@ -150,11 +152,25 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search.
echo sprintf(' echo sprintf('
<li> <li>
<h4><a class="truncate" style="max-width:600px" href="kb.php?cid=%d">%s (%d)</a> - <span>%s</span></h4> <h4><a class="truncate" style="max-width:600px" href="kb.php?cid=%d">%s (%d)</a> - <span>%s</span></h4>
%s %s ',
</li>',$C->getId(),$C->getLocalName(),$C->faq_count, $C->getId(),$C->getLocalName(),$C->faq_count,
$C->getVisibilityDescription(), $C->getVisibilityDescription(),
Format::safe_html($C->getLocalDescriptionWithImages()) Format::safe_html($C->getLocalDescriptionWithImages())
); );
if ($C->children) {
echo '<p/><div>';
foreach ($C->children as $c) {
echo sprintf('<div><i class="icon-folder-open-alt"></i>
<a href="kb.php?cid=%d">%s (%d)</a> - <span>%s</span></div>',
$c->getId(),
$c->getLocalName(),
$c->getNumFAQs(),
$c->getVisibilityDescription()
);
}
echo '</div>';
}
echo '</li>';
} }
echo '</ul>'; echo '</ul>';
} else { } else {
......
...@@ -36,13 +36,27 @@ echo sprintf('<div class="pull-right flush-right"> ...@@ -36,13 +36,27 @@ echo sprintf('<div class="pull-right flush-right">
</div> </div>
<div class="faq-category"> <div class="faq-category">
<div style="margin-bottom:10px;"> <div style="margin-bottom:10px;">
<div class="faq-title pull-left"><?php echo $category->getName() ?></div> <div class="faq-title pull-left"><?php echo $category->getFullName() ?></div>
<div class="faq-status inline">(<?php echo $category->isPublic()?__('Public'):__('Internal'); ?>)</div> <div class="faq-status inline">(<?php echo $category->isPublic()?__('Public'):__('Internal'); ?>)</div>
<div class="clear"><time class="faq"> <?php echo __('Last Updated').' '. Format::daydatetime($category->getUpdateDate()); ?></time></div> <div class="clear"><time class="faq"> <?php echo __('Last Updated').' '. Format::daydatetime($category->getUpdateDate()); ?></time></div>
</div> </div>
<div class="cat-desc has_bottom_border"> <div class="cat-desc has_bottom_border">
<?php echo Format::display($category->getDescription()); ?> <?php echo Format::display($category->getDescription());
</div> if ($category->children) {
echo '<p/><div>';
foreach ($category->children as $c) {
echo sprintf('<div><i class="icon-folder-open-alt"></i>
<a href="kb.php?cid=%d">%s (%d)</a> - <span>%s</span></div>',
$c->getId(),
$c->getLocalName(),
$c->getNumFAQs(),
$c->getVisibilityDescription()
);
}
echo '</div>';
}
?>
</div>
<?php <?php
...@@ -61,7 +75,7 @@ if ($faqs->exists(true)) { ...@@ -61,7 +75,7 @@ if ($faqs->exists(true)) {
} }
echo ' </ol> echo ' </ol>
</div>'; </div>';
}else { } elseif (!$category->children) {
echo '<strong>'.__('Category does not have FAQs').'</strong>'; echo '<strong>'.__('Category does not have FAQs').'</strong>';
} }
?> ?>
......
...@@ -29,7 +29,8 @@ if ($thisstaff->hasPerm(FAQ::PERM_MANAGE)) { ?> ...@@ -29,7 +29,8 @@ if ($thisstaff->hasPerm(FAQ::PERM_MANAGE)) { ?>
<div id="breadcrumbs"> <div id="breadcrumbs">
<a href="kb.php"><?php echo __('All Categories');?></a> <a href="kb.php"><?php echo __('All Categories');?></a>
&raquo; <a href="kb.php?cid=<?php echo $category->getId(); ?>"><?php echo $category->getName(); ?></a> &raquo; <a href="kb.php?cid=<?php echo $category->getId(); ?>"><?php
echo $category->getFullName(); ?></a>
<span class="faded">(<?php echo $category->isPublic()?__('Public'):__('Internal'); ?>)</span> <span class="faded">(<?php echo $category->isPublic()?__('Public'):__('Internal'); ?>)</span>
</div> </div>
......
...@@ -63,7 +63,7 @@ $qstr = Http::build_query($qs); ...@@ -63,7 +63,7 @@ $qstr = Http::build_query($qs);
<option value="<?php echo $C->getId(); ?>" <?php <option value="<?php echo $C->getId(); ?>" <?php
if ($C->getId() == $info['category_id']) echo 'selected="selected"'; if ($C->getId() == $info['category_id']) echo 'selected="selected"';
?>><?php echo sprintf('%s (%s)', ?>><?php echo sprintf('%s (%s)',
$C->getName(), Category::getNameById($C->getId()),
$C->isPublic() ? __('Public') : __('Private') $C->isPublic() ? __('Public') : __('Private')
); ?></option> ); ?></option>
<?php } ?> <?php } ?>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment