diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index 1c27dede5e0257f2689723b42cb822c02ba2d689..b8ca14066d9c2c2188eeb23b997675659ea893b8 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -476,7 +476,7 @@ body { #kb > li h4 a { font-size: 14px; } -#kb li i { +#kb > li > i { display: block; width: 32px; height: 32px; diff --git a/css/thread.css b/css/thread.css index cdf25a7200d7b0365283e88a2fe6760c66c86fa7..2001dde95a01ad6761be77a93eadd390544f494d 100644 --- a/css/thread.css +++ b/css/thread.css @@ -109,8 +109,8 @@ box-sizing: border-box; } .thread-body a { - color: #428bca; - text-decoration: none; + color: #428bca !important; + text-decoration: underline; } .thread-body a:hover, .thread-body a:focus { diff --git a/include/class.faq.php b/include/class.faq.php index 3966bec589df6ecf18f8e75d6e6a33e2935d2153..4e0fe8f456d10bb198636f87afe1ed4d546e307a 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -356,13 +356,17 @@ class FAQ extends VerySimpleModel { return $faq; } + static function allPublic() { + return static::objects()->exclude(array( + 'ispublished'=>self::VISIBILITY_PRIVATE, + 'category__ispublic'=>Category::VISIBILITY_PRIVATE, + )); + } + static function countPublishedFAQs() { static $count; if (!isset($count)) { - $count = self::objects()->filter(array( - 'category__ispublic__gt' => 0, - 'ispublished__gt'=> 0 - ))->count(); + $count = self::allPublic()->count(); } return $count; } diff --git a/include/class.forms.php b/include/class.forms.php index d4041dd47cc6e41dd48f141598b081a490280105..eb5aa52ef18b77075c92bf0e5916b72f96b7e18d 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -2330,41 +2330,4 @@ class VisibilityConstraint { } } -class Q { - const NEGATED = 0x0001; - const ANY = 0x0002; - - var $constraints; - var $flags; - var $negated = false; - var $ored = false; - - function __construct($filter, $flags=0) { - $this->constraints = $filter; - $this->negated = $flags & self::NEGATED; - $this->ored = $flags & self::ANY; - } - - function isNegated() { - return $this->negated; - } - - function isOred() { - return $this->ored; - } - - function negate() { - $this->negated = !$this->negated; - return $this; - } - - static function not(array $constraints) { - return new static($constraints, self::NEGATED); - } - - static function any(array $constraints) { - return new static($constraints, self::ORED); - } -} - ?> diff --git a/include/class.ticket.php b/include/class.ticket.php index 5a837b56a78d6578282fa12f4224cb80638c8f4c..807ea26819b0299bd2f630086363a969cd04f0cb 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -32,6 +32,7 @@ include_once(INCLUDE_DIR.'class.canned.php'); require_once(INCLUDE_DIR.'class.dynamic_forms.php'); require_once(INCLUDE_DIR.'class.user.php'); require_once(INCLUDE_DIR.'class.collaborator.php'); +require_once(INCLUDE_DIR.'class.faq.php'); class Ticket { diff --git a/include/client/faq-category.inc.php b/include/client/faq-category.inc.php index 5a17e2176b75349d231bf22a00f50c02f39d88c3..80168a07c83a0c1758da2304225e9e66b880a345 100644 --- a/include/client/faq-category.inc.php +++ b/include/client/faq-category.inc.php @@ -1,34 +1,64 @@ <?php if(!defined('OSTCLIENTINC') || !$category || !$category->isPublic()) die('Access Denied'); ?> -<h1><strong><?php echo $category->getName() ?></strong></h1> + +<div class="row"> +<div class="span8"> + <h1><?php echo __('Frequently Asked Questions');?></h1> + <h2><strong><?php echo $category->getLocalName() ?></strong></h2> <p> -<?php echo Format::safe_html($category->getDescription()); ?> +<?php echo Format::safe_html($category->getLocalDescriptionWithImages()); ?> </p> <hr> <?php -$sql='SELECT faq.faq_id, question, count(attach.file_id) as attachments ' - .' FROM '.FAQ_TABLE.' faq ' - .' LEFT JOIN '.ATTACHMENT_TABLE.' attach - ON(attach.object_id=faq.faq_id AND attach.type=\'F\' AND attach.inline = 0) ' - .' WHERE faq.ispublished=1 AND faq.category_id='.db_input($category->getId()) - .' GROUP BY faq.faq_id ' - .' ORDER BY question'; -if(($res=db_query($sql)) && db_num_rows($res)) { +$faqs = FAQ::objects() + ->filter(array('category'=>$category)) + ->exclude(array('ispublished'=>false)) + ->annotate(array('has_attachments'=>Aggregate::COUNT('attachments', false, + array('attachments__inline'=>0)))) + ->order_by('-ispublished', 'question'); + +if ($faqs->exists(true)) { echo ' - <h2>'.__('Frequently Asked Questions').'</h2> + <h2>'.__('Further Articles').'</h2> <div id="faq"> <ol>'; - while($row=db_fetch_array($res)) { - $attachments=$row['attachments']?'<span class="Icon file"></span>':''; +foreach ($faqs as $F) { + $attachments=$F->has_attachments?'<span class="Icon file"></span>':''; echo sprintf(' <li><a href="faq.php?id=%d" >%s %s</a></li>', - $row['faq_id'],Format::htmlchars($row['question']), $attachments); + $F->getId(),Format::htmlchars($F->question), $attachments); } echo ' </ol> - </div> - <p><a class="back" href="index.php">« '.__('Go Back').'</a></p>'; + </div>'; }else { echo '<strong>'.__('This category does not have any FAQs.').' <a href="index.php">'.__('Back To Index').'</a></strong>'; } ?> +</div> + +<div class="span4"> + <div class="sidebar"> + <div class="searchbar"> + <form method="get" action="faq.php"> + <input type="hidden" name="a" value="search"/> + <input type="text" name="q" class="search" placeholder="<?php + echo __('Search our knowledge base'); ?>"/> + <input type="submit" style="display:none" value="search"/> + </form> + </div> + <div class="content"> + <section> + <div class="header"><?php echo __('Help Topics'); ?></div> +<?php +foreach (Topic::objects() + ->filter(array('faqs__faq__category__category_id'=>$category->getId())) + as $t) { ?> + <a href="?topicId=<?php echo urlencode($t->getId()); ?>" + ><?php echo $t->getFullname(); ?></a> +<?php } ?> + </section> + </div> + </div> +</div> +</div> diff --git a/include/client/kb-categories.inc.php b/include/client/kb-categories.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..27f41475eb61fa7f859fb5cd100a7bce6a29132a --- /dev/null +++ b/include/client/kb-categories.inc.php @@ -0,0 +1,65 @@ +<div class="row"> +<div class="span8"> +<?php + $categories = Category::objects() + ->exclude(Q::any(array('ispublic'=>false, 'faqs__ispublished'=>false))) + ->annotate(array('faq_count'=>Aggregate::count('faqs'))) + ->filter(array('faq_count__gt'=>0)); + if ($categories->all()) { ?> + <div><?php echo __('Click on the category to browse FAQs.'); ?></div> + <ul id="kb"> +<?php + foreach ($categories as $C) { ?> + <li><i></i> + <div style="margin-left:45px"> + <h4><?php echo sprintf('<a href="faq.php?cid=%d">%s (%d)</a>', + $C->getId(), Format::htmlchars($C->getLocalName()), $C->faq_count); ?></h4> + <div class="faded" style="margin:10px 0"> + <?php echo Format::safe_html($C->getLocalDescriptionWithImages()); ?> + </div> +<?php foreach ($C->faqs + ->exclude(array('ispublished'=>false)) + ->order_by('-views')->limit(5) as $F) { ?> + <div class="popular-faq"><i class="icon-file-alt"></i> + <a href="faq.php?id=<?php echo $F->getId(); ?>"> + <?php echo $F->getLocalQuestion() ?: $F->getQuestion(); ?> + </a></div> +<?php } ?> + </div> + </li> +<?php } ?> + </ul> +<?php + } else { + echo __('NO FAQs found'); + } +?> +</div> +<div class="span4"> + <div class="sidebar"> + <div class="searchbar"> + <form method="get" action="faq.php"> + <input type="hidden" name="a" value="search"/> + <select name="topicId" style="width:100%;max-width:100%" + onchange="javascript:this.form.submit();"> + <option value="">— Browse by Topic —</option> +<?php +$topics = Topic::objects() + ->annotate(array('has_faqs'=>Aggregate::COUNT('faqs'))) + ->filter(array('has_faqs__gt'=>0)); +foreach ($topics as $T) { ?> + <option value="<?php echo $T->getId(); ?>"><?php echo $T->getFullName(); + ?></option> +<?php } ?> + </select> + </form> + </div> + <br/> + <div class="content"> + <section> + <div class="header"><?php echo __('Other Resources'); ?></div> + </section> + </div> + </div> +</div> +</div> diff --git a/include/client/kb-search.inc.php b/include/client/kb-search.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..ee8f51f87e4132a9923387bcc21212b50fddd794 --- /dev/null +++ b/include/client/kb-search.inc.php @@ -0,0 +1,58 @@ +<div class="row"> +<div class="span8"> + <h1><?php echo __('Frequently Asked Questions');?></h1> + <div><strong><?php echo __('Search Results'); ?></strong></div> +<?php + if ($faqs->exists(true)) { + echo '<div id="faq">'.sprintf(__('%d FAQs matched your search criteria.'), + count($faqs->all())) + .'<ol>'; + foreach ($faqs as $F) { + echo sprintf( + '<li><a href="faq.php?id=%d" class="previewfaq">%s</a></li>', + $F->getId(), $F->getLocalQuestion(), $F->getVisibilityDescription()); + } + echo '</ol></div>'; + } else { + echo '<strong class="faded">'.__('The search did not match any FAQs.').'</strong>'; + } +?> +</div> + +<div class="span4"> + <div class="sidebar"> + <div class="searchbar"> + <form method="get" action="faq.php"> + <input type="hidden" name="a" value="search"/> + <input type="text" name="q" class="search" placeholder="<?php + echo __('Search our knowledge base'); ?>"/> + <input type="submit" style="display:none" value="search"/> + </form> + </div> + <div class="content"> + <section> + <div class="header"><?php echo __('Help Topics'); ?></div> +<?php +foreach (Topic::objects() + ->annotate(array('faqs_count'=>Aggregate::count('faqs'))) + ->filter(array('faqs_count__gt'=>0)) + as $t) { ?> + <div><a href="?topicId=<?php echo urlencode($t->getId()); ?>" + ><?php echo $t->getFullName(); ?></a></div> +<?php } ?> + </section> + <section> + <div class="header"><?php echo __('Categories'); ?></div> +<?php +foreach (Category::objects() + ->annotate(array('faqs_count'=>Aggregate::count('faqs'))) + ->filter(array('faqs_count__gt'=>0)) + as $C) { ?> + <div><a href="?cid=<?php echo urlencode($C->getId()); ?>" + ><?php echo $C->getLocalName(); ?></a></div> +<?php } ?> + </section> + </div> + </div> +</div> +</div> diff --git a/include/client/knowledgebase.inc.php b/include/client/knowledgebase.inc.php index 4b35efdc9262bdf95422a4f971ac7df03d3320d8..fa9ba29cc185d435dabaa85daad6d1f79d28a47a 100644 --- a/include/client/knowledgebase.inc.php +++ b/include/client/knowledgebase.inc.php @@ -2,95 +2,33 @@ if(!defined('OSTCLIENTINC')) die('Access Denied'); ?> -<h1><?php echo __('Frequently Asked Questions');?></h1> -<form action="index.php" method="get" id="kb-search"> - <input type="hidden" name="a" value="search"> - <div> - <input id="query" type="text" size="20" name="q" value="<?php echo Format::htmlchars($_REQUEST['q']); ?>"> - <input id="searchSubmit" type="submit" value="<?php echo __('Search');?>"> - </div> - <div class="sidebar"> - <select name="topicId" id="topic-id"> - <option value="">— <?php echo __('All Help Topics');?> —</option> - <?php -foreach (Topic::objects() - ->annotate(array('faqs_count'=>Aggregate::count('faqs'))) - ->filter(array('faqs_count__gt'=>0)) - as $t) { - echo sprintf('<option value="%d" %s>%s</option>', - $t->getId(), - ($_REQUEST['topicId'] && $t->getId() == $_REQUEST['topicId']?'selected="selected"':''), - $t->getFullName()); - } - ?> - </select> - </div> -</form> -<hr> -<div> <?php -if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. - $sql='SELECT faq.faq_id, question ' - .' 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) ' - .' WHERE faq.ispublished=1 AND cat.ispublic=1'; +if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search + $faqs = FAQ::allPublic() + ->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['topicId']) + $faqs->filter(array('topics__topic_id'=>$_REQUEST['topicId'])); + 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'], + ))); - 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)."%') - )"; - } + include CLIENTINC_DIR . 'kb-search.inc.php'; - $sql.=' GROUP BY faq.faq_id ORDER BY question'; - echo "<div><strong>".__('Search Results').'</strong></div><div class="clear"></div>'; - if(($res=db_query($sql)) && ($num=db_num_rows($res))) { - echo '<div id="faq">'.sprintf(__('%d FAQs matched your search criteria.'),$num).' - <ol>'; - while($row=db_fetch_array($res)) { - echo sprintf(' - <li><a href="faq.php?id=%d" class="previewfaq">%s</a></li>', - $row['faq_id'],$row['question'],$row['ispublished']?__('Published'):__('Internal')); - } - echo ' </ol> - </div>'; - } else { - echo '<strong class="faded">'.__('The search did not match any FAQs.').'</strong>'; - } } else { //Category Listing. - $categories = Category::objects() - ->filter(array('ispublic'=>true, 'faqs__ispublished'=>true)) - ->annotate(array('faq_count'=>Aggregate::count('faqs'))) - ->filter(array('faq_count__gt'=>0)); - if ($categories->all()) { - echo '<div>'.__('Click on the category to browse FAQs.').'</div> - <ul id="kb">'; - - foreach ($categories as $C) { ?> - <li><i></i> - <h4><?php echo sprintf('<a href="faq.php?cid=%d">%s (%d)</a>', - $C->getId(), Format::htmlchars($C->name), $C->faq_count); ?></h4> - <?php echo Format::safe_html($C->description); ?> -<?php foreach ($C->faqs->order_by('-view')->limit(5) as $F) { ?> - <div class="popular-faq"><?php echo $F->question; ?></div> -<?php } ?> - </li> -<?php } ?> - </ul> -<?php - } else { - echo __('NO FAQs found'); - } + include CLIENTINC_DIR . 'kb-categories.inc.php'; } ?> -</div> diff --git a/include/staff/templates/faq-print.tmpl.php b/include/staff/templates/faq-print.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..76842eeb37dec37a27f1695f570ecedfc93f761f --- /dev/null +++ b/include/staff/templates/faq-print.tmpl.php @@ -0,0 +1,12 @@ +<div class="faq-title flush-left"><?php echo $faq->getLocalQuestion() ?> +</div> + +<div class="faded"><?php echo __('Last updated');?> + <?php echo Format::daydatetime($faq->getUpdateDate()); ?> +</div> + +<br/> + +<div class="thread-body bleed"> +<?php echo $faq->getLocalAnswerWithImages(); ?> +</div> diff --git a/include/upgrader/upgrade.inc.php b/include/upgrader/upgrade.inc.php index c6492c65cb311e404e9fd497f817e51136f13152..8588f742eb644904593c62a68125ce8940513b5e 100644 --- a/include/upgrader/upgrade.inc.php +++ b/include/upgrader/upgrade.inc.php @@ -37,11 +37,13 @@ $action=$upgrader->getNextAction(); </form> </div> </div> - <div id="sidebar"> + <div class="sidebar"> + <div class="content"> <h3><?php echo __('Upgrade Tips');?></h3> <p>1. <?php echo __('Be patient the process will take a couple of minutes.');?></p> <p>2. <?php echo __('If you experience any problems, you can always restore your files/database backup.');?></p> <p>3. <?php echo sprintf(__('We can help. Feel free to %1$s contact us %2$s for professional help.'), '<a href="http://osticket.com/support" target="_blank">', '</a>');?></p> + </div> </div> <div class="clear"></div> <div id="upgrading"> diff --git a/js/filedrop.field.js b/js/filedrop.field.js index 4eb5d27e91ebdb78033b0e6cdab7e4d20b57f0f8..7d332b575943224a0b6b9884b2a252bdbdb98b70 100644 --- a/js/filedrop.field.js +++ b/js/filedrop.field.js @@ -171,7 +171,6 @@ .hide()) .append($('<input type="hidden"/>').attr('name', this.options.name) .val(file.id)) - .append($('<div class="clear"></div>')); if (this.options.deletable) { filenode.prepend($('<span><i class="icon-trash"></i></span>') .addClass('trash pull-right') diff --git a/scp/css/scp.css b/scp/css/scp.css index 27e0ee3b0ebc755f0a4a8036ed2ae06a791c1858..558362932a82e357388b8f753e982ffc4351fe4f 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -154,7 +154,6 @@ div#header a { height:26px; color:#555; text-align:center; - font-weight:bold; position:relative; border-radius:5px 5px 0 0; @@ -170,6 +169,7 @@ div#header a { #nav .active a { color:#004a80; + font-weight:bold; } #nav > li + li { @@ -242,7 +242,6 @@ div#header a { padding-left:24px; background-position:0 50%; background-repeat:no-repeat; - font-weight:normal; } #nav .inactive li a:hover { @@ -2061,3 +2060,12 @@ button a:hover { .required { font-weight: bold; } +.truncate { + width: auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +td.indented { + padding-left: 20px; +} diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index aa5e22199d44d6063374b4895b90cb169d9b3575..b0b3e6deacdbf5d64e017cb616992209e8b5cd29 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -33,6 +33,8 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq` ( `answer` text NOT NULL, `keywords` tinytext, `notes` text, + `views` int(10) unsigned NOT NULL default '0', + `score` int(10) NOT NULL default '0', `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`faq_id`),