mirror of
https://github.com/godotengine/godot-question2answer.git
synced 2026-01-01 01:48:37 +03:00
This project lived only on the server without version control. This is now the starting point for the repository.
2397 lines
82 KiB
PHP
2397 lines
82 KiB
PHP
<?php
|
|
/*
|
|
Question2Answer by Gideon Greenspan and contributors
|
|
http://www.question2answer.org/
|
|
|
|
Description: Common functions for creating theme-ready structures from data
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
More about this license: http://www.question2answer.org/license.php
|
|
*/
|
|
|
|
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
|
|
header('Location: ../../');
|
|
exit;
|
|
}
|
|
|
|
define('QA_PAGE_FLAGS_EXTERNAL', 1);
|
|
define('QA_PAGE_FLAGS_NEW_WINDOW', 2);
|
|
|
|
|
|
/**
|
|
* Return textual representation of $seconds
|
|
* @param $seconds
|
|
* @return mixed|string
|
|
*/
|
|
function qa_time_to_string($seconds)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$seconds = max($seconds, 1);
|
|
|
|
$scales = array(
|
|
31557600 => array('main/1_year', 'main/x_years'),
|
|
2629800 => array('main/1_month', 'main/x_months'),
|
|
604800 => array('main/1_week', 'main/x_weeks'),
|
|
86400 => array('main/1_day', 'main/x_days'),
|
|
3600 => array('main/1_hour', 'main/x_hours'),
|
|
60 => array('main/1_minute', 'main/x_minutes'),
|
|
1 => array('main/1_second', 'main/x_seconds'),
|
|
);
|
|
|
|
foreach ($scales as $scale => $phrases) {
|
|
if ($seconds >= $scale) {
|
|
$count = floor($seconds / $scale);
|
|
|
|
if ($count == 1)
|
|
$string = qa_lang($phrases[0]);
|
|
else
|
|
$string = qa_lang_sub($phrases[1], $count);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $string;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if $post is by user $userid, or if post is anonymous and $userid not specified, then
|
|
* check if $post is by the anonymous user identified by $cookieid
|
|
* @param $post
|
|
* @param $userid
|
|
* @param $cookieid
|
|
* @return bool
|
|
*/
|
|
function qa_post_is_by_user($post, $userid, $cookieid)
|
|
{
|
|
// In theory we should only test against NULL here, i.e. use isset($post['userid'])
|
|
// but the risk of doing so is so high (if a bug creeps in that allows userid=0)
|
|
// that I'm doing a tougher test. This will break under a zero user or cookie id.
|
|
|
|
if (@$post['userid'] || $userid)
|
|
return @$post['userid'] == $userid;
|
|
elseif (@$post['cookieid'])
|
|
return strcmp($post['cookieid'], $cookieid) == 0;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return array which maps the 'userid' and/or 'lastuserid' of each user to its HTML representation.
|
|
* For internal user management, corresponding 'handle' and/or 'lasthandle' are required in each element.
|
|
*
|
|
* @param array $useridhandles User IDs or usernames.
|
|
* @param bool $microdata Whether to include microdata.
|
|
* @return array The HTML.
|
|
*/
|
|
function qa_userids_handles_html($useridhandles, $microdata = false)
|
|
{
|
|
require_once QA_INCLUDE_DIR . 'app/users.php';
|
|
|
|
if (QA_FINAL_EXTERNAL_USERS) {
|
|
$keyuserids = array();
|
|
|
|
foreach ($useridhandles as $useridhandle) {
|
|
if (isset($useridhandle['userid']))
|
|
$keyuserids[$useridhandle['userid']] = true;
|
|
|
|
if (isset($useridhandle['lastuserid']))
|
|
$keyuserids[$useridhandle['lastuserid']] = true;
|
|
}
|
|
|
|
if (count($keyuserids))
|
|
return qa_get_users_html(array_keys($keyuserids), true, qa_path_to_root(), $microdata);
|
|
|
|
return array();
|
|
} else {
|
|
$usershtml = array();
|
|
$favoritemap = qa_get_favorite_non_qs_map();
|
|
|
|
foreach ($useridhandles as $useridhandle) {
|
|
// only add each user to the array once
|
|
$uid = isset($useridhandle['userid']) ? $useridhandle['userid'] : null;
|
|
if ($uid && !isset($usershtml[$uid])) {
|
|
$usershtml[$uid] = qa_get_one_user_html($useridhandle['handle'], $microdata, @$favoritemap['user'][$uid]);
|
|
}
|
|
|
|
$luid = isset($useridhandle['lastuserid']) ? $useridhandle['lastuserid'] : null;
|
|
if ($luid && !isset($usershtml[$luid])) {
|
|
$usershtml[$luid] = qa_get_one_user_html($useridhandle['lasthandle'], $microdata, @$favoritemap['user'][$luid]);
|
|
}
|
|
}
|
|
|
|
return $usershtml;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get an array listing all of the logged in user's favorite items, except their favorited questions (these are excluded because
|
|
* users tend to favorite many more questions than other things.) The top-level array can contain three keys - 'user' for favorited
|
|
* users, 'tag' for tags, 'category' for categories. The next level down has the identifier for each favorited entity in the *key*
|
|
* of the array, and true for its value. If no user is logged in the empty array is returned. The result is cached for future calls.
|
|
*/
|
|
function qa_get_favorite_non_qs_map()
|
|
{
|
|
global $qa_favorite_non_qs_map;
|
|
|
|
if (!isset($qa_favorite_non_qs_map)) {
|
|
$qa_favorite_non_qs_map = array();
|
|
$loginuserid = qa_get_logged_in_userid();
|
|
|
|
if (isset($loginuserid)) {
|
|
require_once QA_INCLUDE_DIR . 'db/selects.php';
|
|
require_once QA_INCLUDE_DIR . 'util/string.php';
|
|
|
|
$favoritenonqs = qa_db_get_pending_result('favoritenonqs', qa_db_user_favorite_non_qs_selectspec($loginuserid));
|
|
|
|
foreach ($favoritenonqs as $favorite) {
|
|
switch ($favorite['type']) {
|
|
case QA_ENTITY_USER:
|
|
$qa_favorite_non_qs_map['user'][$favorite['userid']] = true;
|
|
break;
|
|
|
|
case QA_ENTITY_TAG:
|
|
$qa_favorite_non_qs_map['tag'][qa_strtolower($favorite['tags'])] = true;
|
|
break;
|
|
|
|
case QA_ENTITY_CATEGORY:
|
|
$qa_favorite_non_qs_map['category'][$favorite['categorybackpath']] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $qa_favorite_non_qs_map;
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert textual tag to HTML representation, linked to its tag page.
|
|
*
|
|
* @param string $tag The tag.
|
|
* @param bool $microdata Whether to include microdata.
|
|
* @param bool $favorited Show the tag as favorited.
|
|
* @return string The tag HTML.
|
|
*/
|
|
function qa_tag_html($tag, $microdata = false, $favorited = false)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$url = qa_path_html('tag/' . $tag);
|
|
$attrs = $microdata ? ' rel="tag"' : '';
|
|
$class = $favorited ? ' qa-tag-favorited' : '';
|
|
|
|
return '<a href="' . $url . '"' . $attrs . ' class="qa-tag-link' . $class . '">' . qa_html($tag) . '</a>';
|
|
}
|
|
|
|
|
|
/**
|
|
* Given $navcategories retrieved for $categoryid from the database (using qa_db_category_nav_selectspec(...)),
|
|
* return an array of elements from $navcategories for the hierarchy down to $categoryid.
|
|
* @param $navcategories
|
|
* @param $categoryid
|
|
* @return array
|
|
*/
|
|
function qa_category_path($navcategories, $categoryid)
|
|
{
|
|
$upcategories = array();
|
|
|
|
for ($upcategory = @$navcategories[$categoryid]; isset($upcategory); $upcategory = @$navcategories[$upcategory['parentid']])
|
|
$upcategories[$upcategory['categoryid']] = $upcategory;
|
|
|
|
return array_reverse($upcategories, true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Given $navcategories retrieved for $categoryid from the database (using qa_db_category_nav_selectspec(...)),
|
|
* return some HTML that shows the category hierarchy down to $categoryid.
|
|
* @param $navcategories
|
|
* @param $categoryid
|
|
* @return string
|
|
*/
|
|
function qa_category_path_html($navcategories, $categoryid)
|
|
{
|
|
$categories = qa_category_path($navcategories, $categoryid);
|
|
|
|
$html = '';
|
|
foreach ($categories as $category)
|
|
$html .= (strlen($html) ? ' / ' : '') . qa_html($category['title']);
|
|
|
|
return $html;
|
|
}
|
|
|
|
|
|
/**
|
|
* Given $navcategories retrieved for $categoryid from the database (using qa_db_category_nav_selectspec(...)),
|
|
* return a Q2A request string that represents the category hierarchy down to $categoryid.
|
|
* @param $navcategories
|
|
* @param $categoryid
|
|
* @return string
|
|
*/
|
|
function qa_category_path_request($navcategories, $categoryid)
|
|
{
|
|
$categories = qa_category_path($navcategories, $categoryid);
|
|
|
|
$request = '';
|
|
foreach ($categories as $category)
|
|
$request .= (strlen($request) ? '/' : '') . $category['tags'];
|
|
|
|
return $request;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return HTML to use for $ip address, which links to appropriate page with $anchorhtml
|
|
* @param $ip
|
|
* @param null $anchorhtml
|
|
* @return mixed|string
|
|
*/
|
|
function qa_ip_anchor_html($ip, $anchorhtml = null)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
if (!strlen($anchorhtml))
|
|
$anchorhtml = qa_html($ip);
|
|
|
|
return '<a href="' . qa_path_html('ip/' . $ip) . '" title="' . qa_lang_html_sub('main/ip_address_x', qa_html($ip)) . '" class="qa-ip-link">' . $anchorhtml . '</a>';
|
|
}
|
|
|
|
|
|
/**
|
|
* Given $post retrieved from database, return array of mostly HTML to be passed to theme layer.
|
|
* $userid and $cookieid refer to the user *viewing* the page.
|
|
* $usershtml is an array of [user id] => [HTML representation of user] built ahead of time.
|
|
* $dummy is a placeholder (used to be $categories parameter but that's no longer needed)
|
|
* $options is an array which sets what is displayed (see qa_post_html_defaults() in /qa-include/app/options.php)
|
|
* If something is missing from $post (e.g. ['content']), correponding HTML also omitted.
|
|
* @param $post
|
|
* @param $userid
|
|
* @param $cookieid
|
|
* @param $usershtml
|
|
* @param $dummy
|
|
* @param array $options
|
|
* @return array
|
|
*/
|
|
function qa_post_html_fields($post, $userid, $cookieid, $usershtml, $dummy, $options = array())
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
require_once QA_INCLUDE_DIR . 'app/updates.php';
|
|
require_once QA_INCLUDE_DIR . 'app/posts.php';
|
|
|
|
if (isset($options['blockwordspreg']))
|
|
require_once QA_INCLUDE_DIR . 'util/string.php';
|
|
|
|
$fields = array('raw' => $post);
|
|
|
|
// Useful stuff used throughout function
|
|
|
|
$postid = $post['postid'];
|
|
$isquestion = $post['basetype'] == 'Q';
|
|
$isanswer = $post['basetype'] == 'A';
|
|
$iscomment = $post['basetype'] == 'C';
|
|
$isbyuser = qa_post_is_by_user($post, $userid, $cookieid);
|
|
$anchor = urlencode(qa_anchor($post['basetype'], $postid));
|
|
$elementid = isset($options['elementid']) ? $options['elementid'] : $anchor;
|
|
$microdata = qa_opt('use_microdata') && !empty($options['contentview']);
|
|
$isselected = @$options['isselected'];
|
|
$favoritedview = @$options['favoritedview'];
|
|
$favoritemap = $favoritedview ? qa_get_favorite_non_qs_map() : array();
|
|
|
|
// High level information
|
|
|
|
$fields['hidden'] = isset($post['hidden']) ? $post['hidden'] : null;
|
|
$fields['queued'] = isset($post['queued']) ? $post['queued'] : null;
|
|
$fields['tags'] = 'id="' . qa_html($elementid) . '"';
|
|
|
|
$fields['classes'] = ($isquestion && $favoritedview && @$post['userfavoriteq']) ? 'qa-q-favorited' : '';
|
|
if ($isquestion && qa_post_is_closed($post)) {
|
|
$fields['classes'] = ltrim($fields['classes'] . ' qa-q-closed');
|
|
}
|
|
|
|
if ($microdata) {
|
|
if ($isanswer) {
|
|
$fields['tags'] .= ' itemprop="suggestedAnswer' . ($isselected ? ' acceptedAnswer' : '') . '" itemscope itemtype="https://schema.org/Answer"';
|
|
}
|
|
if ($iscomment) {
|
|
$fields['tags'] .= ' itemscope itemtype="https://schema.org/Comment"';
|
|
}
|
|
}
|
|
|
|
// Question-specific stuff (title, URL, tags, answer count, category)
|
|
|
|
if ($isquestion) {
|
|
if (isset($post['title'])) {
|
|
$fields['url'] = qa_q_path_html($postid, $post['title']);
|
|
|
|
if (isset($options['blockwordspreg']))
|
|
$post['title'] = qa_block_words_replace($post['title'], $options['blockwordspreg']);
|
|
|
|
$fields['title'] = qa_html($post['title']);
|
|
if ($microdata) {
|
|
$fields['title'] = '<span itemprop="name">' . $fields['title'] . '</span>';
|
|
}
|
|
|
|
/*if (isset($post['score'])) // useful for setting match thresholds
|
|
$fields['title'].=' <small>('.$post['score'].')</small>';*/
|
|
}
|
|
|
|
if (@$options['tagsview'] && isset($post['tags'])) {
|
|
$fields['q_tags'] = array();
|
|
|
|
$tags = qa_tagstring_to_tags($post['tags']);
|
|
foreach ($tags as $tag) {
|
|
if (isset($options['blockwordspreg']) && count(qa_block_words_match_all($tag, $options['blockwordspreg']))) // skip censored tags
|
|
continue;
|
|
|
|
$fields['q_tags'][] = qa_tag_html($tag, $microdata, @$favoritemap['tag'][qa_strtolower($tag)]);
|
|
}
|
|
}
|
|
|
|
if (@$options['answersview'] && isset($post['acount'])) {
|
|
$fields['answers_raw'] = $post['acount'];
|
|
|
|
$fields['answers'] = ($post['acount'] == 1) ? qa_lang_html_sub_split('main/1_answer', '1', '1')
|
|
: qa_lang_html_sub_split('main/x_answers', qa_format_number($post['acount'], 0, true));
|
|
|
|
$fields['answer_selected'] = isset($post['selchildid']);
|
|
}
|
|
|
|
if (@$options['viewsview'] && isset($post['views'])) {
|
|
$fields['views_raw'] = $post['views'];
|
|
|
|
$fields['views'] = ($post['views'] == 1) ? qa_lang_html_sub_split('main/1_view', '1', '1') :
|
|
qa_lang_html_sub_split('main/x_views', qa_format_number($post['views'], 0, true));
|
|
}
|
|
|
|
if (@$options['categoryview'] && isset($post['categoryname']) && isset($post['categorybackpath'])) {
|
|
$favoriteclass = '';
|
|
|
|
if (isset($favoritemap['category']) && !empty($favoritemap['category'])) {
|
|
if (isset($favoritemap['category'][$post['categorybackpath']])) {
|
|
$favoriteclass = ' qa-cat-favorited';
|
|
} else {
|
|
foreach ($favoritemap['category'] as $categorybackpath => $dummy) {
|
|
if (substr('/' . $post['categorybackpath'], -strlen($categorybackpath)) == $categorybackpath)
|
|
$favoriteclass = ' qa-cat-parent-favorited';
|
|
}
|
|
}
|
|
}
|
|
|
|
$fields['where'] = qa_lang_html_sub_split('main/in_category_x',
|
|
'<a href="' . qa_path_html(@$options['categorypathprefix'] . implode('/', array_reverse(explode('/', $post['categorybackpath'])))) .
|
|
'" class="qa-category-link' . $favoriteclass . '">' . qa_html($post['categoryname']) . '</a>');
|
|
}
|
|
}
|
|
|
|
// Answer-specific stuff (selection)
|
|
|
|
if ($isanswer) {
|
|
$fields['selected'] = $isselected;
|
|
|
|
if ($isselected)
|
|
$fields['select_text'] = qa_lang_html('question/select_text');
|
|
}
|
|
|
|
// Post content
|
|
|
|
if (@$options['contentview'] && isset($post['content'])) {
|
|
$viewer = qa_load_viewer($post['content'], $post['format']);
|
|
|
|
$fields['content'] = $viewer->get_html($post['content'], $post['format'], array(
|
|
'blockwordspreg' => @$options['blockwordspreg'],
|
|
'showurllinks' => @$options['showurllinks'],
|
|
'linksnewwindow' => @$options['linksnewwindow'],
|
|
));
|
|
|
|
if ($microdata) {
|
|
$fields['content'] = '<div itemprop="text">' . $fields['content'] . '</div>';
|
|
}
|
|
|
|
// this is for backwards compatibility with any existing links using the old style of anchor
|
|
// that contained the post id only (changed to be valid under W3C specifications)
|
|
$fields['content'] = '<a name="' . qa_html($postid) . '"></a>' . $fields['content'];
|
|
}
|
|
|
|
// Voting stuff
|
|
|
|
if (@$options['voteview']) {
|
|
$voteview = $options['voteview'];
|
|
|
|
// Calculate raw values and pass through
|
|
|
|
if (@$options['ovoteview'] && isset($post['opostid'])) {
|
|
$upvotes = (int)@$post['oupvotes'];
|
|
$downvotes = (int)@$post['odownvotes'];
|
|
$fields['vote_opostid'] = true; // for voters/flaggers layer
|
|
} else {
|
|
$upvotes = (int)@$post['upvotes'];
|
|
$downvotes = (int)@$post['downvotes'];
|
|
}
|
|
|
|
$netvotes = $upvotes - $downvotes;
|
|
|
|
$fields['upvotes_raw'] = $upvotes;
|
|
$fields['downvotes_raw'] = $downvotes;
|
|
$fields['netvotes_raw'] = $netvotes;
|
|
|
|
// Create HTML versions...
|
|
|
|
$upvoteshtml = qa_html(qa_format_number($upvotes, 0, true));
|
|
$downvoteshtml = qa_html(qa_format_number($downvotes, 0, true));
|
|
|
|
if ($netvotes >= 1)
|
|
$netvotesPrefix = '+';
|
|
elseif ($netvotes <= -1)
|
|
$netvotesPrefix = '–';
|
|
else
|
|
$netvotesPrefix = '';
|
|
|
|
$netvotes = abs($netvotes);
|
|
$netvoteshtml = $netvotesPrefix . qa_html(qa_format_number($netvotes, 0, true));
|
|
|
|
// Pass information on vote viewing
|
|
|
|
// $voteview will be one of:
|
|
// updown, updown-disabled-page, updown-disabled-level, updown-uponly-level, updown-disabled-approve, updown-uponly-approve
|
|
// net, net-disabled-page, net-disabled-level, net-uponly-level, net-disabled-approve, net-uponly-approve
|
|
|
|
$fields['vote_view'] = (substr($voteview, 0, 6) == 'updown') ? 'updown' : 'net';
|
|
|
|
$fields['vote_on_page'] = strpos($voteview, '-disabled-page') ? 'disabled' : 'enabled';
|
|
|
|
if ($iscomment) {
|
|
// for comments just show number, no additional text
|
|
$fields['upvotes_view'] = array('prefix' => '', 'data' => $upvoteshtml, 'suffix' => '');
|
|
$fields['downvotes_view'] = array('prefix' => '', 'data' => $downvoteshtml, 'suffix' => '');
|
|
$fields['netvotes_view'] = array('prefix' => '', 'data' => $netvoteshtml, 'suffix' => '');
|
|
} else {
|
|
$fields['upvotes_view'] = $upvotes == 1
|
|
? qa_lang_html_sub_split('main/1_liked', $upvoteshtml, '1')
|
|
: qa_lang_html_sub_split('main/x_liked', $upvoteshtml);
|
|
$fields['downvotes_view'] = $downvotes == 1
|
|
? qa_lang_html_sub_split('main/1_disliked', $downvoteshtml, '1')
|
|
: qa_lang_html_sub_split('main/x_disliked', $downvoteshtml);
|
|
$fields['netvotes_view'] = $netvotes == 1
|
|
? qa_lang_html_sub_split('main/1_vote', $netvoteshtml, '1')
|
|
: qa_lang_html_sub_split('main/x_votes', $netvoteshtml);
|
|
}
|
|
|
|
// schema.org microdata - vote display might be formatted (e.g. '2k') so we use meta tag for true count
|
|
if ($microdata) {
|
|
$fields['netvotes_view']['suffix'] .= ' <meta itemprop="upvoteCount" content="' . qa_html($upvotes - $downvotes) . '"/>';
|
|
$fields['upvotes_view']['suffix'] .= ' <meta itemprop="upvoteCount" content="' . qa_html($upvotes) . '"/>';
|
|
}
|
|
|
|
// Voting buttons
|
|
|
|
$fields['vote_tags'] = 'id="voting_' . qa_html($postid) . '"';
|
|
$onclick = 'onclick="return qa_vote_click(this);"';
|
|
|
|
if ($fields['hidden']) {
|
|
$fields['vote_state'] = 'disabled';
|
|
$fields['vote_up_tags'] = 'title="' . qa_lang_html('main/vote_disabled_hidden_post') . '"';
|
|
$fields['vote_down_tags'] = $fields['vote_up_tags'];
|
|
|
|
} elseif ($fields['queued']) {
|
|
$fields['vote_state'] = 'disabled';
|
|
$fields['vote_up_tags'] = 'title="' . qa_lang_html('main/vote_disabled_queued') . '"';
|
|
$fields['vote_down_tags'] = $fields['vote_up_tags'];
|
|
|
|
} elseif ($isbyuser) {
|
|
$fields['vote_state'] = 'disabled';
|
|
$fields['vote_up_tags'] = 'title="' . qa_lang_html('main/vote_disabled_my_post') . '"';
|
|
$fields['vote_down_tags'] = $fields['vote_up_tags'];
|
|
|
|
} elseif (strpos($voteview, '-disabled-')) {
|
|
$fields['vote_state'] = (@$post['uservote'] > 0) ? 'voted_up_disabled' : ((@$post['uservote'] < 0) ? 'voted_down_disabled' : 'disabled');
|
|
|
|
if (strpos($voteview, '-disabled-page'))
|
|
$fields['vote_up_tags'] = 'title="' . qa_lang_html('main/vote_disabled_q_page_only') . '"';
|
|
elseif (strpos($voteview, '-disabled-approve'))
|
|
$fields['vote_up_tags'] = 'title="' . qa_lang_html('main/vote_disabled_approve') . '"';
|
|
else
|
|
$fields['vote_up_tags'] = 'title="' . qa_lang_html('main/vote_disabled_level') . '"';
|
|
|
|
$fields['vote_down_tags'] = $fields['vote_up_tags'];
|
|
|
|
} elseif (@$post['uservote'] > 0) {
|
|
$fields['vote_state'] = 'voted_up';
|
|
$fields['vote_up_tags'] = 'title="' . qa_lang_html('main/voted_up_popup') . '" name="' . qa_html('vote_' . $postid . '_0_' . $elementid) . '" ' . $onclick;
|
|
$fields['vote_down_tags'] = ' ';
|
|
|
|
} elseif (@$post['uservote'] < 0) {
|
|
$fields['vote_state'] = 'voted_down';
|
|
$fields['vote_up_tags'] = ' ';
|
|
$fields['vote_down_tags'] = 'title="' . qa_lang_html('main/voted_down_popup') . '" name="' . qa_html('vote_' . $postid . '_0_' . $elementid) . '" ' . $onclick;
|
|
|
|
} else {
|
|
$fields['vote_up_tags'] = 'title="' . qa_lang_html('main/vote_up_popup') . '" name="' . qa_html('vote_' . $postid . '_1_' . $elementid) . '" ' . $onclick;
|
|
|
|
if (strpos($voteview, '-uponly-level')) {
|
|
$fields['vote_state'] = 'up_only';
|
|
$fields['vote_down_tags'] = 'title="' . qa_lang_html('main/vote_disabled_down') . '"';
|
|
|
|
} elseif (strpos($voteview, '-uponly-approve')) {
|
|
$fields['vote_state'] = 'up_only';
|
|
$fields['vote_down_tags'] = 'title="' . qa_lang_html('main/vote_disabled_down_approve') . '"';
|
|
|
|
} else {
|
|
$fields['vote_state'] = 'enabled';
|
|
$fields['vote_down_tags'] = 'title="' . qa_lang_html('main/vote_down_popup') . '" name="' . qa_html('vote_' . $postid . '_-1_' . $elementid) . '" ' . $onclick;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flag count
|
|
|
|
if (@$options['flagsview'] && @$post['flagcount']) {
|
|
$fields['flags'] = ($post['flagcount'] == 1) ? qa_lang_html_sub_split('main/1_flag', '1', '1')
|
|
: qa_lang_html_sub_split('main/x_flags', $post['flagcount']);
|
|
}
|
|
|
|
// Created when and by whom
|
|
|
|
$fields['meta_order'] = qa_lang_html('main/meta_order'); // sets ordering of meta elements which can be language-specific
|
|
|
|
if (@$options['whatview']) {
|
|
$fields['what'] = qa_lang_html($isquestion ? 'main/asked' : ($isanswer ? 'main/answered' : 'main/commented'));
|
|
|
|
if (@$options['whatlink'] && strlen(@$options['q_request'])) {
|
|
$fields['what_url'] = $post['basetype'] == 'Q'
|
|
? qa_path_html($options['q_request'])
|
|
: qa_path_html($options['q_request'], array('show' => $postid), null, null, qa_anchor($post['basetype'], $postid));
|
|
if ($microdata) {
|
|
$fields['what_url_tags'] = ' itemprop="url"';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($post['created']) && @$options['whenview']) {
|
|
$fields['when'] = qa_when_to_html($post['created'], @$options['fulldatedays']);
|
|
|
|
if ($microdata) {
|
|
$gmdate = gmdate('Y-m-d\TH:i:sO', $post['created']);
|
|
$fields['when']['data'] = '<time itemprop="dateCreated" datetime="' . $gmdate . '" title="' . $gmdate . '">' . $fields['when']['data'] . '</time>';
|
|
}
|
|
}
|
|
|
|
if (@$options['whoview']) {
|
|
$fields['who'] = qa_who_to_html($isbyuser, @$post['userid'], $usershtml, @$options['ipview'] ? @inet_ntop(@$post['createip']) : null, $microdata, $post['name']);
|
|
|
|
if (isset($post['points'])) {
|
|
if (@$options['pointsview'])
|
|
$fields['who']['points'] = ($post['points'] == 1) ? qa_lang_html_sub_split('main/1_point', '1', '1')
|
|
: qa_lang_html_sub_split('main/x_points', qa_format_number($post['points'], 0, true));
|
|
|
|
if (isset($options['pointstitle']))
|
|
$fields['who']['title'] = qa_get_points_title_html($post['points'], $options['pointstitle']);
|
|
}
|
|
|
|
if (isset($post['level']))
|
|
$fields['who']['level'] = qa_html(qa_user_level_string($post['level']));
|
|
}
|
|
|
|
if (@$options['avatarsize'] > 0) {
|
|
if (QA_FINAL_EXTERNAL_USERS)
|
|
$fields['avatar'] = qa_get_external_avatar_html($post['userid'], $options['avatarsize'], false);
|
|
else
|
|
$fields['avatar'] = qa_get_user_avatar_html(@$post['flags'], @$post['email'], @$post['handle'],
|
|
@$post['avatarblobid'], @$post['avatarwidth'], @$post['avatarheight'], $options['avatarsize']);
|
|
}
|
|
|
|
// Updated when and by whom
|
|
|
|
if (@$options['updateview'] && isset($post['updated']) &&
|
|
($post['updatetype'] != QA_UPDATE_SELECTED || $isselected) && // only show selected change if it's still selected
|
|
( // otherwise check if one of these conditions is fulfilled...
|
|
(!isset($post['created'])) || // ... we didn't show the created time (should never happen in practice)
|
|
($post['hidden'] && ($post['updatetype'] == QA_UPDATE_VISIBLE)) || // ... the post was hidden as the last action
|
|
(qa_post_is_closed($post) && $post['updatetype'] == QA_UPDATE_CLOSED) || // ... the post was closed as the last action
|
|
(abs($post['updated'] - $post['created']) > 300) || // ... or over 5 minutes passed between create and update times
|
|
($post['lastuserid'] != $post['userid']) // ... or it was updated by a different user
|
|
)
|
|
) {
|
|
switch ($post['updatetype']) {
|
|
case QA_UPDATE_TYPE:
|
|
case QA_UPDATE_PARENT:
|
|
$langstring = 'main/moved';
|
|
break;
|
|
|
|
case QA_UPDATE_CATEGORY:
|
|
$langstring = 'main/recategorized';
|
|
break;
|
|
|
|
case QA_UPDATE_VISIBLE:
|
|
$langstring = $post['hidden'] ? 'main/hidden' : 'main/reshown';
|
|
break;
|
|
|
|
case QA_UPDATE_CLOSED:
|
|
$langstring = qa_post_is_closed($post) ? 'main/closed' : 'main/reopened';
|
|
break;
|
|
|
|
case QA_UPDATE_TAGS:
|
|
$langstring = 'main/retagged';
|
|
break;
|
|
|
|
case QA_UPDATE_SELECTED:
|
|
$langstring = 'main/selected';
|
|
break;
|
|
|
|
default:
|
|
$langstring = 'main/edited';
|
|
break;
|
|
}
|
|
|
|
$fields['what_2'] = qa_lang_html($langstring);
|
|
|
|
if (@$options['whenview']) {
|
|
$fields['when_2'] = qa_when_to_html($post['updated'], @$options['fulldatedays']);
|
|
|
|
if ($microdata) {
|
|
$gmdate = gmdate('Y-m-d\TH:i:sO', $post['updated']);
|
|
$fields['when_2']['data'] = '<time itemprop="dateModified" datetime="' . $gmdate . '" title="' . $gmdate . '">' . $fields['when_2']['data'] . '</time>';
|
|
}
|
|
}
|
|
|
|
if (isset($post['lastuserid']) && @$options['whoview'])
|
|
$fields['who_2'] = qa_who_to_html(isset($userid) && ($post['lastuserid'] == $userid), $post['lastuserid'], $usershtml, @$options['ipview'] ? @inet_ntop($post['lastip']) : null, false);
|
|
}
|
|
|
|
|
|
// That's it!
|
|
|
|
return $fields;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate array of mostly HTML representing a message, to be passed to theme layer.
|
|
*
|
|
* @param array $message The message object (as retrieved from database).
|
|
* @param array $options Viewing options (see qa_message_html_defaults() in /qa-include/app/options.php).
|
|
* @return array The HTML.
|
|
*/
|
|
function qa_message_html_fields($message, $options = array())
|
|
{
|
|
require_once QA_INCLUDE_DIR . 'app/users.php';
|
|
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$fields = array('raw' => $message);
|
|
$fields['tags'] = 'id="m' . qa_html($message['messageid']) . '"';
|
|
|
|
// message content
|
|
$viewer = qa_load_viewer($message['content'], $message['format']);
|
|
|
|
$fields['content'] = $viewer->get_html($message['content'], $message['format'], array(
|
|
'blockwordspreg' => @$options['blockwordspreg'],
|
|
'showurllinks' => @$options['showurllinks'],
|
|
'linksnewwindow' => @$options['linksnewwindow'],
|
|
));
|
|
|
|
// set ordering of meta elements which can be language-specific
|
|
$fields['meta_order'] = qa_lang_html('main/meta_order');
|
|
|
|
$fields['what'] = qa_lang_html('main/written');
|
|
|
|
// when it was written
|
|
if (@$options['whenview'])
|
|
$fields['when'] = qa_when_to_html($message['created'], @$options['fulldatedays']);
|
|
|
|
// who wrote it, and their avatar
|
|
if (@$options['towhomview']) {
|
|
// for sent private messages page (i.e. show who message was sent to)
|
|
$fields['who'] = qa_lang_html_sub_split('main/to_x', qa_get_one_user_html($message['tohandle'], false));
|
|
$fields['avatar'] = qa_get_user_avatar_html(@$message['toflags'], @$message['toemail'], @$message['tohandle'],
|
|
@$message['toavatarblobid'], @$message['toavatarwidth'], @$message['toavatarheight'], $options['avatarsize']);
|
|
} else {
|
|
// for everything else (received private messages, wall messages)
|
|
if (@$options['whoview']) {
|
|
$fields['who'] = qa_lang_html_sub_split('main/by_x', qa_get_one_user_html($message['fromhandle'], false));
|
|
}
|
|
if (@$options['avatarsize'] > 0) {
|
|
$fields['avatar'] = qa_get_user_avatar_html(@$message['fromflags'], @$message['fromemail'], @$message['fromhandle'],
|
|
@$message['fromavatarblobid'], @$message['fromavatarwidth'], @$message['fromavatarheight'], $options['avatarsize']);
|
|
}
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate array of split HTML (prefix, data, suffix) to represent author of post.
|
|
*
|
|
* @param bool $isbyuser True if the current user made the post.
|
|
* @param int $postuserid The post user's ID.
|
|
* @param array $usershtml Array of HTML representing usernames.
|
|
* @param string $ip The post user's IP.
|
|
* @param bool|string $microdata Whether to include microdata.
|
|
* @param string $name The author's username.
|
|
* @return array The HTML.
|
|
*/
|
|
function qa_who_to_html($isbyuser, $postuserid, $usershtml, $ip = null, $microdata = false, $name = null)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
if (isset($postuserid) && isset($usershtml[$postuserid])) {
|
|
$whohtml = $usershtml[$postuserid];
|
|
} else {
|
|
if (strlen($name))
|
|
$whohtml = qa_html($name);
|
|
elseif ($isbyuser)
|
|
$whohtml = qa_lang_html('main/me');
|
|
else
|
|
$whohtml = qa_lang_html('main/anonymous');
|
|
|
|
if ($microdata) {
|
|
// duplicate HTML from qa_get_one_user_html()
|
|
$whohtml = '<span itemprop="author" itemscope itemtype="https://schema.org/Person"><span itemprop="name">' . $whohtml . '</span></span>';
|
|
}
|
|
|
|
if (isset($ip))
|
|
$whohtml = qa_ip_anchor_html($ip, $whohtml);
|
|
}
|
|
|
|
return qa_lang_html_sub_split('main/by_x', $whohtml);
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate array of split HTML (prefix, data, suffix) to represent a timestamp, optionally with the full date.
|
|
*
|
|
* @param int $timestamp Unix timestamp.
|
|
* @param int $fulldatedays Number of days after which to show the full date.
|
|
* @return array The HTML.
|
|
*/
|
|
function qa_when_to_html($timestamp, $fulldatedays)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$interval = qa_opt('db_time') - $timestamp;
|
|
|
|
if ($interval < 0 || (isset($fulldatedays) && $interval > 86400 * $fulldatedays)) {
|
|
// full style date
|
|
$stampyear = date('Y', $timestamp);
|
|
$thisyear = date('Y', qa_opt('db_time'));
|
|
|
|
$dateFormat = qa_lang($stampyear == $thisyear ? 'main/date_format_this_year' : 'main/date_format_other_years');
|
|
$replaceData = array(
|
|
'^day' => date(qa_lang('main/date_day_min_digits') == 2 ? 'd' : 'j', $timestamp),
|
|
'^month' => qa_lang('main/date_month_' . date('n', $timestamp)),
|
|
'^year' => date(qa_lang('main/date_year_digits') == 2 ? 'y' : 'Y', $timestamp),
|
|
);
|
|
|
|
return array(
|
|
'data' => qa_html(strtr($dateFormat, $replaceData)),
|
|
);
|
|
|
|
} else {
|
|
// ago-style date
|
|
return qa_lang_html_sub_split('main/x_ago', qa_html(qa_time_to_string($interval)));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Return array of mostly HTML to be passed to theme layer, to *link* to an answer, comment or edit on
|
|
* $question, as retrieved from database, with fields prefixed 'o' for the answer, comment or edit.
|
|
* $userid, $cookieid, $usershtml, $options are passed through to qa_post_html_fields(). If $question['opersonal']
|
|
* is set and true then the item is displayed with its personal relevance to the user (for user updates page).
|
|
* @param $question
|
|
* @param $userid
|
|
* @param $cookieid
|
|
* @param $usershtml
|
|
* @param $dummy
|
|
* @param $options
|
|
* @return array
|
|
*/
|
|
function qa_other_to_q_html_fields($question, $userid, $cookieid, $usershtml, $dummy, $options)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
require_once QA_INCLUDE_DIR . 'app/updates.php';
|
|
|
|
$fields = qa_post_html_fields($question, $userid, $cookieid, $usershtml, null, $options);
|
|
|
|
switch ($question['obasetype'] . '-' . @$question['oupdatetype']) {
|
|
case 'Q-':
|
|
$langstring = 'main/asked';
|
|
break;
|
|
|
|
case 'Q-' . QA_UPDATE_VISIBLE:
|
|
if (@$question['opersonal'])
|
|
$langstring = $question['hidden'] ? 'misc/your_q_hidden' : 'misc/your_q_reshown';
|
|
else
|
|
$langstring = $question['hidden'] ? 'main/hidden' : 'main/reshown';
|
|
break;
|
|
|
|
case 'Q-' . QA_UPDATE_CLOSED:
|
|
$isClosed = qa_post_is_closed($question);
|
|
if (@$question['opersonal'])
|
|
$langstring = $isClosed ? 'misc/your_q_closed' : 'misc/your_q_reopened';
|
|
else
|
|
$langstring = $isClosed ? 'main/closed' : 'main/reopened';
|
|
break;
|
|
|
|
case 'Q-' . QA_UPDATE_TAGS:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_q_retagged' : 'main/retagged';
|
|
break;
|
|
|
|
case 'Q-' . QA_UPDATE_CATEGORY:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_q_recategorized' : 'main/recategorized';
|
|
break;
|
|
|
|
case 'A-':
|
|
$langstring = @$question['opersonal'] ? 'misc/your_q_answered' : 'main/answered';
|
|
break;
|
|
|
|
case 'A-' . QA_UPDATE_SELECTED:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_a_selected' : 'main/answer_selected';
|
|
break;
|
|
|
|
case 'A-' . QA_UPDATE_VISIBLE:
|
|
if (@$question['opersonal'])
|
|
$langstring = $question['ohidden'] ? 'misc/your_a_hidden' : 'misc/your_a_reshown';
|
|
else
|
|
$langstring = $question['ohidden'] ? 'main/hidden' : 'main/answer_reshown';
|
|
break;
|
|
|
|
case 'A-' . QA_UPDATE_CONTENT:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_a_edited' : 'main/answer_edited';
|
|
break;
|
|
|
|
case 'Q-' . QA_UPDATE_FOLLOWS:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_a_questioned' : 'main/asked_related_q';
|
|
break;
|
|
|
|
case 'C-':
|
|
$langstring = 'main/commented';
|
|
break;
|
|
|
|
case 'C-' . QA_UPDATE_C_FOR_Q:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_q_commented' : 'main/commented';
|
|
break;
|
|
|
|
case 'C-' . QA_UPDATE_C_FOR_A:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_a_commented' : 'main/commented';
|
|
break;
|
|
|
|
case 'C-' . QA_UPDATE_FOLLOWS:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_c_followed' : 'main/commented';
|
|
break;
|
|
|
|
case 'C-' . QA_UPDATE_TYPE:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_c_moved' : 'main/comment_moved';
|
|
break;
|
|
|
|
case 'C-' . QA_UPDATE_VISIBLE:
|
|
if (@$question['opersonal'])
|
|
$langstring = $question['ohidden'] ? 'misc/your_c_hidden' : 'misc/your_c_reshown';
|
|
else
|
|
$langstring = $question['ohidden'] ? 'main/hidden' : 'main/comment_reshown';
|
|
break;
|
|
|
|
case 'C-' . QA_UPDATE_CONTENT:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_c_edited' : 'main/comment_edited';
|
|
break;
|
|
|
|
case 'Q-' . QA_UPDATE_CONTENT:
|
|
default:
|
|
$langstring = @$question['opersonal'] ? 'misc/your_q_edited' : 'main/edited';
|
|
break;
|
|
}
|
|
|
|
$fields['what'] = qa_lang_html($langstring);
|
|
|
|
if (@$question['opersonal'])
|
|
$fields['what_your'] = true;
|
|
|
|
if ($question['obasetype'] != 'Q' || @$question['oupdatetype'] == QA_UPDATE_FOLLOWS)
|
|
$fields['what_url'] = qa_q_path_html($question['postid'], $question['title'], false, $question['obasetype'], $question['opostid']);
|
|
|
|
if (@$options['contentview'] && !empty($question['ocontent'])) {
|
|
$viewer = qa_load_viewer($question['ocontent'], $question['oformat']);
|
|
|
|
$fields['content'] = $viewer->get_html($question['ocontent'], $question['oformat'], array(
|
|
'blockwordspreg' => @$options['blockwordspreg'],
|
|
'showurllinks' => @$options['showurllinks'],
|
|
'linksnewwindow' => @$options['linksnewwindow'],
|
|
));
|
|
}
|
|
|
|
if (@$options['whenview'])
|
|
$fields['when'] = qa_when_to_html($question['otime'], @$options['fulldatedays']);
|
|
|
|
if (@$options['whoview']) {
|
|
$isbyuser = qa_post_is_by_user(array('userid' => $question['ouserid'], 'cookieid' => @$question['ocookieid']), $userid, $cookieid);
|
|
|
|
$fields['who'] = qa_who_to_html($isbyuser, $question['ouserid'], $usershtml, @$options['ipview'] ? @inet_ntop(@$question['oip']) : null, false, @$question['oname']);
|
|
if (isset($question['opoints'])) {
|
|
if (@$options['pointsview'])
|
|
$fields['who']['points'] = ($question['opoints'] == 1) ? qa_lang_html_sub_split('main/1_point', '1', '1')
|
|
: qa_lang_html_sub_split('main/x_points', qa_format_number($question['opoints'], 0, true));
|
|
|
|
if (isset($options['pointstitle']))
|
|
$fields['who']['title'] = qa_get_points_title_html($question['opoints'], $options['pointstitle']);
|
|
}
|
|
|
|
if (isset($question['olevel']))
|
|
$fields['who']['level'] = qa_html(qa_user_level_string($question['olevel']));
|
|
}
|
|
|
|
unset($fields['flags']);
|
|
if (@$options['flagsview'] && @$question['oflagcount']) {
|
|
$fields['flags'] = ($question['oflagcount'] == 1) ? qa_lang_html_sub_split('main/1_flag', '1', '1')
|
|
: qa_lang_html_sub_split('main/x_flags', $question['oflagcount']);
|
|
}
|
|
|
|
unset($fields['avatar']);
|
|
if (@$options['avatarsize'] > 0) {
|
|
if (QA_FINAL_EXTERNAL_USERS)
|
|
$fields['avatar'] = qa_get_external_avatar_html($question['ouserid'], $options['avatarsize'], false);
|
|
else
|
|
$fields['avatar'] = qa_get_user_avatar_html($question['oflags'], $question['oemail'], $question['ohandle'],
|
|
$question['oavatarblobid'], $question['oavatarwidth'], $question['oavatarheight'], $options['avatarsize']);
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
|
|
/**
|
|
* Based on the elements in $question, return HTML to be passed to theme layer to link
|
|
* to the question, or to an associated answer, comment or edit.
|
|
* @param $question
|
|
* @param $userid
|
|
* @param $cookieid
|
|
* @param $usershtml
|
|
* @param $dummy
|
|
* @param $options
|
|
* @return array
|
|
*/
|
|
function qa_any_to_q_html_fields($question, $userid, $cookieid, $usershtml, $dummy, $options)
|
|
{
|
|
if (isset($question['opostid']))
|
|
$fields = qa_other_to_q_html_fields($question, $userid, $cookieid, $usershtml, null, $options);
|
|
else
|
|
$fields = qa_post_html_fields($question, $userid, $cookieid, $usershtml, null, $options);
|
|
|
|
return $fields;
|
|
}
|
|
|
|
|
|
/**
|
|
* Each element in $questions represents a question and optional associated answer, comment or edit, as retrieved from database.
|
|
* Return it sorted by the date appropriate for each element, without removing duplicate references to the same question.
|
|
* @param $questions
|
|
* @return mixed
|
|
*/
|
|
function qa_any_sort_by_date($questions)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
require_once QA_INCLUDE_DIR . 'util/sort.php';
|
|
|
|
foreach ($questions as $key => $question) // collect information about action referenced by each $question
|
|
$questions[$key]['sort'] = -(isset($question['opostid']) ? $question['otime'] : $question['created']);
|
|
|
|
qa_sort_by($questions, 'sort');
|
|
|
|
return $questions;
|
|
}
|
|
|
|
|
|
/**
|
|
* Each element in $questions represents a question and optional associated answer, comment or edit, as retrieved from database.
|
|
* Return it sorted by the date appropriate for each element, and keep only the first item related to each question.
|
|
* @param $questions
|
|
* @return array
|
|
*/
|
|
function qa_any_sort_and_dedupe($questions)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
require_once QA_INCLUDE_DIR . 'util/sort.php';
|
|
|
|
foreach ($questions as $key => $question) { // collect information about action referenced by each $question
|
|
if (isset($question['opostid'])) {
|
|
$questions[$key]['_time'] = $question['otime'];
|
|
$questions[$key]['_type'] = $question['obasetype'];
|
|
$questions[$key]['_userid'] = @$question['ouserid'];
|
|
} else {
|
|
$questions[$key]['_time'] = $question['created'];
|
|
$questions[$key]['_type'] = 'Q';
|
|
$questions[$key]['_userid'] = $question['userid'];
|
|
}
|
|
|
|
$questions[$key]['sort'] = -$questions[$key]['_time'];
|
|
}
|
|
qa_sort_by($questions, 'sort');
|
|
|
|
$keepquestions = array(); // now remove duplicate references to same question
|
|
foreach ($questions as $question) { // going in order from most recent to oldest
|
|
$laterquestion = @$keepquestions[$question['postid']];
|
|
|
|
if (isset($laterquestion)) {
|
|
// the two events were within 5 minutes of each other
|
|
$close_events = abs($laterquestion['_time'] - $question['_time']) < 300;
|
|
|
|
$later_edit =
|
|
@$laterquestion['oupdatetype'] && // the more recent reference was an edit
|
|
!@$question['oupdatetype'] && // this is not an edit
|
|
$laterquestion['_type'] == $question['_type'] && // the same part (Q/A/C) is referenced here
|
|
$laterquestion['_userid'] == $question['_userid']; // the same user made the later edit
|
|
|
|
// this question (in an update list) is personal to the user, but the other one was not
|
|
$this_personal = @$question['opersonal'] && !@$laterquestion['opersonal'];
|
|
|
|
if ($close_events && ($later_edit || $this_personal)) {
|
|
// Remove any previous instance of the post to force a new position
|
|
unset($keepquestions[$question['postid']]);
|
|
$keepquestions[$question['postid']] = $question;
|
|
}
|
|
} else // keep this reference if there is no more recent one
|
|
$keepquestions[$question['postid']] = $question;
|
|
}
|
|
|
|
return $keepquestions;
|
|
}
|
|
|
|
|
|
/**
|
|
* Each element in $questions represents a question and optional associated answer, comment or edit, as retrieved from database.
|
|
* Return an array of elements (userid,handle) for the appropriate user for each element.
|
|
* @param $questions
|
|
* @return array
|
|
*/
|
|
function qa_any_get_userids_handles($questions)
|
|
{
|
|
$userids_handles = array();
|
|
|
|
foreach ($questions as $question) {
|
|
if (isset($question['opostid'])) {
|
|
$userids_handles[] = array(
|
|
'userid' => @$question['ouserid'],
|
|
'handle' => @$question['ohandle'],
|
|
);
|
|
} else {
|
|
$userids_handles[] = array(
|
|
'userid' => @$question['userid'],
|
|
'handle' => @$question['handle'],
|
|
);
|
|
}
|
|
}
|
|
|
|
return $userids_handles;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return $html with any URLs converted into links (with nofollow and in a new window if $newwindow).
|
|
* Closing parentheses/brackets are removed from the link if they don't have a matching opening one. This avoids creating
|
|
* incorrect URLs from (http://www.question2answer.org) but allow URLs such as http://www.wikipedia.org/Computers_(Software)
|
|
* @param $html
|
|
* @param bool $newwindow
|
|
* @return mixed
|
|
*/
|
|
function qa_html_convert_urls($html, $newwindow = false)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$uc = 'a-z\x{00a1}-\x{ffff}';
|
|
$url_regex = '#\b((?:https?|ftp)://(?:[0-9' . $uc . '][0-9' . $uc . '-]*\.)+[' . $uc . ']{2,}(?::\d{2,5})?(?:/(?:[^\s<>]*[^\s<>\.])?)?)#iu';
|
|
|
|
// get matches and their positions
|
|
if (preg_match_all($url_regex, $html, $matches, PREG_OFFSET_CAPTURE)) {
|
|
$brackets = array(
|
|
')' => '(',
|
|
'}' => '{',
|
|
']' => '[',
|
|
);
|
|
|
|
// loop backwards so we substitute correctly
|
|
for ($i = count($matches[1]) - 1; $i >= 0; $i--) {
|
|
$match = $matches[1][$i];
|
|
$text_url = $match[0];
|
|
$removed = '';
|
|
$lastch = substr($text_url, -1);
|
|
|
|
// exclude bracket from link if no matching bracket
|
|
while (array_key_exists($lastch, $brackets)) {
|
|
$open_char = $brackets[$lastch];
|
|
$num_open = substr_count($text_url, $open_char);
|
|
$num_close = substr_count($text_url, $lastch);
|
|
|
|
if ($num_close == $num_open + 1) {
|
|
$text_url = substr($text_url, 0, -1);
|
|
$removed = $lastch . $removed;
|
|
$lastch = substr($text_url, -1);
|
|
} else
|
|
break;
|
|
}
|
|
|
|
$target = $newwindow ? ' target="_blank"' : '';
|
|
$replace = '<a href="' . $text_url . '" rel="nofollow"' . $target . '>' . $text_url . '</a>' . $removed;
|
|
$html = substr_replace($html, $replace, $match[1], strlen($match[0]));
|
|
}
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return HTML representation of $url (if it appears to be an URL), linked with nofollow and in a new window if $newwindow
|
|
* @param $url
|
|
* @param bool $newwindow
|
|
* @return mixed|string
|
|
*/
|
|
function qa_url_to_html_link($url, $newwindow = false)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
if (is_numeric(strpos($url, '.'))) {
|
|
$linkurl = $url;
|
|
if (!is_numeric(strpos($linkurl, ':/')))
|
|
$linkurl = 'http://' . $linkurl;
|
|
|
|
return '<a href="' . qa_html($linkurl) . '" rel="nofollow"' . ($newwindow ? ' target="_blank"' : '') . '>' . qa_html($url) . '</a>';
|
|
|
|
} else
|
|
return qa_html($url);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return $htmlmessage with ^1...^6 substituted for links to log in or register or confirm email and come back to $topage with $params
|
|
* @param $htmlmessage
|
|
* @param null $topage
|
|
* @param null $params
|
|
* @return string
|
|
*/
|
|
function qa_insert_login_links($htmlmessage, $topage = null, $params = null)
|
|
{
|
|
require_once QA_INCLUDE_DIR . 'app/users.php';
|
|
|
|
$userlinks = qa_get_login_links(qa_path_to_root(), isset($topage) ? qa_path($topage, $params, '') : null);
|
|
|
|
return strtr(
|
|
$htmlmessage,
|
|
|
|
array(
|
|
'^1' => empty($userlinks['login']) ? '' : '<a href="' . qa_html($userlinks['login']) . '">',
|
|
'^2' => empty($userlinks['login']) ? '' : '</a>',
|
|
'^3' => empty($userlinks['register']) ? '' : '<a href="' . qa_html($userlinks['register']) . '">',
|
|
'^4' => empty($userlinks['register']) ? '' : '</a>',
|
|
'^5' => empty($userlinks['confirm']) ? '' : '<a href="' . qa_html($userlinks['confirm']) . '">',
|
|
'^6' => empty($userlinks['confirm']) ? '' : '</a>',
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return structure to pass through to theme layer to show linked page numbers for $request.
|
|
* Q2A uses offset-based paging, i.e. pages are referenced in the URL by a 'start' parameter.
|
|
* $start is current offset, there are $pagesize items per page and $count items in total
|
|
* (unless $hasmore is true in which case there are at least $count items).
|
|
* Show links to $prevnext pages before and after this one and include $params in the URLs.
|
|
* @param $request
|
|
* @param $start
|
|
* @param $pagesize
|
|
* @param $count
|
|
* @param $prevnext
|
|
* @param array $params
|
|
* @param bool $hasmore
|
|
* @param null $anchor
|
|
* @return array|null
|
|
*/
|
|
function qa_html_page_links($request, $start, $pagesize, $count, $prevnext, $params = array(), $hasmore = false, $anchor = null)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$thispage = 1 + floor($start / $pagesize);
|
|
$lastpage = ceil(min((int)$count, 1 + QA_MAX_LIMIT_START) / $pagesize);
|
|
|
|
if ($thispage > 1 || $lastpage > $thispage) {
|
|
$links = array('label' => qa_lang_html('main/page_label'), 'items' => array());
|
|
|
|
$keypages[1] = true;
|
|
|
|
for ($page = max(2, min($thispage, $lastpage) - $prevnext); $page <= min($thispage + $prevnext, $lastpage); $page++)
|
|
$keypages[$page] = true;
|
|
|
|
$keypages[$lastpage] = true;
|
|
|
|
if ($thispage > 1) {
|
|
$links['items'][] = array(
|
|
'type' => 'prev',
|
|
'label' => qa_lang_html('main/page_prev'),
|
|
'page' => $thispage - 1,
|
|
'ellipsis' => false,
|
|
);
|
|
}
|
|
|
|
foreach (array_keys($keypages) as $page) {
|
|
$links['items'][] = array(
|
|
'type' => ($page == $thispage) ? 'this' : 'jump',
|
|
'label' => $page,
|
|
'page' => $page,
|
|
'ellipsis' => (($page < $lastpage) || $hasmore) && (!isset($keypages[$page + 1])),
|
|
);
|
|
}
|
|
|
|
if ($thispage < $lastpage) {
|
|
$links['items'][] = array(
|
|
'type' => 'next',
|
|
'label' => qa_lang_html('main/page_next'),
|
|
'page' => $thispage + 1,
|
|
'ellipsis' => false,
|
|
);
|
|
}
|
|
|
|
foreach ($links['items'] as $key => $link) {
|
|
if ($link['page'] != $thispage) {
|
|
$params['start'] = $pagesize * ($link['page'] - 1);
|
|
$links['items'][$key]['url'] = qa_path_html($request, $params, null, null, $anchor);
|
|
}
|
|
}
|
|
|
|
} else
|
|
$links = null;
|
|
|
|
return $links;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return HTML that suggests browsing all questions (in the category specified by $categoryrequest, if
|
|
* it's not null) and also popular tags if $usingtags is true
|
|
* @param bool $usingtags
|
|
* @param null $categoryrequest
|
|
* @return mixed|string
|
|
*/
|
|
function qa_html_suggest_qs_tags($usingtags = false, $categoryrequest = null)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$hascategory = strlen($categoryrequest);
|
|
|
|
$htmlmessage = $hascategory ? qa_lang_html('main/suggest_category_qs') :
|
|
($usingtags ? qa_lang_html('main/suggest_qs_tags') : qa_lang_html('main/suggest_qs'));
|
|
|
|
return strtr(
|
|
$htmlmessage,
|
|
|
|
array(
|
|
'^1' => '<a href="' . qa_path_html('questions' . ($hascategory ? ('/' . $categoryrequest) : '')) . '">',
|
|
'^2' => '</a>',
|
|
'^3' => '<a href="' . qa_path_html('tags') . '">',
|
|
'^4' => '</a>',
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return HTML that suggest getting things started by asking a question, in $categoryid if not null
|
|
* @param null $categoryid
|
|
* @return mixed|string
|
|
*/
|
|
function qa_html_suggest_ask($categoryid = null)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$htmlmessage = qa_lang_html('main/suggest_ask');
|
|
|
|
return strtr(
|
|
$htmlmessage,
|
|
|
|
array(
|
|
'^1' => '<a href="' . qa_path_html('ask', strlen($categoryid) ? array('cat' => $categoryid) : null) . '">',
|
|
'^2' => '</a>',
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the navigation structure for the category hierarchical menu, with $selectedid selected,
|
|
* and links beginning with $pathprefix, and showing question counts if $showqcount
|
|
* @param $categories
|
|
* @param null $selectedid
|
|
* @param string $pathprefix
|
|
* @param bool $showqcount
|
|
* @param null $pathparams
|
|
* @return array|mixed
|
|
*/
|
|
function qa_category_navigation($categories, $selectedid = null, $pathprefix = '', $showqcount = true, $pathparams = null)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$parentcategories = array();
|
|
|
|
foreach ($categories as $category)
|
|
$parentcategories[$category['parentid']][] = $category;
|
|
|
|
$selecteds = qa_category_path($categories, $selectedid);
|
|
$favoritemap = qa_get_favorite_non_qs_map();
|
|
|
|
return qa_category_navigation_sub($parentcategories, null, $selecteds, $pathprefix, $showqcount, $pathparams, $favoritemap);
|
|
}
|
|
|
|
|
|
/**
|
|
* Recursion function used by qa_category_navigation(...) to build hierarchical category menu.
|
|
* @param $parentcategories
|
|
* @param $parentid
|
|
* @param $selecteds
|
|
* @param $pathprefix
|
|
* @param $showqcount
|
|
* @param $pathparams
|
|
* @param null $favoritemap
|
|
* @return array|mixed
|
|
*/
|
|
function qa_category_navigation_sub($parentcategories, $parentid, $selecteds, $pathprefix, $showqcount, $pathparams, $favoritemap = null)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$navigation = array();
|
|
|
|
if (!isset($parentid)) {
|
|
$navigation['all'] = array(
|
|
'url' => qa_path_html($pathprefix, $pathparams),
|
|
'label' => qa_lang_html('main/all_categories'),
|
|
'selected' => !count($selecteds),
|
|
'categoryid' => null,
|
|
);
|
|
}
|
|
|
|
if (isset($parentcategories[$parentid])) {
|
|
foreach ($parentcategories[$parentid] as $category) {
|
|
$navigation[qa_html($category['tags'])] = array(
|
|
'url' => qa_path_html($pathprefix . $category['tags'], $pathparams),
|
|
'label' => qa_html($category['title']),
|
|
'popup' => qa_html(@$category['content']),
|
|
'selected' => isset($selecteds[$category['categoryid']]),
|
|
'note' => $showqcount ? ('(' . qa_html(qa_format_number($category['qcount'], 0, true)) . ')') : null,
|
|
'subnav' => qa_category_navigation_sub($parentcategories, $category['categoryid'], $selecteds,
|
|
$pathprefix . $category['tags'] . '/', $showqcount, $pathparams, $favoritemap),
|
|
'categoryid' => $category['categoryid'],
|
|
'favorited' => @$favoritemap['category'][$category['backpath']],
|
|
);
|
|
}
|
|
}
|
|
|
|
return $navigation;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the sub navigation structure for user listing pages
|
|
*/
|
|
function qa_users_sub_navigation()
|
|
{
|
|
if (QA_FINAL_EXTERNAL_USERS) {
|
|
return null;
|
|
}
|
|
|
|
$menuItems = array();
|
|
|
|
$moderatorPlus = qa_get_logged_in_level() >= QA_USER_LEVEL_MODERATOR;
|
|
$showNewUsersPage = !qa_user_permit_error('permit_view_new_users_page');
|
|
$showSpecialUsersPage = !qa_user_permit_error('permit_view_special_users_page');
|
|
|
|
if ($moderatorPlus || $showNewUsersPage || $showSpecialUsersPage) {
|
|
// We want to show this item when more than one item should be displayed
|
|
$menuItems['users$'] = array(
|
|
'label' => qa_lang_html('main/highest_users'),
|
|
'url' => qa_path_html('users'),
|
|
);
|
|
}
|
|
|
|
if ($showNewUsersPage) {
|
|
$menuItems['users/new'] = array(
|
|
'label' => qa_lang_html('main/newest_users'),
|
|
'url' => qa_path_html('users/new'),
|
|
);
|
|
}
|
|
|
|
if ($showSpecialUsersPage) {
|
|
$menuItems['users/special'] = array(
|
|
'label' => qa_lang('users/special_users'),
|
|
'url' => qa_path_html('users/special'),
|
|
);
|
|
}
|
|
|
|
if ($moderatorPlus) {
|
|
$menuItems['users/blocked'] = array(
|
|
'label' => qa_lang('users/blocked_users'),
|
|
'url' => qa_path_html('users/blocked'),
|
|
);
|
|
}
|
|
|
|
return $menuItems;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the sub navigation structure for navigating between the different pages relating to a user
|
|
* @param $handle
|
|
* @param $selected
|
|
* @param bool $ismyuser
|
|
* @return array
|
|
*/
|
|
function qa_user_sub_navigation($handle, $selected, $ismyuser = false)
|
|
{
|
|
$navigation = array(
|
|
'profile' => array(
|
|
'label' => qa_lang_html_sub('profile/user_x', qa_html($handle)),
|
|
'url' => qa_path_html('user/' . $handle),
|
|
),
|
|
|
|
'account' => array(
|
|
'label' => qa_lang_html('misc/nav_my_details'),
|
|
'url' => qa_path_html('account'),
|
|
),
|
|
|
|
'favorites' => array(
|
|
'label' => qa_lang_html('misc/nav_my_favorites'),
|
|
'url' => qa_path_html('favorites'),
|
|
),
|
|
|
|
'wall' => array(
|
|
'label' => qa_lang_html('misc/nav_user_wall'),
|
|
'url' => qa_path_html('user/' . $handle . '/wall'),
|
|
),
|
|
|
|
'messages' => array(
|
|
'label' => qa_lang_html('misc/nav_user_pms'),
|
|
'url' => qa_path_html('messages'),
|
|
),
|
|
|
|
'activity' => array(
|
|
'label' => qa_lang_html('misc/nav_user_activity'),
|
|
'url' => qa_path_html('user/' . $handle . '/activity'),
|
|
),
|
|
|
|
'questions' => array(
|
|
'label' => qa_lang_html('misc/nav_user_qs'),
|
|
'url' => qa_path_html('user/' . $handle . '/questions'),
|
|
),
|
|
|
|
'answers' => array(
|
|
'label' => qa_lang_html('misc/nav_user_as'),
|
|
'url' => qa_path_html('user/' . $handle . '/answers'),
|
|
),
|
|
);
|
|
|
|
if (isset($navigation[$selected]))
|
|
$navigation[$selected]['selected'] = true;
|
|
|
|
if (QA_FINAL_EXTERNAL_USERS || !qa_opt('allow_user_walls'))
|
|
unset($navigation['wall']);
|
|
|
|
if (QA_FINAL_EXTERNAL_USERS || !$ismyuser)
|
|
unset($navigation['account']);
|
|
|
|
if (!$ismyuser)
|
|
unset($navigation['favorites']);
|
|
|
|
if (QA_FINAL_EXTERNAL_USERS || !$ismyuser || !qa_opt('allow_private_messages') || !qa_opt('show_message_history'))
|
|
unset($navigation['messages']);
|
|
|
|
return $navigation;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the sub navigation structure for private message pages
|
|
* @deprecated 1.8.0 This menu is no longer used.
|
|
* @param null $selected
|
|
* @return array
|
|
*/
|
|
function qa_messages_sub_navigation($selected = null)
|
|
{
|
|
$navigation = array(
|
|
'inbox' => array(
|
|
'label' => qa_lang_html('misc/inbox'),
|
|
'url' => qa_path_html('messages'),
|
|
),
|
|
|
|
'outbox' => array(
|
|
'label' => qa_lang_html('misc/outbox'),
|
|
'url' => qa_path_html('messages/sent'),
|
|
),
|
|
);
|
|
|
|
if (isset($navigation[$selected]))
|
|
$navigation[$selected]['selected'] = true;
|
|
|
|
return $navigation;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the sub navigation structure for user account pages.
|
|
*
|
|
* @deprecated Deprecated from 1.6.3; use `qa_user_sub_navigation()` instead.
|
|
*/
|
|
function qa_account_sub_navigation()
|
|
{
|
|
return array(
|
|
'account' => array(
|
|
'label' => qa_lang_html('misc/nav_my_details'),
|
|
'url' => qa_path_html('account'),
|
|
),
|
|
|
|
'favorites' => array(
|
|
'label' => qa_lang_html('misc/nav_my_favorites'),
|
|
'url' => qa_path_html('favorites'),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the url for $page retrieved from the database
|
|
* @param $page
|
|
* @return string
|
|
*/
|
|
function qa_custom_page_url($page)
|
|
{
|
|
return ($page['flags'] & QA_PAGE_FLAGS_EXTERNAL)
|
|
? (is_numeric(strpos($page['tags'], '://')) ? $page['tags'] : qa_path_to_root() . $page['tags'])
|
|
: qa_path($page['tags']);
|
|
}
|
|
|
|
|
|
/**
|
|
* Add an element to the $navigation array corresponding to $page retrieved from the database
|
|
* @param $navigation
|
|
* @param $page
|
|
*/
|
|
function qa_navigation_add_page(&$navigation, $page)
|
|
{
|
|
if (!isset($page['permit']) || !qa_permit_value_error($page['permit'], qa_get_logged_in_userid(), qa_get_logged_in_level(), qa_get_logged_in_flags())) {
|
|
$url = qa_custom_page_url($page);
|
|
|
|
$navigation[($page['flags'] & QA_PAGE_FLAGS_EXTERNAL) ? ('custom-' . $page['pageid']) : ($page['tags'] . '$')] = array(
|
|
'url' => qa_html($url),
|
|
'label' => qa_html($page['title']),
|
|
'opposite' => ($page['nav'] == 'O'),
|
|
'target' => ($page['flags'] & QA_PAGE_FLAGS_NEW_WINDOW) ? '_blank' : null,
|
|
'selected' => ($page['flags'] & QA_PAGE_FLAGS_EXTERNAL) && (($url == qa_path(qa_request())) || ($url == qa_self_html())),
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert an admin option for matching into a threshold for the score given by database search
|
|
* @param $match
|
|
* @return int
|
|
*/
|
|
function qa_match_to_min_score($match)
|
|
{
|
|
return 10 - 2 * $match;
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds JavaScript to the page to handle toggling of form fields based on other fields.
|
|
*
|
|
* @param array $qa_content Page content array.
|
|
* @param array $effects List of rules for element toggling, with the structure:
|
|
* array('target1' => 'source1', 'target2' => 'source2', ...)
|
|
* When the source expression is true, the DOM element ID represented by target is shown. The
|
|
* source can be a combination of ID as a JS expression.
|
|
*/
|
|
function qa_set_display_rules(&$qa_content, $effects)
|
|
{
|
|
$keysourceids = array();
|
|
$jsVarRegex = '/[A-Za-z_][A-Za-z0-9_]*/';
|
|
|
|
// extract all JS variable names in all sources
|
|
foreach ($effects as $target => $sources) {
|
|
if (preg_match_all($jsVarRegex, $sources, $matches)) {
|
|
foreach ($matches[0] as $element) {
|
|
if (!in_array($element, $keysourceids))
|
|
$keysourceids[] = $element;
|
|
}
|
|
}
|
|
}
|
|
|
|
$funcOrd = isset($qa_content['script_lines']) ? count($qa_content['script_lines']) : 0;
|
|
$function = "qa_display_rule_$funcOrd";
|
|
$optVar = "qa_optids_$funcOrd";
|
|
|
|
// set up variables
|
|
$funcscript = array("var $optVar = " . json_encode($keysourceids) . ";");
|
|
|
|
// check and set all display rules
|
|
$funcscript[] = "function {$function}(first) {";
|
|
$funcscript[] = "\tvar opts = {};";
|
|
$funcscript[] = "\tfor (var i = 0; i < {$optVar}.length; i++) {";
|
|
$funcscript[] = "\t\tvar e = document.getElementById({$optVar}[i]);";
|
|
$funcscript[] = "\t\topts[{$optVar}[i]] = e && (e.checked || (e.options && e.options[e.selectedIndex].value));";
|
|
$funcscript[] = "\t}";
|
|
foreach ($effects as $target => $sources) {
|
|
$sourcesobj = preg_replace($jsVarRegex, 'opts.$0', $sources);
|
|
$funcscript[] = "\tqa_display_rule_show(" . qa_js($target) . ", (" . $sourcesobj . "), first);";
|
|
}
|
|
$funcscript[] = "}";
|
|
|
|
// set default state of options
|
|
$loadscript = array(
|
|
"for (var i = 0; i < {$optVar}.length; i++) {",
|
|
"\t$('#'+{$optVar}[i]).change(function() { " . $function . "(false); });",
|
|
"}",
|
|
"{$function}(true);",
|
|
);
|
|
|
|
$qa_content['script_lines'][] = $funcscript;
|
|
$qa_content['script_onloads'][] = $loadscript;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set up $qa_content and $field (with HTML name $fieldname) for tag auto-completion, where
|
|
* $exampletags are suggestions and $completetags are simply the most popular ones. Show up to $maxtags.
|
|
* @param $qa_content
|
|
* @param $field
|
|
* @param $fieldname
|
|
* @param $tags
|
|
* @param $exampletags
|
|
* @param $completetags
|
|
* @param $maxtags
|
|
*/
|
|
function qa_set_up_tag_field(&$qa_content, &$field, $fieldname, $tags, $exampletags, $completetags, $maxtags)
|
|
{
|
|
$template = '<a href="#" class="qa-tag-link" onclick="return qa_tag_click(this);">^</a>';
|
|
|
|
$qa_content['script_var']['qa_tag_template'] = $template;
|
|
$qa_content['script_var']['qa_tag_onlycomma'] = (int)qa_opt('tag_separator_comma');
|
|
$qa_content['script_var']['qa_tags_examples'] = qa_html(implode(',', $exampletags));
|
|
$qa_content['script_var']['qa_tags_complete'] = qa_html(implode(',', $completetags));
|
|
$qa_content['script_var']['qa_tags_max'] = (int)$maxtags;
|
|
|
|
$separatorcomma = qa_opt('tag_separator_comma');
|
|
|
|
$field['label'] = qa_lang_html($separatorcomma ? 'question/q_tags_comma_label' : 'question/q_tags_label');
|
|
$field['value'] = qa_html(implode($separatorcomma ? ', ' : ' ', $tags));
|
|
$field['tags'] = 'name="' . $fieldname . '" id="tags" autocomplete="off" onkeyup="qa_tag_hints();" onmouseup="qa_tag_hints();"';
|
|
|
|
$sdn = ' style="display:none;"';
|
|
|
|
$field['note'] =
|
|
'<span id="tag_examples_title"' . (count($exampletags) ? '' : $sdn) . '>' . qa_lang_html('question/example_tags') . '</span>' .
|
|
'<span id="tag_complete_title"' . $sdn . '>' . qa_lang_html('question/matching_tags') . '</span><span id="tag_hints">';
|
|
|
|
foreach ($exampletags as $tag)
|
|
$field['note'] .= str_replace('^', qa_html($tag), $template) . ' ';
|
|
|
|
$field['note'] .= '</span>';
|
|
$field['note_force'] = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get a list of user-entered tags submitted from a field that was created with qa_set_up_tag_field(...)
|
|
* @param $fieldname
|
|
* @return array
|
|
*/
|
|
function qa_get_tags_field_value($fieldname)
|
|
{
|
|
require_once QA_INCLUDE_DIR . 'util/string.php';
|
|
|
|
$text = qa_remove_utf8mb4(qa_post_text($fieldname));
|
|
|
|
if (qa_opt('tag_separator_comma'))
|
|
return array_unique(preg_split('/\s*,\s*/', trim(qa_strtolower(strtr($text, '/', ' '))), -1, PREG_SPLIT_NO_EMPTY));
|
|
else
|
|
return array_unique(qa_string_to_words($text, true, false, false, false));
|
|
}
|
|
|
|
|
|
/**
|
|
* Set up $qa_content and $field (with HTML name $fieldname) for hierarchical category navigation, with the initial value
|
|
* set to $categoryid (and $navcategories retrieved for $categoryid using qa_db_category_nav_selectspec(...)).
|
|
* If $allownone is true, it will allow selection of no category. If $allownosub is true, it will allow a category to be
|
|
* selected without selecting a subcategory within. Set $maxdepth to the maximum depth of category that can be selected
|
|
* (or null for no maximum) and $excludecategoryid to a category that should not be included.
|
|
* @param $qa_content
|
|
* @param $field
|
|
* @param $fieldname
|
|
* @param $navcategories
|
|
* @param $categoryid
|
|
* @param $allownone
|
|
* @param $allownosub
|
|
* @param null $maxdepth
|
|
* @param null $excludecategoryid
|
|
*/
|
|
function qa_set_up_category_field(&$qa_content, &$field, $fieldname, $navcategories, $categoryid, $allownone, $allownosub, $maxdepth = null, $excludecategoryid = null)
|
|
{
|
|
$pathcategories = qa_category_path($navcategories, $categoryid);
|
|
|
|
$startpath = '';
|
|
foreach ($pathcategories as $category)
|
|
$startpath .= '/' . $category['categoryid'];
|
|
|
|
if (isset($maxdepth))
|
|
$maxdepth = min(QA_CATEGORY_DEPTH, $maxdepth);
|
|
else
|
|
$maxdepth = QA_CATEGORY_DEPTH;
|
|
|
|
$qa_content['script_onloads'][] = sprintf('qa_category_select(%s, %s);', qa_js($fieldname), qa_js($startpath));
|
|
|
|
$qa_content['script_var']['qa_cat_exclude'] = $excludecategoryid;
|
|
$qa_content['script_var']['qa_cat_allownone'] = (int)$allownone;
|
|
$qa_content['script_var']['qa_cat_allownosub'] = (int)$allownosub;
|
|
$qa_content['script_var']['qa_cat_maxdepth'] = $maxdepth;
|
|
|
|
$field['type'] = 'select';
|
|
$field['tags'] = sprintf('name="%s_0" id="%s_0" onchange="qa_category_select(%s);"', $fieldname, $fieldname, qa_js($fieldname));
|
|
$field['options'] = array();
|
|
|
|
// create the menu that will be shown if Javascript is disabled
|
|
|
|
if ($allownone)
|
|
$field['options'][''] = qa_lang_html('main/no_category'); // this is also copied to first menu created by Javascript
|
|
|
|
$keycategoryids = array();
|
|
|
|
if ($allownosub) {
|
|
$category = @$navcategories[$categoryid];
|
|
|
|
$upcategory = @$navcategories[$category['parentid']]; // first get supercategories
|
|
while (isset($upcategory)) {
|
|
$keycategoryids[$upcategory['categoryid']] = true;
|
|
$upcategory = @$navcategories[$upcategory['parentid']];
|
|
}
|
|
|
|
$keycategoryids = array_reverse($keycategoryids, true);
|
|
|
|
$depth = count($keycategoryids); // number of levels above
|
|
|
|
if (isset($category)) {
|
|
$depth++; // to count category itself
|
|
|
|
foreach ($navcategories as $navcategory) // now get siblings and self
|
|
if (!strcmp($navcategory['parentid'], $category['parentid']))
|
|
$keycategoryids[$navcategory['categoryid']] = true;
|
|
}
|
|
|
|
if ($depth < $maxdepth)
|
|
foreach ($navcategories as $navcategory) // now get children, if not too deep
|
|
if (!strcmp($navcategory['parentid'], $categoryid))
|
|
$keycategoryids[$navcategory['categoryid']] = true;
|
|
|
|
} else {
|
|
$haschildren = false;
|
|
|
|
foreach ($navcategories as $navcategory) {
|
|
// check if it has any children
|
|
if (!strcmp($navcategory['parentid'], $categoryid)) {
|
|
$haschildren = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$haschildren)
|
|
$keycategoryids[$categoryid] = true; // show this category if it has no children
|
|
}
|
|
|
|
foreach ($keycategoryids as $keycategoryid => $dummy)
|
|
if (strcmp($keycategoryid, $excludecategoryid))
|
|
$field['options'][$keycategoryid] = qa_category_path_html($navcategories, $keycategoryid);
|
|
|
|
$field['value'] = @$field['options'][$categoryid];
|
|
$field['note'] =
|
|
'<div id="' . $fieldname . '_note">' .
|
|
'<noscript style="color:red;">' . qa_lang_html('question/category_js_note') . '</noscript>' .
|
|
'</div>';
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the user-entered category id submitted from a field that was created with qa_set_up_category_field(...)
|
|
* @param $fieldname
|
|
* @return mixed|null
|
|
*/
|
|
function qa_get_category_field_value($fieldname)
|
|
{
|
|
for ($level = QA_CATEGORY_DEPTH; $level >= 1; $level--) {
|
|
$levelid = qa_post_text($fieldname . '_' . $level);
|
|
if (strlen($levelid))
|
|
return $levelid;
|
|
}
|
|
|
|
if (!isset($levelid)) { // no Javascript-generated menu was present so take original menu
|
|
$levelid = qa_post_text($fieldname . '_0');
|
|
if (strlen($levelid))
|
|
return $levelid;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set up $qa_content and add to $fields to allow the user to enter their name for a post if they are not logged in
|
|
* $inname is from previous submission/validation. Pass $fieldprefix to add a prefix to the form field name used.
|
|
* @param $qa_content
|
|
* @param $fields
|
|
* @param $inname
|
|
* @param string $fieldprefix
|
|
*/
|
|
function qa_set_up_name_field(&$qa_content, &$fields, $inname, $fieldprefix = '')
|
|
{
|
|
$fields['name'] = array(
|
|
'label' => qa_lang_html('question/anon_name_label'),
|
|
'tags' => 'name="' . $fieldprefix . 'name"',
|
|
'value' => qa_html($inname),
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Set up $qa_content and add to $fields to allow user to set if they want to be notified regarding their post.
|
|
* $basetype is 'Q', 'A' or 'C' for question, answer or comment. $login_email is the email of logged in user,
|
|
* or null if this is an anonymous post. $innotify, $inemail and $errors_email are from previous submission/validation.
|
|
* Pass $fieldprefix to add a prefix to the form field names and IDs used.
|
|
* @param $qa_content
|
|
* @param $fields
|
|
* @param $basetype
|
|
* @param $login_email
|
|
* @param $innotify
|
|
* @param $inemail
|
|
* @param $errors_email
|
|
* @param string $fieldprefix
|
|
*/
|
|
function qa_set_up_notify_fields(&$qa_content, &$fields, $basetype, $login_email, $innotify, $inemail, $errors_email, $fieldprefix = '')
|
|
{
|
|
$fields['notify'] = array(
|
|
'tags' => 'name="' . $fieldprefix . 'notify"',
|
|
'type' => 'checkbox',
|
|
'value' => qa_html($innotify),
|
|
);
|
|
|
|
switch ($basetype) {
|
|
case 'Q':
|
|
$labelaskemail = qa_lang_html('question/q_notify_email');
|
|
$labelonly = qa_lang_html('question/q_notify_label');
|
|
$labelgotemail = qa_lang_html('question/q_notify_x_label');
|
|
break;
|
|
|
|
case 'A':
|
|
$labelaskemail = qa_lang_html('question/a_notify_email');
|
|
$labelonly = qa_lang_html('question/a_notify_label');
|
|
$labelgotemail = qa_lang_html('question/a_notify_x_label');
|
|
break;
|
|
|
|
case 'C':
|
|
$labelaskemail = qa_lang_html('question/c_notify_email');
|
|
$labelonly = qa_lang_html('question/c_notify_label');
|
|
$labelgotemail = qa_lang_html('question/c_notify_x_label');
|
|
break;
|
|
}
|
|
|
|
if (empty($login_email)) {
|
|
$fields['notify']['label'] =
|
|
'<span id="' . $fieldprefix . 'email_shown">' . $labelaskemail . '</span>' .
|
|
'<span id="' . $fieldprefix . 'email_hidden" style="display:none;">' . $labelonly . '</span>';
|
|
|
|
$fields['notify']['tags'] .= ' id="' . $fieldprefix . 'notify" onclick="if (document.getElementById(\'' . $fieldprefix . 'notify\').checked) document.getElementById(\'' . $fieldprefix . 'email\').focus();"';
|
|
$fields['notify']['tight'] = true;
|
|
|
|
$fields['email'] = array(
|
|
'id' => $fieldprefix . 'email_display',
|
|
'tags' => 'name="' . $fieldprefix . 'email" id="' . $fieldprefix . 'email"',
|
|
'value' => qa_html($inemail),
|
|
'note' => qa_lang_html('question/notify_email_note'),
|
|
'error' => qa_html($errors_email),
|
|
);
|
|
|
|
qa_set_display_rules($qa_content, array(
|
|
$fieldprefix . 'email_display' => $fieldprefix . 'notify',
|
|
$fieldprefix . 'email_shown' => $fieldprefix . 'notify',
|
|
$fieldprefix . 'email_hidden' => '!' . $fieldprefix . 'notify',
|
|
));
|
|
|
|
} else {
|
|
$fields['notify']['label'] = str_replace('^', qa_html($login_email), $labelgotemail);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the theme that should be used for displaying the page
|
|
* @return string
|
|
*/
|
|
function qa_get_site_theme()
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
return qa_opt(qa_is_mobile_probably() ? 'site_theme_mobile' : 'site_theme');
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the initialized class for $theme (or the default if it's gone), passing $template, $content and $request.
|
|
* Also applies any registered plugin layers.
|
|
* @param $theme
|
|
* @param $template
|
|
* @param $content
|
|
* @param $request
|
|
* @return qa_html_theme_base
|
|
*/
|
|
function qa_load_theme_class($theme, $template, $content, $request)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
global $qa_layers;
|
|
|
|
// First load the default class
|
|
|
|
require_once QA_INCLUDE_DIR . 'qa-theme-base.php';
|
|
|
|
$classname = 'qa_html_theme_base';
|
|
|
|
// Then load the selected theme if valid, otherwise load the Classic theme
|
|
|
|
if (!file_exists(QA_THEME_DIR . $theme . '/qa-styles.css'))
|
|
$theme = 'Classic';
|
|
|
|
$themeroothtml = qa_html(qa_path_to_root() . 'qa-theme/' . $theme . '/');
|
|
|
|
if (file_exists(QA_THEME_DIR . $theme . '/qa-theme.php')) {
|
|
require_once QA_THEME_DIR . $theme . '/qa-theme.php';
|
|
|
|
if (class_exists('qa_html_theme'))
|
|
$classname = 'qa_html_theme';
|
|
}
|
|
|
|
// Create the list of layers to load
|
|
|
|
$loadlayers = $qa_layers;
|
|
|
|
if (!qa_user_maximum_permit_error('permit_view_voters_flaggers')) {
|
|
$loadlayers[] = array(
|
|
'directory' => QA_INCLUDE_DIR . 'plugins/',
|
|
'include' => 'qa-layer-voters-flaggers.php',
|
|
'urltoroot' => null,
|
|
);
|
|
}
|
|
|
|
// Then load any theme layers using some class-munging magic (substitute class names)
|
|
|
|
$layerindex = 0;
|
|
|
|
foreach ($loadlayers as $layer) {
|
|
$filename = $layer['directory'] . $layer['include'];
|
|
$layerphp = file_get_contents($filename);
|
|
|
|
if (strlen($layerphp)) {
|
|
// include file name in layer class name to make debugging easier if there is an error
|
|
$newclassname = 'qa_layer_' . (++$layerindex) . '_from_' . preg_replace('/[^A-Za-z0-9_]+/', '_', basename($layer['include']));
|
|
|
|
if (preg_match('/\s+class\s+qa_html_theme_layer\s+extends\s+qa_html_theme_base\s+/im', $layerphp) != 1)
|
|
qa_fatal_error('Class for layer must be declared as "class qa_html_theme_layer extends qa_html_theme_base" in ' . $layer['directory'] . $layer['include']);
|
|
|
|
$searchwordreplace = array(
|
|
'qa_html_theme_base::qa_html_theme_base' => $classname . '::__construct', // PHP5 constructor fix
|
|
'parent::qa_html_theme_base' => 'parent::__construct', // PHP5 constructor fix
|
|
'qa_html_theme_layer' => $newclassname,
|
|
'qa_html_theme_base' => $classname,
|
|
'QA_HTML_THEME_LAYER_DIRECTORY' => "'" . $layer['directory'] . "'",
|
|
'QA_HTML_THEME_LAYER_URLTOROOT' => "'" . qa_path_to_root() . $layer['urltoroot'] . "'",
|
|
);
|
|
|
|
foreach ($searchwordreplace as $searchword => $replace) {
|
|
if (preg_match_all('/\W(' . preg_quote($searchword, '/') . ')\W/im', $layerphp, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE)) {
|
|
$searchmatches = array_reverse($matches[1]); // don't use preg_replace due to complication of escaping replacement phrase
|
|
|
|
foreach ($searchmatches as $searchmatch)
|
|
$layerphp = substr_replace($layerphp, $replace, $searchmatch[1], strlen($searchmatch[0]));
|
|
}
|
|
}
|
|
|
|
// echo '<pre style="text-align:left;">'.htmlspecialchars($layerphp).'</pre>'; // to debug munged code
|
|
|
|
qa_eval_from_file($layerphp, $filename);
|
|
|
|
$classname = $newclassname;
|
|
}
|
|
}
|
|
|
|
// Finally, instantiate the object
|
|
|
|
$themeclass = new $classname($template, $content, $themeroothtml, $request);
|
|
|
|
return $themeclass;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return an instantiation of the appropriate editor module class, given $content in $format
|
|
* Pass the preferred module name in $editorname, on return it will contain the name of the module used.
|
|
* @param $content string
|
|
* @param $format string
|
|
* @param $editorname string
|
|
* @return object
|
|
*/
|
|
function qa_load_editor($content, $format, &$editorname)
|
|
{
|
|
$maxeditor = qa_load_module('editor', $editorname); // take preferred one first
|
|
|
|
if (isset($maxeditor) && method_exists($maxeditor, 'calc_quality')) {
|
|
$maxquality = $maxeditor->calc_quality($content, $format);
|
|
if ($maxquality >= 0.5)
|
|
return $maxeditor;
|
|
|
|
} else
|
|
$maxquality = 0;
|
|
|
|
$editormodules = qa_load_modules_with('editor', 'calc_quality');
|
|
foreach ($editormodules as $tryname => $tryeditor) {
|
|
$tryquality = $tryeditor->calc_quality($content, $format);
|
|
|
|
if ($tryquality > $maxquality) {
|
|
$maxeditor = $tryeditor;
|
|
$maxquality = $tryquality;
|
|
$editorname = $tryname;
|
|
}
|
|
}
|
|
|
|
return $maxeditor;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a form field from the $editor module while making necessary modifications to $qa_content. The parameters
|
|
* $content, $format, $fieldname, $rows and $focusnow are passed through to the module's get_field() method. ($focusnow
|
|
* is deprecated as a parameter to get_field() but it's still passed through for old editor modules.) Based on
|
|
* $focusnow and $loadnow, also add the editor's load and/or focus scripts to $qa_content's onload handlers.
|
|
* @param $editor object
|
|
* @param array $qa_content
|
|
* @param string $content
|
|
* @param string $format
|
|
* @param string $fieldname
|
|
* @param int $rows
|
|
* @param bool $focusnow
|
|
* @param bool $loadnow
|
|
* @return string|array
|
|
*/
|
|
function qa_editor_load_field($editor, &$qa_content, $content, $format, $fieldname, $rows, $focusnow = false, $loadnow = true)
|
|
{
|
|
if (!isset($editor))
|
|
qa_fatal_error('No editor found for format: ' . $format);
|
|
|
|
$field = $editor->get_field($qa_content, $content, $format, $fieldname, $rows, $focusnow);
|
|
|
|
$onloads = array();
|
|
|
|
if ($loadnow && method_exists($editor, 'load_script'))
|
|
$onloads[] = $editor->load_script($fieldname);
|
|
|
|
if ($focusnow && method_exists($editor, 'focus_script'))
|
|
$onloads[] = $editor->focus_script($fieldname);
|
|
|
|
if (count($onloads))
|
|
$qa_content['script_onloads'][] = $onloads;
|
|
|
|
return $field;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return an instantiation of the appropriate viewer module class, given $content in $format
|
|
* @param string $content
|
|
* @param string $format
|
|
* @return object
|
|
*/
|
|
function qa_load_viewer($content, $format)
|
|
{
|
|
$maxviewer = null;
|
|
$maxquality = 0;
|
|
|
|
$viewermodules = qa_load_modules_with('viewer', 'calc_quality');
|
|
|
|
foreach ($viewermodules as $tryviewer) {
|
|
$tryquality = $tryviewer->calc_quality($content, $format);
|
|
|
|
if ($tryquality > $maxquality) {
|
|
$maxviewer = $tryviewer;
|
|
$maxquality = $tryquality;
|
|
}
|
|
}
|
|
|
|
return $maxviewer;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the plain text rendering of $content in $format, passing $options to the appropriate module
|
|
* @param string $content
|
|
* @param string $format
|
|
* @param array $options
|
|
* @return string
|
|
*/
|
|
function qa_viewer_text($content, $format, $options = array())
|
|
{
|
|
$viewer = qa_load_viewer($content, $format);
|
|
return $viewer->get_text($content, $format, $options);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the HTML rendering of $content in $format, passing $options to the appropriate module
|
|
* @param string $content
|
|
* @param string $format
|
|
* @param array $options
|
|
* @return string
|
|
*/
|
|
function qa_viewer_html($content, $format, $options = array())
|
|
{
|
|
$viewer = qa_load_viewer($content, $format);
|
|
return $viewer->get_html($content, $format, $options);
|
|
}
|
|
|
|
/**
|
|
* Retrieve title from HTTP POST, appropriately sanitised.
|
|
* @param string $fieldname
|
|
* @return string
|
|
*/
|
|
function qa_get_post_title($fieldname)
|
|
{
|
|
require_once QA_INCLUDE_DIR . 'util/string.php';
|
|
|
|
return qa_remove_utf8mb4(qa_post_text($fieldname));
|
|
}
|
|
|
|
/**
|
|
* Retrieve the POST from an editor module's HTML field named $contentfield, where the editor's name was in HTML field $editorfield
|
|
* Assigns the module's output to $incontent and $informat, editor's name in $ineditor, text rendering of content in $intext
|
|
* @param $editorfield
|
|
* @param $contentfield
|
|
* @param $ineditor
|
|
* @param $incontent
|
|
* @param $informat
|
|
* @param $intext
|
|
*/
|
|
function qa_get_post_content($editorfield, $contentfield, &$ineditor, &$incontent, &$informat, &$intext)
|
|
{
|
|
require_once QA_INCLUDE_DIR . 'util/string.php';
|
|
|
|
$ineditor = qa_post_text($editorfield);
|
|
$editor = qa_load_module('editor', $ineditor);
|
|
$readdata = $editor->read_post($contentfield);
|
|
|
|
// sanitise 4-byte Unicode
|
|
$incontent = qa_remove_utf8mb4($readdata['content']);
|
|
$informat = $readdata['format'];
|
|
$intext = qa_remove_utf8mb4(qa_viewer_text($incontent, $informat));
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if any of the 'content', 'format' or 'text' elements have changed between $oldfields and $fields
|
|
* If so, recalculate $fields['text'] based on $fields['content'] and $fields['format']
|
|
* @param $fields
|
|
* @param $oldfields
|
|
*/
|
|
function qa_update_post_text(&$fields, $oldfields)
|
|
{
|
|
if (strcmp($oldfields['content'], $fields['content']) ||
|
|
strcmp($oldfields['format'], $fields['format']) ||
|
|
strcmp($oldfields['text'], $fields['text'])
|
|
) {
|
|
$fields['text'] = qa_viewer_text($fields['content'], $fields['format']);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the <img...> HTML to display avatar $blobid whose stored size is $width and $height
|
|
* Constrain the image to $size (width AND height) and pad it to that size if $padding is true
|
|
* @param $blobId
|
|
* @param $width
|
|
* @param $height
|
|
* @param $size
|
|
* @param bool $padding
|
|
* @return null|string
|
|
*/
|
|
function qa_get_avatar_blob_html($blobId, $width, $height, $size, $padding = false)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
require_once QA_INCLUDE_DIR . 'util/image.php';
|
|
require_once QA_INCLUDE_DIR . 'app/users.php';
|
|
|
|
if (strlen($blobId) == 0 || (int)$size <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$avatarLink = qa_html(qa_get_avatar_blob_url($blobId, $size));
|
|
|
|
qa_image_constrain($width, $height, $size);
|
|
|
|
$params = array(
|
|
$avatarLink,
|
|
$width && $height ? sprintf(' width="%d" height="%d"', $width, $height) : '',
|
|
);
|
|
|
|
$html = vsprintf('<img src="%s"%s class="qa-avatar-image" alt=""/>', $params);
|
|
|
|
if ($padding && $width && $height) {
|
|
$padleft = floor(($size - $width) / 2);
|
|
$padright = $size - $width - $padleft;
|
|
$padtop = floor(($size - $height) / 2);
|
|
$padbottom = $size - $height - $padtop;
|
|
$html = sprintf('<span style="display:inline-block; padding:%dpx %dpx %dpx %dpx;">%s</span>', $padtop, $padright, $padbottom, $padleft, $html);
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the <img...> HTML to display the Gravatar for $email, constrained to $size
|
|
* @param $email
|
|
* @param $size
|
|
* @return mixed|null|string
|
|
*/
|
|
function qa_get_gravatar_html($email, $size)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
require_once QA_INCLUDE_DIR . 'app/users.php';
|
|
|
|
$avatarLink = qa_html(qa_get_gravatar_url($email, $size));
|
|
|
|
$size = (int)$size;
|
|
if ($size > 0) {
|
|
return sprintf('<img src="%s" width="%d" height="%d" class="qa-avatar-image" alt="" />', $avatarLink, $size, $size);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve the appropriate user title from $pointstitle for a user with $userpoints points, or null if none
|
|
* @param $userpoints
|
|
* @param $pointstitle
|
|
* @return null
|
|
*/
|
|
function qa_get_points_title_html($userpoints, $pointstitle)
|
|
{
|
|
foreach ($pointstitle as $points => $title) {
|
|
if ($userpoints >= $points)
|
|
return $title;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return an form to add to the $qa_content['notices'] array for displaying a user notice with id $noticeid
|
|
* and $content. Pass the raw database information for the notice in $rawnotice.
|
|
* @param $noticeid
|
|
* @param $content
|
|
* @param null $rawnotice
|
|
* @return array
|
|
*/
|
|
function qa_notice_form($noticeid, $content, $rawnotice = null)
|
|
{
|
|
$elementid = 'notice_' . $noticeid;
|
|
|
|
return array(
|
|
'id' => qa_html($elementid),
|
|
'raw' => $rawnotice,
|
|
'form_tags' => 'method="post" action="' . qa_self_html() . '"',
|
|
'form_hidden' => array('code' => qa_get_form_security_code('notice-' . $noticeid)),
|
|
'close_tags' => 'name="' . qa_html($elementid) . '" onclick="return qa_notice_click(this);"',
|
|
'content' => $content,
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a form to set in $qa_content['favorite'] for the favoriting button for entity $entitytype with $entityid.
|
|
* Set $favorite to whether the entity is currently a favorite and a description title for the button in $title.
|
|
* @param $entitytype
|
|
* @param $entityid
|
|
* @param $favorite
|
|
* @param $title
|
|
* @return array
|
|
*/
|
|
function qa_favorite_form($entitytype, $entityid, $favorite, $title)
|
|
{
|
|
return array(
|
|
'form_tags' => 'method="post" action="' . qa_self_html() . '"',
|
|
'form_hidden' => array('code' => qa_get_form_security_code('favorite-' . $entitytype . '-' . $entityid)),
|
|
'favorite_tags' => 'id="favoriting"',
|
|
($favorite ? 'favorite_remove_tags' : 'favorite_add_tags') =>
|
|
'title="' . qa_html($title) . '" name="' . qa_html('favorite_' . $entitytype . '_' . $entityid . '_' . (int)!$favorite) . '" onclick="return qa_favorite_click(this);"',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Format a number using the decimal point and thousand separator specified in the language files.
|
|
* If the number is compacted it is turned into a string such as 17.3k (only the thousand/million
|
|
* cases are currently supported).
|
|
*
|
|
* @since 1.8.0
|
|
* @param integer $number Number to be formatted
|
|
* @param integer $decimals Amount of decimals to use (ignored if number gets shortened)
|
|
* @param bool $compact Whether the number can be shown as compact or not
|
|
* @return string The formatted number as a string
|
|
*/
|
|
function qa_format_number($number, $decimals = 0, $compact = false)
|
|
{
|
|
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
|
|
|
|
$suffix = '';
|
|
|
|
if ($compact && qa_opt('show_compact_numbers')) {
|
|
// when compacting we keep 2-3 significant figures (e.g. 9.1k, 234k)
|
|
if ($number >= 1000000) {
|
|
$number /= 1000000;
|
|
$suffix = qa_lang_html('main/_millions_suffix');
|
|
$decimals = $number < 100 ? 1 : 0;
|
|
} elseif ($number >= 1000) {
|
|
$number /= 1000;
|
|
$suffix = qa_lang_html('main/_thousands_suffix');
|
|
$decimals = $number < 100 ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
return number_format(
|
|
$number,
|
|
$decimals,
|
|
qa_lang_html('main/_decimal_point'),
|
|
qa_lang_html('main/_thousands_separator')
|
|
) . $suffix;
|
|
}
|