diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index 156d65da13eb2711d4dfe5c5505926ea83a61d85..fa3432c5aeda55594612550fe2183db435009312 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -238,13 +238,13 @@ h2, .subject { .button, .button:visited { background: #222; + border: none; display: inline-block; font-size: 16px; - padding: 8px 16px 6px 16px; + padding: 4px 16px 4px 16px; max-width: 220px; text-align: center; color: #fff; - font-weight: bold; text-decoration: none; border-radius: 5px; -moz-border-radius: 5px; @@ -401,13 +401,15 @@ body { } .front-page-button { } +#landing_page .welcome { + width: 560px; +} #landing_page #new_ticket { margin-top: 40px; background: url('../images/new_ticket_icon.png') top left no-repeat; } #landing_page #new_ticket, -#landing_page #check_status, -.front-page-button { +#landing_page #check_status { width: 295px; padding-left: 75px; } @@ -415,12 +417,8 @@ body { margin-top: 40px; background: url('../images/check_status_icon.png') top left no-repeat; } -.rtl #landing_page #new_ticket, -.rtl #landing_page #check_status, -.rtl .front-page-button { - padding-left: 0; - padding-right: 75px; - background-position: top right; +#landing_page h1, #landing_page h2, #landing_page h3 { + margin-bottom: 10px; } /* Landing page FAQ not yet implemented. */ #faq { @@ -470,10 +468,7 @@ body { margin: 0; background: url(../images/kb_category_bg.png) bottom left repeat-x; border-bottom: 1px solid #ddd; -} -#kb > li h4 { - padding-bottom: 3px; - margin-bottom: 3px; + display: block; } #kb > li h4 span { color: #666; @@ -489,6 +484,43 @@ body { margin-right: 6px; background: url(../images/kb_large_folder.png) top left no-repeat; } +.featured-category { + margin-top: 10px; + width: 49.7%; + display: inline-block; + box-sizing: border-box; +} +.category-name { + display: inline-block; + font-weight: 400; + font-size: 120%; +} +.featured-category i { + color: rgba(0,174,239, 0.8); + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1); + display: inline-block; + vertical-align: middle; +} +.article-headline { + margin-left: 34px; +} +.rtl .article-headline { + margin-left: 0; + margin-right: 34px; +} +.article-teaser { + font-size: 90%; + line-height: 1.5em; + height: 3em; + overflow: hidden; +} +.article-title { + font-weight: 400; +} +.faq-content .article-title { + font-size: 16pt; + margin-top: 15px; +} #kb-search { padding: 10px 0; overflow: hidden; @@ -935,3 +967,58 @@ img.sign-in-image { .flush-left { text-align: left; } +.sidebar { + margin-bottom: 20px; + margin-left: 20px; +} +#landing_page .sidebar { + width: 220px; +} +.rtl .sidebar { + margin-left: 0; + margin-right: 20px; +} +.sidebar .content { + padding: 10px; border: 1px solid #C8DDFA; background: #F7FBFE; +} + +.sidebar section .header { + font-weight: bold; +} +.sidebar section + section { + margin-top: 15px; +} +.search-form { + padding: 12px 0px 20px 0; +} +.searchbar .search, +.search-form .search { + display: inline-block; + width: 400px; + border-radius: 5px; + border: 1px solid #ccc; + padding: 5px 10px; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); +} + +.searchbar .search { + width: 100%; + box-sizing: border-box; + margin-bottom: 10px; +} +.bleed { + margin: 0 !important; + padding: 0 !important; +} +.row { +} +.span4 { + display: inline-block; + width: 31%; + margin: 0 1%; +} +.span8 { + display: inline-block; + width: 65%; + margin: 0 1%; +} diff --git a/include/class.category.php b/include/class.category.php index 41953b7ec46ad02ee26a247225e0452e7aeff230..a73556e16b0c707b2422cf35715c2d92e75090da 100644 --- a/include/class.category.php +++ b/include/class.category.php @@ -38,6 +38,13 @@ class Category extends VerySimpleModel { function isPublic() { return $this->ispublic; } function getHashtable() { return $this->ht; } + function getTopArticles() { + return $this->faqs + ->filter(Q::not(array('ispublished'=>0))) + ->order_by('-ispublished', '-views') + ->limit(5); + } + /* ------------------> Setter methods <--------------------- */ function setName($name) { $this->name=$name; } function setNotes($notes) { $this->notes=$notes; } @@ -97,7 +104,7 @@ class Category extends VerySimpleModel { function save($refetch=false) { if ($this->dirty) $this->updated = SqlFunction::NOW(); - return parent::save($refetch); + return parent::save($refetch || $this->dirty); } /* ------------------> Static methods <--------------------- */ @@ -117,7 +124,13 @@ class Category extends VerySimpleModel { ))->one(); } - static function create($vars) { + static function getFeatured() { + return self::objects()->filter(array( + 'ispublic'=>2 + )); + } + + static function create($vars=false) { $category = parent::create($vars); $category->created = SqlFunction::NOW(); return $category; diff --git a/include/class.draft.php b/include/class.draft.php index fae8fd8d72389d6c37f2dc2892e564a99f246dec..54d023a99831056b94e04f2a694f46bd919b50db 100644 --- a/include/class.draft.php +++ b/include/class.draft.php @@ -44,7 +44,7 @@ class Draft extends VerySimpleModel { $attrs[] = sprintf('data-draft-object-id="%s"', Format::htmlchars($id)); $criteria['namespace'] .= '.' . $id; } - if ($draft = static::lookup($criteria)) { + if ($draft = static::objects()->filter($criteria)->first()) { $attrs[] = sprintf('data-draft-id="%s"', $draft->getId()); $draft_body = $draft->getBody(); } diff --git a/include/class.faq.php b/include/class.faq.php index c7a6e940d5de96d956461f549ed8e4e661340786..6a709120880dfa03aefc138afd695e0efc829c4d 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -21,6 +21,7 @@ class FAQ extends VerySimpleModel { 'table' => FAQ_TABLE, 'pk' => array('faq_id'), 'ordering' => array('question'), + 'defer' => array('answer'), 'joins' => array( 'category' => array( 'constraint' => array( @@ -49,6 +50,9 @@ class FAQ extends VerySimpleModel { function getAnswerWithImages() { return Format::viewableImages($this->answer, ROOT_PATH.'image.php'); } + function getTeaser() { + return Format::truncate(Format::striptags($this->answer), 150); + } function getSearchableAnswer() { return ThreadBody::fromFormattedText($this->answer, 'html') ->getSearchable(); @@ -71,6 +75,13 @@ class FAQ extends VerySimpleModel { return $ids; } + function getHelpTopicNames() { + $names = array(); + foreach ($this->getHelpTopics() as $topic) + $names[] = $topic->getFullName(); + return $names; + } + function getHelpTopics() { //XXX: change it to obj (when needed)! @@ -105,6 +116,11 @@ class FAQ extends VerySimpleModel { return $this->save(); } + function logView() { + $this->views++; + $this->save(); + } + function printPdf() { global $thisstaff; require_once(INCLUDE_DIR.'mpdf/mpdf.php'); @@ -306,10 +322,20 @@ class FAQ extends VerySimpleModel { } static function countPublishedFAQs() { - return self::objects()->filter(array( - 'category__ispublic' => true, - 'ispublished'=> true - ))->count(); + static $count; + if (!isset($count)) { + $count = self::objects()->filter(array( + 'category__ispublic__gt' => 0, + 'ispublished__gt'=> 0 + ))->count(); + } + return $count; + } + + static function getFeatured() { + return self::objects() + ->filter(array('ispublished__in'=>array(1,2), 'category__ispublic'=>1)) + ->order_by('-ispublished','-views'); } static function findIdByQuestion($question) { @@ -355,7 +381,7 @@ class FAQ extends VerySimpleModel { $this->category = $category; $this->ispublished = !!$vars['ispublished']; $this->notes = Format::sanitize($vars['notes']); - $this->keywords = ''; + $this->keywords = ' '; if (!$this->save()) return false; diff --git a/include/class.i18n.php b/include/class.i18n.php index 317c851cf5480c42789ebac1aeb0228316309fc5..78feac7c0556ec334bc99367b9ad4609bb2ce4cc 100644 --- a/include/class.i18n.php +++ b/include/class.i18n.php @@ -240,7 +240,7 @@ class Internationalization { ); } } - uasort($installed, function($a, $b) { return strcasecmp($a['code'], $b['code']); }); + ksort($installed); return $cache = $installed; } diff --git a/include/client/faq.inc.php b/include/client/faq.inc.php index 29c45e4dca350058d0a6aa368b3f3217fa617353..b43db1431ec4d15d6e5433efd4b8c63b2a5126fe 100644 --- a/include/client/faq.inc.php +++ b/include/client/faq.inc.php @@ -4,29 +4,64 @@ if(!defined('OSTCLIENTINC') || !$faq || !$faq->isPublished()) die('Access Denie $category=$faq->getCategory(); ?> +<div class="row"> +<div class="span8"> + <h1><?php echo __('Frequently Asked Questions');?></h1> <div id="breadcrumbs"> <a href="index.php"><?php echo __('All Categories');?></a> » <a href="faq.php?cid=<?php echo $category->getId(); ?>"><?php echo $category->getName(); ?></a> </div> -<div style="width:700px;padding-top:2px;" class="pull-left"> -<strong style="font-size:16px;"><?php echo $faq->getLocalQuestion() ?></strong> + +<div class="faq-content"> +<div class="article-title flush-left"> +<?php echo $faq->getLocalQuestion() ?> </div> -<div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"></div> -<div class="clear"></div> -<p> +<div class="faded"><?php echo __('Last updated').' '.Format::db_daydatetime($category->getUpdateDate()); ?></div> +<br/> +<div class="thread-body bleed"> <?php echo Format::safe_html($faq->getLocalAnswerWithImages()); ?> -</p> -<p> -<?php -if($faq->getNumAttachments()) { ?> - <div><span class="faded"><b><?php echo __('Attachments');?>:</b></span> <?php echo $faq->getAttachmentsLinks(); ?></div> -<?php -} ?> +</div> +</div> +</div> -<div class="article-meta"><span class="faded"><b><?php echo __('Help Topics');?>:</b></span> - <?php echo ($topics=$faq->getHelpTopics())?implode(', ',$topics):' '; ?> +<div class="span4 pull-right"> +<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> -</p> -<hr> -<div class="faded"> <?php echo __('Last updated').' '.Format::db_daydatetime($category->getUpdateDate()); ?></div> +<div class="content"> +<?php if ($attachments = $faq->getVisibleAttachments()) { ?> +<section> + <strong><?php echo __('Attachments');?>:</strong> +<?php foreach ($attachments as $att) { ?> + <div> + <a href="file.php?h=<?php echo $att['download']; ?>" class="no-pjax"> + <i class="icon-file"></i> + <?php echo Format::htmlchars($att['name']); ?> + </a> + </div> +<?php } ?> +</section> +<?php } ?> + +<?php if ($faq->getHelpTopics()->count()) { ?> +<section> + <strong><?php echo __('Help Topics'); ?></strong> +<?php foreach ($faq->getHelpTopics() as $topic) { ?> + <div><?php echo $topic->getFullName(); ?></div> +<?php } ?> +</section> +<?php } ?> +</div> +</div> +</div> + +</div> + +<?php $faq->logView(); ?> diff --git a/include/client/knowledgebase.inc.php b/include/client/knowledgebase.inc.php index c3ab59ef5098f48963ae85212d14ee1ad9b5a60a..4b35efdc9262bdf95422a4f971ac7df03d3320d8 100644 --- a/include/client/knowledgebase.inc.php +++ b/include/client/knowledgebase.inc.php @@ -6,47 +6,21 @@ if(!defined('OSTCLIENTINC')) die('Access Denied'); <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']); ?>"> - <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) ' - .' WHERE cat.ispublic=1 AND faq.ispublished=1 ' - .' 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']); - } - ?> - </select> + <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> + <div class="sidebar"> <select name="topicId" 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) ' - .' WHERE ht.ispublic=1 ' - .' 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']); +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> @@ -94,28 +68,26 @@ 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 AND faq.ispublished=1) ' - .' WHERE cat.ispublic=1 ' - .' GROUP BY cat.category_id ' - .' HAVING faqs>0 ' - .' ORDER BY cat.name'; - if(($res=db_query($sql)) && db_num_rows($res)) { + $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">'; - while($row=db_fetch_array($res)) { - echo sprintf(' - <li> - <i></i> - <h4><a href="faq.php?cid=%d">%s (%d)</a></h4> - %s - </li>',$row['category_id'], - Format::htmlchars($row['name']),$row['faqs'], - Format::safe_html($row['description'])); - } - echo '</ul>'; + 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'); } diff --git a/include/staff/faq-view.inc.php b/include/staff/faq-view.inc.php index 8a50c363d8c0c3bc6ba33644ce12c3a0deb34276..8cb85661cbd2bd9d607156ff0da2760538d98592 100644 --- a/include/staff/faq-view.inc.php +++ b/include/staff/faq-view.inc.php @@ -11,7 +11,7 @@ $category=$faq->getCategory(); <span class="faded">(<?php echo $category->isPublic()?__('Public'):__('Internal'); ?>)</span> </div> -<div class="pull-right faq-meta"> +<div class="pull-right sidebar faq-meta"> <?php if ($attachments = $faq->getVisibleAttachments()) { ?> <section> <strong><?php echo __('Attachments');?>:</strong> diff --git a/include/upgrader/prereq.inc.php b/include/upgrader/prereq.inc.php index 8cdd98e97e87ed89ac94d4a4ecdfa78bc4c30375..ddb19ee2112ee9dd066d5c22a9b4e0ba793bc3a2 100644 --- a/include/upgrader/prereq.inc.php +++ b/include/upgrader/prereq.inc.php @@ -36,7 +36,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D </form> </div> </div> - <div id="sidebar"> + <div class="sidebar pull-right"> <h3><?php echo __('Upgrade Tips');?></h3> <p>1. <?php echo __('Remember to back up your osTicket database');?></p> <p>2. <?php echo sprintf(__('Refer to %1$s Upgrade Guide %2$s for the latest tips'), '<a href="http://osticket.com/wiki/Upgrade_and_Migration" target="_blank">', '</a>');?></p> diff --git a/index.php b/index.php index 2e526f6999014056b8fc491c0db74dfada677079..97ff2378770c07cf0a2f3a58add652a7bea8c718 100644 --- a/index.php +++ b/index.php @@ -14,51 +14,98 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ require('client.inc.php'); + +require_once INCLUDE_DIR . 'class.page.php'; + $section = 'home'; require(CLIENTINC_DIR.'header.inc.php'); ?> <div id="landing_page"> - <?php + <div class="sidebar pull-right"> + <div class="front-page-button flush-right"> +<p> + <a href="open.php" style="display:block" class="blue button"><?php + echo __('Open a New Ticket');?></a> +</p> + </div> + <div class="content"> +<?php + $faqs = FAQ::getFeatured()->select_related('category')->limit(5); + if ($faqs->all()) { ?> + <section><div class="header"><?php echo __('Featured Questions'); ?></div> +<?php foreach ($faqs as $F) { ?> + <div><a href="<?php echo ROOT_PATH; ?>/kb/faq.php?id=<?php + echo urlencode($F->getId()); + ?>"><?php echo $F->getLocalQuestion(); ?></a></div> +<?php } ?> + </section> +<?php + } + $resources = Page::getActivePages()->filter(array('type'=>'other')); + if ($resources->all()) { ?> + <section><div class="header"><?php echo __('Other Resources'); ?></div> +<?php foreach ($resources as $page) { ?> + <a href="<?php echo ROOT_PATH; ?>pages/<?php echo $page->getNameAsSlug(); + ?>"><?php echo $page->getLocalName(); ?></a> +<?php } ?> + </section> +<?php + } ?> + </div> + </div> +<div class="welcome"> +<?php +if ($cfg && $cfg->isKnowledgebaseEnabled()) { ?> +<div class="search-form"> + <form method="get" action="kb/faq.php"> + <input type="hidden" name="a" value="search"/> + <input type="text" name="q" class="search" placeholder="Search our knowledge base"/> + <button type="submit" class="green button">Search</button> + </form> +</div> +<?php +} if($cfg && ($page = $cfg->getLandingPage())) echo $page->getBodyWithImages(); else echo '<h1>'.__('Welcome to the Support Center').'</h1>'; ?> - <div id="new_ticket" class="pull-left"> - <h3><?php echo __('Open a New Ticket');?></h3> - <br> - <div><?php echo __('Please provide as much detail as possible so we can best assist you. To update a previously submitted ticket, please login.');?></div> - </div> - - <div id="check_status" class="pull-right"> - <h3><?php echo __('Check Ticket Status');?></h3> - <br> - <div><?php echo __('We provide archives and history of all your current and past support requests complete with responses.');?></div> - </div> - - <div class="clear"></div> - <div class="front-page-button pull-left"> - <p> - <a href="open.php" class="green button"><?php echo __('Open a New Ticket');?></a> - </p> - </div> - <div class="front-page-button pull-right"> - <p> - <a href="view.php" class="blue button"><?php echo __('Check Ticket Status');?></a> - </p> - </div> </div> <div class="clear"></div> + +<div> <?php if($cfg && $cfg->isKnowledgebaseEnabled()){ //FIXME: provide ability to feature or select random FAQs ?? ?> -<p><?php echo sprintf( - __('Be sure to browse our %s before opening a ticket'), - sprintf('<a href="kb/index.php">%s</a>', - __('Frequently Asked Questions (FAQs)') - )); ?></p> -</div> +<br/><br/> +<?php +$cats = Category::getFeatured(); +if ($cats->all()) { ?> +<h1>Featured Knowledge Base Articles</h1> +<?php +} + + foreach ($cats as $C) { ?> + <div class="featured-category front-page"> + <i class="icon-folder-open icon-2x"></i> + <div class="category-name"> + <?php echo $C->getName(); ?> + </div> +<?php foreach ($C->getTopArticles() as $F) { ?> + <div class="article-headline"> + <div class="article-title"><a href="<?php echo ROOT_PATH; + ?>kb/faq.php?id=<?php echo $F->getId(); ?>"><?php + echo $F->getQuestion(); ?></a></div> + <div class="article-teaser"><?php echo $F->getTeaser(); ?></div> + </div> +<?php } ?> + </div> <?php -} ?> + } +} +?> +</div> +</div> + <?php require(CLIENTINC_DIR.'footer.inc.php'); ?> diff --git a/scp/css/scp.css b/scp/css/scp.css index 255bcab8af43634e2cb9db56085d00f44a32f4e8..27e0ee3b0ebc755f0a4a8036ed2ae06a791c1858 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -1641,8 +1641,8 @@ time { /* Upgrader */ #upgrader { width: 100%; height: auto; clear: both;} -#upgrader #sidebar { width: 220px; padding: 10px; border: 1px solid #C8DDFA; float: right; background: #F7FBFE; } -#upgrader #sidebar h3 { font-size: 10pt; margin: 0 0 5px 0; padding: 0; text-indent: 32px; background: url('../images/cog.png?1312913866') top left no-repeat; line-height: 24px; color: #2a67ac; } +.sidebar { width: 220px; padding: 10px; border: 1px solid #C8DDFA; float: right; background: #F7FBFE; } +.sidebar h3 { font-size: 10pt; margin: 0 0 5px 0; padding: 0; text-indent: 32px; background: url('../images/cog.png?1312913866') top left no-repeat; line-height: 24px; color: #2a67ac; } #upgrader #main { width: 680px; float: left;} #upgrader #main h1 { margin: 0; padding: 0; font-size: 21pt; font-weight: normal; } @@ -1987,13 +1987,6 @@ table.custom-info td { width: 670px; margin: 0 15px; } -.faq-meta { - border: 1px solid #888; - padding: 10px; - width: 200px; - background-color: #ddd; - background-color: rgba(0, 0, 0, 0.1); -} .faq-meta section + section { margin-top: 15px; }