diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index fa3432c5aeda55594612550fe2183db435009312..1c27dede5e0257f2689723b42cb822c02ba2d689 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -489,6 +489,7 @@ body { width: 49.7%; display: inline-block; box-sizing: border-box; + vertical-align: top; } .category-name { display: inline-block; @@ -1014,11 +1015,13 @@ img.sign-in-image { } .span4 { display: inline-block; - width: 31%; + width: 30.5%; margin: 0 1%; + vertical-align: top; } .span8 { display: inline-block; - width: 65%; + width: 64.5%; margin: 0 1%; + vertical-align: top; } diff --git a/include/class.category.php b/include/class.category.php index a32d0563d3cafa733ff1bd7ee1440a502517ca10..784e2769ceeaf1c3af9eddb8106412d85e2fabc6 100644 --- a/include/class.category.php +++ b/include/class.category.php @@ -27,6 +27,10 @@ class Category extends VerySimpleModel { ), ); + const VISIBILITY_FEATURED = 2; + const VISIBILITY_PUBLIC = 1; + const VISIBILITY_PRIVATE = 0; + var $_local; /* ------------------> Getter methods <--------------------- */ @@ -34,11 +38,24 @@ class Category extends VerySimpleModel { function getName() { return $this->name; } function getNumFAQs() { return $this->faqs->count(); } function getDescription() { return $this->description; } + function getDescriptionWithImages() { return Format::viewableImages($this->description); } function getNotes() { return $this->notes; } function getCreateDate() { return $this->created; } function getUpdateDate() { return $this->updated; } - function isPublic() { return $this->ispublic; } + function isPublic() { + return $this->ispublic != self::VISIBILITY_PRIVATE; + } + function getVisibilityDescription() { + switch ($this->ispublic) { + case self::VISIBILITY_PRIVATE: + return __('Private'); + case self::VISIBILITY_PUBLIC: + return __('Public'); + case self::VISIBILITY_FEATURED: + return __('Featured'); + } + } function getHashtable() { return $this->ht; } // Translation interface ---------------------------------- @@ -50,8 +67,8 @@ class Category extends VerySimpleModel { $T = CustomDataTranslation::translate($tag); return $T != $tag ? $T : $this->ht[$subtag]; } - function getLocalDescription($lang=false) { - return $this->_getLocal('description', $lang); + function getLocalDescriptionWithImages($lang=false) { + return Format::viewableImages($this->_getLocal('description', $lang)); } function getLocalName($lang=false) { return $this->_getLocal('name', $lang); @@ -223,7 +240,7 @@ class Category extends VerySimpleModel { static function getFeatured() { return self::objects()->filter(array( - 'ispublic'=>2 + 'ispublic'=>self::VISIBILITY_FEATURED )); } diff --git a/include/class.faq.php b/include/class.faq.php index a8d8c512df782d6e07ed08713b5737227af06bd9..3966bec589df6ecf18f8e75d6e6a33e2935d2153 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -22,6 +22,7 @@ class FAQ extends VerySimpleModel { 'pk' => array('faq_id'), 'ordering' => array('question'), 'defer' => array('answer'), + 'select_related'=> array('category'), 'joins' => array( 'category' => array( 'constraint' => array( @@ -36,6 +37,12 @@ class FAQ extends VerySimpleModel { 'list' => true, 'null' => true, ), + 'topics' => array( + 'constraint' => array( + 'faq_id' => 'FaqTopic.faq_id' + ), + 'null' => true, + ), ), ); @@ -43,6 +50,10 @@ class FAQ extends VerySimpleModel { var $topics; var $_local; + const VISIBILITY_PRIVATE = 0; + const VISIBILITY_PUBLIC = 1; + const VISIBILITY_FEATURED = 2; + function __onload() { if (isset($this->faq_id)) $this->attachments = new GenericAttachments($this->getId(), 'F'); @@ -50,7 +61,11 @@ class FAQ extends VerySimpleModel { /* ------------------> Getter methods <--------------------- */ function getId() { return $this->faq_id; } - function getHashtable() { return $this->ht; } + function getHashtable() { + $base = $this->ht; + unset($base['category']); + return $base; + } function getKeywords() { return $this->keywords; } function getQuestion() { return $this->question; } function getAnswer() { return $this->answer; } @@ -67,7 +82,20 @@ class FAQ extends VerySimpleModel { function getNotes() { return $this->notes; } function getNumAttachments() { return $this->attachments->count(); } - function isPublished() { return (!!$this->ispublished && !!$this->category->ispublic); } + function isPublished() { + return $this->ispublished != self::VISIBILITY_PRIVATE + && $this->category->isPublic(); + } + function getVisibilityDescription() { + switch ($this->ispublished) { + case self::VISIBILITY_PRIVATE: + return __('Internal'); + case self::VISIBILITY_PUBLIC: + return __('Public'); + case self::VISIBILITY_FEATURED: + return __('Featured'); + } + } function getCreateDate() { return $this->created; } function getUpdateDate() { return $this->updated; } diff --git a/include/staff/categories.inc.php b/include/staff/categories.inc.php index a948f4976ca123a275e5c3dc0b663db2788e55d7..1cb927395517c996325a8d2b1863014f88d2482c 100644 --- a/include/staff/categories.inc.php +++ b/include/staff/categories.inc.php @@ -2,39 +2,35 @@ if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); $qstr=''; -$sql='SELECT cat.category_id, cat.name, cat.ispublic, cat.updated, count(faq.faq_id) as faqs '. - ' FROM '.FAQ_CATEGORY_TABLE.' cat '. - ' LEFT JOIN '.FAQ_TABLE.' faq ON (faq.category_id=cat.category_id) '; -$sql.=' WHERE 1'; -$sortOptions=array('name'=>'cat.name','type'=>'cat.ispublic','faqs'=>'faqs','updated'=>'cat.updated'); -$orderWays=array('DESC'=>'DESC','ASC'=>'ASC'); +$categories = Category::objects() + ->annotate(array('faq_count'=>Aggregate::COUNT('faqs'))); +$sortOptions=array('name'=>'name','type'=>'ispublic','faqs'=>'faq_count','updated'=>'updated'); +$orderWays=array('DESC'=>'-','ASC'=>''); $sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name'; //Sorting options... if($sort && $sortOptions[$sort]) { $order_column =$sortOptions[$sort]; } -$order_column=$order_column?$order_column:'cat.name'; +$order_column=$order_column ?: 'name'; if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) { $order=$orderWays[strtoupper($_REQUEST['order'])]; } -$order=$order?$order:'ASC'; +$order=$order ?: ''; -if($order_column && strpos($order_column,',')){ - $order_column=str_replace(','," $order,",$order_column); -} $x=$sort.'_sort'; $$x=' class="'.strtolower($order).'" '; $order_by="$order_column $order "; -$total=db_count('SELECT count(*) FROM '.FAQ_CATEGORY_TABLE.' cat '); +$total=$categories->count(); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); $pageNav->setURL('categories.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); $qstr.='&order='.($order=='DESC'?'ASC':'DESC'); -$query="$sql GROUP BY cat.category_id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); -$res=db_query($query); -if($res && ($num=db_num_rows($res))) + +$categories = $categories->offset($pageNav->getStart()) + ->limit($pageNav->getLimit()); +if ($total) $showing=$pageNav->showing().' '.__('categories'); else $showing=__('No FAQ categories found!'); @@ -65,29 +61,27 @@ else <?php $total=0; $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null; - if($res && db_num_rows($res)): - while ($row = db_fetch_array($res)) { - $sel=false; - if($ids && in_array($row['category_id'],$ids)) - $sel=true; + foreach ($categories as $C) { + $sel=false; + if ($ids && in_array($C->getId(), $ids)) + $sel=true; - $faqs=0; - if($row['faqs']) - $faqs=sprintf('<a href="faq.php?cid=%d">%d</a>',$row['category_id'],$row['faqs']); - ?> - <tr id="<?php echo $row['category_id']; ?>"> + $faqs=0; + if ($C->faq_count) + $faqs=sprintf('<a href="faq.php?cid=%d">%d</a>',$C->getId(),$C->faq_count); + ?> + <tr id="<?php echo $C->getId(); ?>"> <td width=7px> - <input type="checkbox" name="ids[]" value="<?php echo $row['category_id']; ?>" class="ckb" + <input type="checkbox" name="ids[]" value="<?php echo $C->getId(); ?>" class="ckb" <?php echo $sel?'checked="checked"':''; ?>> </td> - <td><a href="categories.php?id=<?php echo $row['category_id']; ?>"><?php echo Format::truncate($row['name'],200); ?></a> </td> - <td><?php echo $row['ispublic']?'<b>'.__('Public').'</b>':__('Internal'); ?></td> + <td><a class="truncate" style="width:500px" href="categories.php?id=<?php echo $C->getId(); ?>"><?php + echo $C->getLocalName(); ?></a></td> + <td><?php echo $C->getVisibilityDescription(); ?></td> <td style="text-align:right;padding-right:25px;"><?php echo $faqs; ?></td> - <td> <?php echo Format::db_datetime($row['updated']); ?></td> - </tr> - <?php - } //end of while. - endif; ?> + <td> <?php echo Format::db_datetime($C->updated); ?></td> + </tr><?php + } // end of foreach ?> <tfoot> <tr> <td colspan="5"> diff --git a/include/staff/faq-categories.inc.php b/include/staff/faq-categories.inc.php index 9d9efa32ba994d56730d3b49f8fa62e343455fdb..0019380d022a5e9fd00d7fa87b74785c41854c0b 100644 --- a/include/staff/faq-categories.inc.php +++ b/include/staff/faq-categories.inc.php @@ -10,21 +10,19 @@ if(!defined('OSTSTAFFINC') || !$thisstaff) die('Access Denied'); <select name="cid" id="cid"> <option value="">— <?php echo __('All Categories');?> —</option> <?php - $sql='SELECT category_id, name, count(faq.category_id) as faqs ' - .' FROM '.FAQ_CATEGORY_TABLE.' cat ' - .' LEFT JOIN '.FAQ_TABLE.' faq USING(category_id) ' - .' GROUP BY cat.category_id ' - .' HAVING faqs>0 ' - .' ORDER BY cat.name DESC '; - if(($res=db_query($sql)) && db_num_rows($res)) { - while($row=db_fetch_array($res)) - echo sprintf('<option value="%d" %s>%s (%d)</option>', - $row['category_id'], - ($_REQUEST['cid'] && $row['category_id']==$_REQUEST['cid']?'selected="selected"':''), - $row['name'], - $row['faqs']); - } - ?> + $categories = Category::objects() + ->annotate(array('faq_count'=>Aggregate::COUNT('faqs'))) + ->filter(array('faq_count__gt'=>0)) + ->order_by('name'); +print $categories; + foreach ($categories as $C) { + echo sprintf('<option value="%d" %s>%s (%d)</option>', + $C->getId(), + ($_REQUEST['cid'] && $C->getId()==$_REQUEST['cid']?'selected="selected"':''), + $C->getLocalName(), + $C->faq_count + ); + } ?> </select> <input id="searchSubmit" type="submit" value="<?php echo __('Search');?>"> </div> @@ -32,19 +30,18 @@ if(!defined('OSTSTAFFINC') || !$thisstaff) die('Access Denied'); <select name="topicId" style="width:350px;" id="topic-id"> <option value="">— <?php echo __('All Help Topics');?> —</option> <?php - $sql='SELECT ht.topic_id, CONCAT_WS(" / ", pht.topic, ht.topic) as helptopic, count(faq.topic_id) as faqs ' - .' FROM '.TOPIC_TABLE.' ht ' - .' LEFT JOIN '.TOPIC_TABLE.' pht ON (pht.topic_id=ht.topic_pid) ' - .' LEFT JOIN '.FAQ_TOPIC_TABLE.' faq ON(faq.topic_id=ht.topic_id) ' - .' GROUP BY ht.topic_id ' - .' HAVING faqs>0 ' - .' ORDER BY helptopic'; - if(($res=db_query($sql)) && db_num_rows($res)) { - while($row=db_fetch_array($res)) - echo sprintf('<option value="%d" %s>%s (%d)</option>', - $row['topic_id'], - ($_REQUEST['topicId'] && $row['topic_id']==$_REQUEST['topicId']?'selected="selected"':''), - $row['helptopic'], $row['faqs']); + $topics = Topic::objects() + ->annotate(array('faq_count'=>Aggregate::COUNT('faqs'))) + ->filter(array('faq_count__gt'=>0)) + ->all(); + usort($topics, function($a, $b) { + return strcmp($a->getFullName(), $b->getFullName()); + }); + foreach ($topics as $T) { + echo sprintf('<option value="%d" %s>%s (%d)</option>', + $T->getId(), + ($_REQUEST['topicId'] && $T->getId()==$_REQUEST['topicId']?'selected="selected"':''), + $T->getFullName(), $T->faq_count); } ?> </select> @@ -54,39 +51,36 @@ if(!defined('OSTSTAFFINC') || !$thisstaff) die('Access Denied'); <div> <?php if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. - $sql='SELECT faq.faq_id, question, ispublished, count(attach.file_id) as attachments, count(ft.topic_id) as topics ' - .' FROM '.FAQ_TABLE.' faq ' - .' LEFT JOIN '.FAQ_CATEGORY_TABLE.' cat ON(cat.category_id=faq.category_id) ' - .' LEFT JOIN '.FAQ_TOPIC_TABLE.' ft ON(ft.faq_id=faq.faq_id) ' - .' LEFT JOIN '.ATTACHMENT_TABLE.' attach - ON(attach.object_id=faq.faq_id AND attach.type=\'F\' AND attach.inline = 0) ' - .' WHERE 1 '; + $faqs = FAQ::objects() + ->annotate(array( + 'attachment_count'=>Aggregate::COUNT('attachments'), + 'topic_count'=>Aggregate::COUNT('topics') + )) + ->order_by('question'); - if($_REQUEST['cid']) - $sql.=' AND faq.category_id='.db_input($_REQUEST['cid']); + if ($_REQUEST['cid']) + $faqs->filter(array('category_id'=>$_REQUEST['cid'])); - if($_REQUEST['topicId']) - $sql.=' AND ft.topic_id='.db_input($_REQUEST['topicId']); - - if($_REQUEST['q']) { - $sql.=" AND (question LIKE ('%".db_input($_REQUEST['q'],false)."%') - OR answer LIKE ('%".db_input($_REQUEST['q'],false)."%') - OR keywords LIKE ('%".db_input($_REQUEST['q'],false)."%') - OR cat.name LIKE ('%".db_input($_REQUEST['q'],false)."%') - OR cat.description LIKE ('%".db_input($_REQUEST['q'],false)."%') - )"; - } + if ($_REQUEST['topicId']) + $faqs->filter(array('topic_id'=>$_REQUEST['topicId'])); - $sql.=' GROUP BY faq.faq_id ORDER BY question'; + if ($_REQUEST['q']) + $faqs->filter(Q::ANY(array( + 'question__contains'=>$_REQUEST['q'], + 'answer__contains'=>$_REQUEST['q'], + 'keywords__contains'=>$_REQUEST['q'], + 'category__name__contains'=>$_REQUEST['q'], + 'category__description__contains'=>$_REQUEST['q'], + ))); echo "<div><strong>".__('Search Results')."</strong></div><div class='clear'></div>"; - if(($res=db_query($sql)) && db_num_rows($res)) { + if ($faqs->exists(true)) { echo '<div id="faq"> <ol>'; - while($row=db_fetch_array($res)) { - echo sprintf(' - <li><a href="faq.php?id=%d" class="previewfaq">%s</a> - <span>%s</span></li>', - $row['faq_id'],$row['question'],$row['ispublished']?__('Published'):__('Internal')); + foreach ($faqs as $F) { + echo sprintf( + '<li><a href="faq.php?id=%d" class="previewfaq">%s</a> - <span>%s</span></li>', + $F->getId(), $F->getLocalQuestion(), $F->getVisibilityDescription()); } echo ' </ol> </div>'; @@ -94,23 +88,25 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. echo '<strong class="faded">'.__('The search did not match any FAQs.').'</strong>'; } } else { //Category Listing. - $sql='SELECT cat.category_id, cat.name, cat.description, cat.ispublic, count(faq.faq_id) as faqs ' - .' FROM '.FAQ_CATEGORY_TABLE.' cat ' - .' LEFT JOIN '.FAQ_TABLE.' faq ON(faq.category_id=cat.category_id) ' - .' GROUP BY cat.category_id ' - .' ORDER BY cat.name'; - if(($res=db_query($sql)) && db_num_rows($res)) { + $categories = Category::objects() + ->annotate(array('faq_count'=>Aggregate::COUNT('faqs'))) + ->all(); + + if (count($categories)) { + usort($categories, function($a, $b) { + return strcmp($a->getLocalName(), $b->getLocalName()); + }); echo '<div>'.__('Click on the category to browse FAQs or manage its existing FAQs.').'</div> <ul id="kb">'; - while($row=db_fetch_array($res)) { - + foreach ($categories as $C) { echo sprintf(' <li> - <h4><a 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 - </li>',$row['category_id'],$row['name'],$row['faqs'], - ($row['ispublic']?__('Public'):__('Internal')), - Format::safe_html($row['description'])); + </li>',$C->getId(),$C->getLocalName(),$C->faq_count, + $C->getVisibilityDescription(), + Format::safe_html($C->getLocalDescriptionWithImages()) + ); } echo '</ul>'; } else { diff --git a/include/staff/faq-view.inc.php b/include/staff/faq-view.inc.php index 8cb85661cbd2bd9d607156ff0da2760538d98592..0b5533ff8c5a8e1564a3bd11fc103ec916eaed7b 100644 --- a/include/staff/faq-view.inc.php +++ b/include/staff/faq-view.inc.php @@ -49,9 +49,9 @@ if ($otherLangs) { ?> <div><strong><?php echo __('Other Languages'); ?></strong></div> <?php foreach ($otherLangs as $lang) { ?> - <a href="faq.php?kblang=<?php echo $lang; ?>&id=<?php echo $faq->getId(); ?>"> + <div><a href="faq.php?kblang=<?php echo $lang; ?>&id=<?php echo $faq->getId(); ?>"> <?php echo Internationalization::getLanguageDescription($lang); ?> - </a> + </a></div> <?php } ?> </section> <?php } ?> diff --git a/include/staff/faq.inc.php b/include/staff/faq.inc.php index 4578c1f2d71311568fd51111a002aeb2248219da..7315a7fa1667a3fb319b222edb224d2ca02857b3 100644 --- a/include/staff/faq.inc.php +++ b/include/staff/faq.inc.php @@ -14,7 +14,7 @@ if($faq){ $qstr='id='.$faq->getId(); $langs = $cfg->getSecondaryLanguages(); $translations = $faq->getAllTranslations(); - foreach ($cfg->getSecondaryLanguages() as $tag) { + foreach ($langs as $tag) { foreach ($translations as $t) { if (strcasecmp($t->lang, $tag) === 0) { $trans = $t->getComplex(); @@ -102,10 +102,10 @@ if ($topics = Topic::getAllHelpTopics()) { <?php echo __('Featured (promote to front page)'); ?> </option> <option value="1" <?php echo $info['ispublished'] ? 'selected="selected"' : ''; ?>> - <?php echo __('Public (publish)'); ?> + <?php echo __('Public').' '.__('(publish)'); ?> </option> <option value="0" <?php echo !$info['ispublished'] ? 'selected="selected"' : ''; ?>> - <?php echo __('Internal (private)'); ?> + <?php echo __('Internal').' '.('(private)'); ?> </option> </select> <div class="error"><?php echo $errors['ispublished']; ?></div> @@ -125,6 +125,7 @@ if ($topics = Topic::getAllHelpTopics()) { <strong><?php echo __('Knowledgebase Article Content'); ?></strong><br/> <?php echo __('Here you can manage the question and answer for the article. Multiple languages are available if enabled in the admin panel.'); ?> <div class="clear"></div> + <?php $langs = Internationalization::getConfiguredSystemLanguages(); if ($faq) { ?> diff --git a/scp/helptopics.php b/scp/helptopics.php index c447675fda8d796d8fe4528c8e2b77f9a09c3154..01c01337b5f82a083dfd69089c01fc6d5d94fcae 100644 --- a/scp/helptopics.php +++ b/scp/helptopics.php @@ -15,6 +15,7 @@ **********************************************************************/ require('admin.inc.php'); include_once(INCLUDE_DIR.'class.topic.php'); +include_once(INCLUDE_DIR.'class.faq.php'); require_once(INCLUDE_DIR.'class.dynamic_forms.php'); $topic=null;