Commit f24542bb by Scott

Coding style (app posts/admin)

parent 5a94ef80
......@@ -20,623 +20,618 @@
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;
}
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../');
exit;
}
function qa_admin_check_privileges(&$qa_content)
/*
Return true if user is logged in with admin privileges. If not, return false
and set up $qa_content with the appropriate title and error message
*/
{
if (!qa_is_logged_in()) {
require_once QA_INCLUDE_DIR.'app/format.php';
/**
* Return true if user is logged in with admin privileges. If not, return false
* and set up $qa_content with the appropriate title and error message
*/
function qa_admin_check_privileges(&$qa_content)
{
if (!qa_is_logged_in()) {
require_once QA_INCLUDE_DIR.'app/format.php';
$qa_content=qa_content_prepare();
$qa_content=qa_content_prepare();
$qa_content['title']=qa_lang_html('admin/admin_title');
$qa_content['error']=qa_insert_login_links(qa_lang_html('admin/not_logged_in'), qa_request());
$qa_content['title']=qa_lang_html('admin/admin_title');
$qa_content['error']=qa_insert_login_links(qa_lang_html('admin/not_logged_in'), qa_request());
return false;
} elseif (qa_get_logged_in_level()<QA_USER_LEVEL_ADMIN) {
$qa_content=qa_content_prepare();
return false;
$qa_content['title']=qa_lang_html('admin/admin_title');
$qa_content['error']=qa_lang_html('admin/no_privileges');
} elseif (qa_get_logged_in_level()<QA_USER_LEVEL_ADMIN) {
$qa_content=qa_content_prepare();
return false;
}
$qa_content['title']=qa_lang_html('admin/admin_title');
$qa_content['error']=qa_lang_html('admin/no_privileges');
return true;
return false;
}
return true;
}
/**
* Return a sorted array of available languages, [short code] => [long name]
*/
function qa_admin_language_options()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Return a sorted array of available languages, [short code] => [long name]
* @deprecated The hardcoded language ids will be removed in favor of language metadata files.
* See qa-lang/en-GB directory for a clear example of how to use them.
*/
function qa_admin_language_options()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* @deprecated The hardcoded language ids will be removed in favor of language metadata files.
* See qa-lang/en-GB directory for a clear example of how to use them.
*/
$codetolanguage = array( // 2-letter language codes as per ISO 639-1
'ar' => 'Arabic - العربية',
'az' => 'Azerbaijani - Azərbaycanca',
'bg' => 'Bulgarian - Български',
'bn' => 'Bengali - বাংলা',
'ca' => 'Catalan - Català',
'cs' => 'Czech - Čeština',
'cy' => 'Welsh - Cymraeg',
'da' => 'Danish - Dansk',
'de' => 'German - Deutsch',
'el' => 'Greek - Ελληνικά',
'en-GB' => 'English (UK)',
'es' => 'Spanish - Español',
'et' => 'Estonian - Eesti',
'fa' => 'Persian - فارسی',
'fi' => 'Finnish - Suomi',
'fr' => 'French - Français',
'he' => 'Hebrew - עברית',
'hr' => 'Croatian - Hrvatski',
'hu' => 'Hungarian - Magyar',
'id' => 'Indonesian - Bahasa Indonesia',
'is' => 'Icelandic - Íslenska',
'it' => 'Italian - Italiano',
'ja' => 'Japanese - 日本語',
'ka' => 'Georgian - ქართული ენა',
'kh' => 'Khmer - ភាសាខ្មែរ',
'ko' => 'Korean - 한국어',
'ku-CKB' => 'Kurdish Central - کورد',
'lt' => 'Lithuanian - Lietuvių',
'lv' => 'Latvian - Latviešu',
'nl' => 'Dutch - Nederlands',
'no' => 'Norwegian - Norsk',
'pl' => 'Polish - Polski',
'pt' => 'Portuguese - Português',
'ro' => 'Romanian - Română',
'ru' => 'Russian - Русский',
'sk' => 'Slovak - Slovenčina',
'sl' => 'Slovenian - Slovenščina',
'sq' => 'Albanian - Shqip',
'sr' => 'Serbian - Српски',
'sv' => 'Swedish - Svenska',
'th' => 'Thai - ไทย',
'tr' => 'Turkish - Türkçe',
'ug' => 'Uyghur - ئۇيغۇرچە',
'uk' => 'Ukrainian - Українська',
'uz' => 'Uzbek - ўзбек',
'vi' => 'Vietnamese - Tiếng Việt',
'zh-TW' => 'Chinese Traditional - 繁體中文',
'zh' => 'Chinese Simplified - 简体中文',
);
$codetolanguage = array( // 2-letter language codes as per ISO 639-1
'ar' => 'Arabic - العربية',
'az' => 'Azerbaijani - Azərbaycanca',
'bg' => 'Bulgarian - Български',
'bn' => 'Bengali - বাংলা',
'ca' => 'Catalan - Català',
'cs' => 'Czech - Čeština',
'cy' => 'Welsh - Cymraeg',
'da' => 'Danish - Dansk',
'de' => 'German - Deutsch',
'el' => 'Greek - Ελληνικά',
'en-GB' => 'English (UK)',
'es' => 'Spanish - Español',
'et' => 'Estonian - Eesti',
'fa' => 'Persian - فارسی',
'fi' => 'Finnish - Suomi',
'fr' => 'French - Français',
'he' => 'Hebrew - עברית',
'hr' => 'Croatian - Hrvatski',
'hu' => 'Hungarian - Magyar',
'id' => 'Indonesian - Bahasa Indonesia',
'is' => 'Icelandic - Íslenska',
'it' => 'Italian - Italiano',
'ja' => 'Japanese - 日本語',
'ka' => 'Georgian - ქართული ენა',
'kh' => 'Khmer - ភាសាខ្មែរ',
'ko' => 'Korean - 한국어',
'ku-CKB' => 'Kurdish Central - کورد',
'lt' => 'Lithuanian - Lietuvių',
'lv' => 'Latvian - Latviešu',
'nl' => 'Dutch - Nederlands',
'no' => 'Norwegian - Norsk',
'pl' => 'Polish - Polski',
'pt' => 'Portuguese - Português',
'ro' => 'Romanian - Română',
'ru' => 'Russian - Русский',
'sk' => 'Slovak - Slovenčina',
'sl' => 'Slovenian - Slovenščina',
'sq' => 'Albanian - Shqip',
'sr' => 'Serbian - Српски',
'sv' => 'Swedish - Svenska',
'th' => 'Thai - ไทย',
'tr' => 'Turkish - Türkçe',
'ug' => 'Uyghur - ئۇيغۇرچە',
'uk' => 'Ukrainian - Українська',
'uz' => 'Uzbek - ўзбек',
'vi' => 'Vietnamese - Tiếng Việt',
'zh-TW' => 'Chinese Traditional - 繁體中文',
'zh' => 'Chinese Simplified - 简体中文',
);
$options = array('' => 'English (US)');
// find all language folders
$metadataUtil = new Q2A_Util_Metadata();
foreach (glob(QA_LANG_DIR . '*', GLOB_ONLYDIR) as $directory) {
$code = basename($directory);
$metadata = $metadataUtil->fetchFromAddonPath($directory);
if (isset($metadata['name']))
$options[$code] = $metadata['name'];
// otherwise use an entry from above
elseif (isset($codetolanguage[$code]))
$options[$code] = $codetolanguage[$code];
}
$options = array('' => 'English (US)');
// find all language folders
$metadataUtil = new Q2A_Util_Metadata();
foreach (glob(QA_LANG_DIR . '*', GLOB_ONLYDIR) as $directory) {
$code = basename($directory);
$metadata = $metadataUtil->fetchFromAddonPath($directory);
if (isset($metadata['name']))
$options[$code] = $metadata['name'];
// otherwise use an entry from above
elseif (isset($codetolanguage[$code]))
$options[$code] = $codetolanguage[$code];
asort($options, SORT_STRING);
return $options;
}
/**
* Return a sorted array of available themes, [theme name] => [theme name]
*/
function qa_admin_theme_options()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$metadataUtil = new Q2A_Util_Metadata();
foreach (glob(QA_THEME_DIR . '*', GLOB_ONLYDIR) as $directory) {
$theme = basename($directory);
$metadata = $metadataUtil->fetchFromAddonPath($directory);
if (empty($metadata)) {
// limit theme parsing to first 8kB
$contents = file_get_contents($directory . '/qa-styles.css', false, null, -1, 8192);
$metadata = qa_addon_metadata($contents, 'Theme');
}
asort($options, SORT_STRING);
return $options;
$options[$theme] = isset($metadata['name']) ? $metadata['name'] : $theme;
}
asort($options, SORT_STRING);
return $options;
}
/**
* Return an array of widget placement options, with keys matching the database value
*/
function qa_admin_place_options()
{
return array(
'FT' => qa_lang_html('options/place_full_top'),
'FH' => qa_lang_html('options/place_full_below_nav'),
'FL' => qa_lang_html('options/place_full_below_content'),
'FB' => qa_lang_html('options/place_full_below_footer'),
'MT' => qa_lang_html('options/place_main_top'),
'MH' => qa_lang_html('options/place_main_below_title'),
'ML' => qa_lang_html('options/place_main_below_lists'),
'MB' => qa_lang_html('options/place_main_bottom'),
'ST' => qa_lang_html('options/place_side_top'),
'SH' => qa_lang_html('options/place_side_below_sidebar'),
'SL' => qa_lang_html('options/place_side_low'),
'SB' => qa_lang_html('options/place_side_last'),
);
}
/**
* Return an array of page size options up to $maximum, [page size] => [page size]
*/
function qa_admin_page_size_options($maximum)
{
$rawoptions=array(5, 10, 15, 20, 25, 30, 40, 50, 60, 80, 100, 120, 150, 200, 250, 300, 400, 500, 600, 800, 1000);
$options=array();
foreach ($rawoptions as $rawoption) {
if ($rawoption>$maximum)
break;
$options[$rawoption]=$rawoption;
}
/**
* Return a sorted array of available themes, [theme name] => [theme name]
*/
function qa_admin_theme_options()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$metadataUtil = new Q2A_Util_Metadata();
foreach (glob(QA_THEME_DIR . '*', GLOB_ONLYDIR) as $directory) {
$theme = basename($directory);
$metadata = $metadataUtil->fetchFromAddonPath($directory);
if (empty($metadata)) {
// limit theme parsing to first 8kB
$contents = file_get_contents($directory . '/qa-styles.css', false, null, -1, 8192);
$metadata = qa_addon_metadata($contents, 'Theme');
}
$options[$theme] = isset($metadata['name']) ? $metadata['name'] : $theme;
}
asort($options, SORT_STRING);
return $options;
return $options;
}
/**
* Return an array of options representing matching precision, [value] => [label]
*/
function qa_admin_match_options()
{
return array(
5 => qa_lang_html('options/match_5'),
4 => qa_lang_html('options/match_4'),
3 => qa_lang_html('options/match_3'),
2 => qa_lang_html('options/match_2'),
1 => qa_lang_html('options/match_1'),
);
}
/**
* Return an array of options representing permission restrictions, [value] => [label]
* ranging from $widest to $narrowest. Set $doconfirms to whether email confirmations are on
*/
function qa_admin_permit_options($widest, $narrowest, $doconfirms=true, $dopoints=true)
{
require_once QA_INCLUDE_DIR.'app/options.php';
$options=array(
QA_PERMIT_ALL => qa_lang_html('options/permit_all'),
QA_PERMIT_USERS => qa_lang_html('options/permit_users'),
QA_PERMIT_CONFIRMED => qa_lang_html('options/permit_confirmed'),
QA_PERMIT_POINTS => qa_lang_html('options/permit_points'),
QA_PERMIT_POINTS_CONFIRMED => qa_lang_html('options/permit_points_confirmed'),
QA_PERMIT_APPROVED => qa_lang_html('options/permit_approved'),
QA_PERMIT_APPROVED_POINTS => qa_lang_html('options/permit_approved_points'),
QA_PERMIT_EXPERTS => qa_lang_html('options/permit_experts'),
QA_PERMIT_EDITORS => qa_lang_html('options/permit_editors'),
QA_PERMIT_MODERATORS => qa_lang_html('options/permit_moderators'),
QA_PERMIT_ADMINS => qa_lang_html('options/permit_admins'),
QA_PERMIT_SUPERS => qa_lang_html('options/permit_supers'),
);
foreach ($options as $key => $label)
if (($key<$narrowest) || ($key>$widest))
unset($options[$key]);
if (!$doconfirms) {
unset($options[QA_PERMIT_CONFIRMED]);
unset($options[QA_PERMIT_POINTS_CONFIRMED]);
}
if (!$dopoints) {
unset($options[QA_PERMIT_POINTS]);
unset($options[QA_PERMIT_POINTS_CONFIRMED]);
unset($options[QA_PERMIT_APPROVED_POINTS]);
}
function qa_admin_place_options()
/*
Return an array of widget placement options, with keys matching the database value
*/
{
return array(
'FT' => qa_lang_html('options/place_full_top'),
'FH' => qa_lang_html('options/place_full_below_nav'),
'FL' => qa_lang_html('options/place_full_below_content'),
'FB' => qa_lang_html('options/place_full_below_footer'),
'MT' => qa_lang_html('options/place_main_top'),
'MH' => qa_lang_html('options/place_main_below_title'),
'ML' => qa_lang_html('options/place_main_below_lists'),
'MB' => qa_lang_html('options/place_main_bottom'),
'ST' => qa_lang_html('options/place_side_top'),
'SH' => qa_lang_html('options/place_side_below_sidebar'),
'SL' => qa_lang_html('options/place_side_low'),
'SB' => qa_lang_html('options/place_side_last'),
);
if (QA_FINAL_EXTERNAL_USERS || !qa_opt('moderate_users')) {
unset($options[QA_PERMIT_APPROVED]);
unset($options[QA_PERMIT_APPROVED_POINTS]);
}
return $options;
}
function qa_admin_page_size_options($maximum)
/*
Return an array of page size options up to $maximum, [page size] => [page size]
*/
{
$rawoptions=array(5, 10, 15, 20, 25, 30, 40, 50, 60, 80, 100, 120, 150, 200, 250, 300, 400, 500, 600, 800, 1000);
$options=array();
foreach ($rawoptions as $rawoption) {
if ($rawoption>$maximum)
break;
/**
* Return the sub navigation structure common to admin pages
*/
function qa_admin_sub_navigation()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$options[$rawoption]=$rawoption;
}
$navigation=array();
$level=qa_get_logged_in_level();
return $options;
}
if ($level>=QA_USER_LEVEL_ADMIN) {
$navigation['admin/general']=array(
'label' => qa_lang_html('admin/general_title'),
'url' => qa_path_html('admin/general'),
);
$navigation['admin/emails']=array(
'label' => qa_lang_html('admin/emails_title'),
'url' => qa_path_html('admin/emails'),
);
function qa_admin_match_options()
/*
Return an array of options representing matching precision, [value] => [label]
*/
{
return array(
5 => qa_lang_html('options/match_5'),
4 => qa_lang_html('options/match_4'),
3 => qa_lang_html('options/match_3'),
2 => qa_lang_html('options/match_2'),
1 => qa_lang_html('options/match_1'),
$navigation['admin/users']=array(
'label' => qa_lang_html('admin/users_title'),
'url' => qa_path_html('admin/users'),
'selected_on' => array('admin/users$', 'admin/userfields$', 'admin/usertitles$'),
);
}
$navigation['admin/layout']=array(
'label' => qa_lang_html('admin/layout_title'),
'url' => qa_path_html('admin/layout'),
);
function qa_admin_permit_options($widest, $narrowest, $doconfirms=true, $dopoints=true)
/*
Return an array of options representing permission restrictions, [value] => [label]
ranging from $widest to $narrowest. Set $doconfirms to whether email confirmations are on
*/
{
require_once QA_INCLUDE_DIR.'app/options.php';
$options=array(
QA_PERMIT_ALL => qa_lang_html('options/permit_all'),
QA_PERMIT_USERS => qa_lang_html('options/permit_users'),
QA_PERMIT_CONFIRMED => qa_lang_html('options/permit_confirmed'),
QA_PERMIT_POINTS => qa_lang_html('options/permit_points'),
QA_PERMIT_POINTS_CONFIRMED => qa_lang_html('options/permit_points_confirmed'),
QA_PERMIT_APPROVED => qa_lang_html('options/permit_approved'),
QA_PERMIT_APPROVED_POINTS => qa_lang_html('options/permit_approved_points'),
QA_PERMIT_EXPERTS => qa_lang_html('options/permit_experts'),
QA_PERMIT_EDITORS => qa_lang_html('options/permit_editors'),
QA_PERMIT_MODERATORS => qa_lang_html('options/permit_moderators'),
QA_PERMIT_ADMINS => qa_lang_html('options/permit_admins'),
QA_PERMIT_SUPERS => qa_lang_html('options/permit_supers'),
$navigation['admin/posting']=array(
'label' => qa_lang_html('admin/posting_title'),
'url' => qa_path_html('admin/posting'),
);
foreach ($options as $key => $label)
if (($key<$narrowest) || ($key>$widest))
unset($options[$key]);
$navigation['admin/viewing']=array(
'label' => qa_lang_html('admin/viewing_title'),
'url' => qa_path_html('admin/viewing'),
);
if (!$doconfirms) {
unset($options[QA_PERMIT_CONFIRMED]);
unset($options[QA_PERMIT_POINTS_CONFIRMED]);
}
$navigation['admin/lists']=array(
'label' => qa_lang_html('admin/lists_title'),
'url' => qa_path_html('admin/lists'),
);
if (!$dopoints) {
unset($options[QA_PERMIT_POINTS]);
unset($options[QA_PERMIT_POINTS_CONFIRMED]);
unset($options[QA_PERMIT_APPROVED_POINTS]);
}
if (qa_using_categories())
$navigation['admin/categories']=array(
'label' => qa_lang_html('admin/categories_title'),
'url' => qa_path_html('admin/categories'),
);
if (QA_FINAL_EXTERNAL_USERS || !qa_opt('moderate_users')) {
unset($options[QA_PERMIT_APPROVED]);
unset($options[QA_PERMIT_APPROVED_POINTS]);
}
$navigation['admin/permissions']=array(
'label' => qa_lang_html('admin/permissions_title'),
'url' => qa_path_html('admin/permissions'),
);
return $options;
}
$navigation['admin/pages']=array(
'label' => qa_lang_html('admin/pages_title'),
'url' => qa_path_html('admin/pages'),
);
$navigation['admin/feeds']=array(
'label' => qa_lang_html('admin/feeds_title'),
'url' => qa_path_html('admin/feeds'),
);
function qa_admin_sub_navigation()
/*
Return the sub navigation structure common to admin pages
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$navigation['admin/points']=array(
'label' => qa_lang_html('admin/points_title'),
'url' => qa_path_html('admin/points'),
);
$navigation=array();
$level=qa_get_logged_in_level();
$navigation['admin/spam']=array(
'label' => qa_lang_html('admin/spam_title'),
'url' => qa_path_html('admin/spam'),
);
if ($level>=QA_USER_LEVEL_ADMIN) {
$navigation['admin/general']=array(
'label' => qa_lang_html('admin/general_title'),
'url' => qa_path_html('admin/general'),
if (defined('QA_CACHE_DIRECTORY')) {
$navigation['admin/caching']=array(
'label' => qa_lang_html('admin/caching_title'),
'url' => qa_path_html('admin/caching'),
);
}
$navigation['admin/emails']=array(
'label' => qa_lang_html('admin/emails_title'),
'url' => qa_path_html('admin/emails'),
);
$navigation['admin/stats']=array(
'label' => qa_lang_html('admin/stats_title'),
'url' => qa_path_html('admin/stats'),
);
$navigation['admin/users']=array(
'label' => qa_lang_html('admin/users_title'),
'url' => qa_path_html('admin/users'),
'selected_on' => array('admin/users$', 'admin/userfields$', 'admin/usertitles$'),
if (!QA_FINAL_EXTERNAL_USERS)
$navigation['admin/mailing']=array(
'label' => qa_lang_html('admin/mailing_title'),
'url' => qa_path_html('admin/mailing'),
);
$navigation['admin/layout']=array(
'label' => qa_lang_html('admin/layout_title'),
'url' => qa_path_html('admin/layout'),
);
$navigation['admin/plugins']=array(
'label' => qa_lang_html('admin/plugins_title'),
'url' => qa_path_html('admin/plugins'),
);
}
$navigation['admin/posting']=array(
'label' => qa_lang_html('admin/posting_title'),
'url' => qa_path_html('admin/posting'),
);
if (!qa_user_maximum_permit_error('permit_moderate')) {
$count=qa_user_permit_error('permit_moderate') ? null : qa_opt('cache_queuedcount'); // if only in some categories don't show cached count
$navigation['admin/viewing']=array(
'label' => qa_lang_html('admin/viewing_title'),
'url' => qa_path_html('admin/viewing'),
);
$navigation['admin/moderate']=array(
'label' => qa_lang_html('admin/moderate_title').($count ? (' ('.$count.')') : ''),
'url' => qa_path_html('admin/moderate'),
);
}
$navigation['admin/lists']=array(
'label' => qa_lang_html('admin/lists_title'),
'url' => qa_path_html('admin/lists'),
);
if (qa_opt('flagging_of_posts') && !qa_user_maximum_permit_error('permit_hide_show')) {
$count=qa_user_permit_error('permit_hide_show') ? null : qa_opt('cache_flaggedcount'); // if only in some categories don't show cached count
if (qa_using_categories())
$navigation['admin/categories']=array(
'label' => qa_lang_html('admin/categories_title'),
'url' => qa_path_html('admin/categories'),
);
$navigation['admin/flagged']=array(
'label' => qa_lang_html('admin/flagged_title').($count ? (' ('.$count.')') : ''),
'url' => qa_path_html('admin/flagged'),
);
}
$navigation['admin/permissions']=array(
'label' => qa_lang_html('admin/permissions_title'),
'url' => qa_path_html('admin/permissions'),
);
if ( (!qa_user_maximum_permit_error('permit_hide_show')) || (!qa_user_maximum_permit_error('permit_delete_hidden')) )
$navigation['admin/hidden']=array(
'label' => qa_lang_html('admin/hidden_title'),
'url' => qa_path_html('admin/hidden'),
);
$navigation['admin/pages']=array(
'label' => qa_lang_html('admin/pages_title'),
'url' => qa_path_html('admin/pages'),
);
if ( (!QA_FINAL_EXTERNAL_USERS) && qa_opt('moderate_users') && ($level>=QA_USER_LEVEL_MODERATOR)) {
$count=qa_opt('cache_uapprovecount');
$navigation['admin/feeds']=array(
'label' => qa_lang_html('admin/feeds_title'),
'url' => qa_path_html('admin/feeds'),
);
$navigation['admin/approve']=array(
'label' => qa_lang_html('admin/approve_users_title').($count ? (' ('.$count.')') : ''),
'url' => qa_path_html('admin/approve'),
);
}
$navigation['admin/points']=array(
'label' => qa_lang_html('admin/points_title'),
'url' => qa_path_html('admin/points'),
);
return $navigation;
}
$navigation['admin/spam']=array(
'label' => qa_lang_html('admin/spam_title'),
'url' => qa_path_html('admin/spam'),
);
if (defined('QA_CACHE_DIRECTORY')) {
$navigation['admin/caching']=array(
'label' => qa_lang_html('admin/caching_title'),
'url' => qa_path_html('admin/caching'),
);
}
/**
* Return the error that needs to displayed on all admin pages, or null if none
*/
function qa_admin_page_error()
{
if (file_exists(QA_INCLUDE_DIR.'db/install.php')) // file can be removed for extra security
include_once QA_INCLUDE_DIR.'db/install.php';
$navigation['admin/stats']=array(
'label' => qa_lang_html('admin/stats_title'),
'url' => qa_path_html('admin/stats'),
);
if (defined('QA_DB_VERSION_CURRENT') && (qa_opt('db_version')<QA_DB_VERSION_CURRENT) && (qa_get_logged_in_level()>=QA_USER_LEVEL_ADMIN))
return strtr(
qa_lang_html('admin/upgrade_db'),
array(
'^1' => '<a href="'.qa_path_html('install').'">',
'^2' => '</a>',
)
);
if (!QA_FINAL_EXTERNAL_USERS)
$navigation['admin/mailing']=array(
'label' => qa_lang_html('admin/mailing_title'),
'url' => qa_path_html('admin/mailing'),
);
elseif (defined('QA_BLOBS_DIRECTORY') && !is_writable(QA_BLOBS_DIRECTORY))
return qa_lang_html_sub('admin/blobs_directory_error', qa_html(QA_BLOBS_DIRECTORY));
$navigation['admin/plugins']=array(
'label' => qa_lang_html('admin/plugins_title'),
'url' => qa_path_html('admin/plugins'),
);
}
else
return null;
}
if (!qa_user_maximum_permit_error('permit_moderate')) {
$count=qa_user_permit_error('permit_moderate') ? null : qa_opt('cache_queuedcount'); // if only in some categories don't show cached count
$navigation['admin/moderate']=array(
'label' => qa_lang_html('admin/moderate_title').($count ? (' ('.$count.')') : ''),
'url' => qa_path_html('admin/moderate'),
);
}
/**
* Return an HTML fragment to display for a URL test which has passed
*/
function qa_admin_url_test_html()
{
return '; font-size:9px; color:#060; font-weight:bold; font-family:arial,sans-serif; border-color:#060;">OK<';
}
if (qa_opt('flagging_of_posts') && !qa_user_maximum_permit_error('permit_hide_show')) {
$count=qa_user_permit_error('permit_hide_show') ? null : qa_opt('cache_flaggedcount'); // if only in some categories don't show cached count
$navigation['admin/flagged']=array(
'label' => qa_lang_html('admin/flagged_title').($count ? (' ('.$count.')') : ''),
'url' => qa_path_html('admin/flagged'),
);
}
/**
* Returns whether a URL path beginning with $requestpart is reserved by the engine or a plugin page module
*/
function qa_admin_is_slug_reserved($requestpart)
{
$requestpart=trim(strtolower($requestpart));
$routing=qa_page_routing();
if ( (!qa_user_maximum_permit_error('permit_hide_show')) || (!qa_user_maximum_permit_error('permit_delete_hidden')) )
$navigation['admin/hidden']=array(
'label' => qa_lang_html('admin/hidden_title'),
'url' => qa_path_html('admin/hidden'),
);
if (isset($routing[$requestpart]) || isset($routing[$requestpart.'/']) || is_numeric($requestpart))
return true;
if ( (!QA_FINAL_EXTERNAL_USERS) && qa_opt('moderate_users') && ($level>=QA_USER_LEVEL_MODERATOR)) {
$count=qa_opt('cache_uapprovecount');
$pathmap=qa_get_request_map();
$navigation['admin/approve']=array(
'label' => qa_lang_html('admin/approve_users_title').($count ? (' ('.$count.')') : ''),
'url' => qa_path_html('admin/approve'),
);
}
foreach ($pathmap as $mappedrequest)
if (trim(strtolower($mappedrequest)) == $requestpart)
return true;
return $navigation;
switch ($requestpart) {
case '':
case 'qa':
case 'feed':
case 'install':
case 'url':
case 'image':
case 'ajax':
return true;
}
$pagemodules=qa_load_modules_with('page', 'match_request');
foreach ($pagemodules as $pagemodule)
if ($pagemodule->match_request($requestpart))
return true;
function qa_admin_page_error()
/*
Return the error that needs to displayed on all admin pages, or null if none
*/
{
if (file_exists(QA_INCLUDE_DIR.'db/install.php')) // file can be removed for extra security
include_once QA_INCLUDE_DIR.'db/install.php';
if (defined('QA_DB_VERSION_CURRENT') && (qa_opt('db_version')<QA_DB_VERSION_CURRENT) && (qa_get_logged_in_level()>=QA_USER_LEVEL_ADMIN))
return strtr(
qa_lang_html('admin/upgrade_db'),
array(
'^1' => '<a href="'.qa_path_html('install').'">',
'^2' => '</a>',
)
);
return false;
}
elseif (defined('QA_BLOBS_DIRECTORY') && !is_writable(QA_BLOBS_DIRECTORY))
return qa_lang_html_sub('admin/blobs_directory_error', qa_html(QA_BLOBS_DIRECTORY));
else
return null;
}
/**
* Returns true if admin (hidden/flagged/approve/moderate) page $action performed on $entityid is permitted by the
* logged in user and was processed successfully
*/
function qa_admin_single_click($entityid, $action)
{
$userid=qa_get_logged_in_userid();
if ( (!QA_FINAL_EXTERNAL_USERS) && (($action=='userapprove') || ($action=='userblock')) ) { // approve/block moderated users
require_once QA_INCLUDE_DIR.'db/selects.php';
function qa_admin_url_test_html()
/*
Return an HTML fragment to display for a URL test which has passed
*/
{
return '; font-size:9px; color:#060; font-weight:bold; font-family:arial,sans-serif; border-color:#060;">OK<';
}
$useraccount=qa_db_select_with_pending(qa_db_user_account_selectspec($entityid, true));
if ( isset($useraccount) && (qa_get_logged_in_level()>=QA_USER_LEVEL_MODERATOR) )
switch ($action) {
case 'userapprove':
if ($useraccount['level']<=QA_USER_LEVEL_APPROVED) { // don't demote higher level users
require_once QA_INCLUDE_DIR.'app/users-edit.php';
qa_set_user_level($useraccount['userid'], $useraccount['handle'], QA_USER_LEVEL_APPROVED, $useraccount['level']);
return true;
}
break;
function qa_admin_is_slug_reserved($requestpart)
/*
Returns whether a URL path beginning with $requestpart is reserved by the engine or a plugin page module
*/
{
$requestpart=trim(strtolower($requestpart));
$routing=qa_page_routing();
case 'userblock':
require_once QA_INCLUDE_DIR.'app/users-edit.php';
qa_set_user_blocked($useraccount['userid'], $useraccount['handle'], true);
return true;
break;
}
if (isset($routing[$requestpart]) || isset($routing[$requestpart.'/']) || is_numeric($requestpart))
return true;
} else { // something to do with a post
require_once QA_INCLUDE_DIR.'app/posts.php';
$pathmap=qa_get_request_map();
foreach ($pathmap as $mappedrequest)
if (trim(strtolower($mappedrequest)) == $requestpart)
return true;
switch ($requestpart) {
case '':
case 'qa':
case 'feed':
case 'install':
case 'url':
case 'image':
case 'ajax':
return true;
}
$post=qa_post_get_full($entityid);
$pagemodules=qa_load_modules_with('page', 'match_request');
foreach ($pagemodules as $pagemodule)
if ($pagemodule->match_request($requestpart))
return true;
if (isset($post)) {
$queued=(substr($post['type'], 1)=='_QUEUED');
return false;
}
switch ($action) {
case 'approve':
if ($queued && !qa_user_post_permit_error('permit_moderate', $post)) {
qa_post_set_hidden($entityid, false, $userid);
return true;
}
break;
case 'reject':
if ($queued && !qa_user_post_permit_error('permit_moderate', $post)) {
qa_post_set_hidden($entityid, true, $userid);
return true;
}
break;
function qa_admin_single_click($entityid, $action)
/*
Returns true if admin (hidden/flagged/approve/moderate) page $action performed on $entityid is permitted by the
logged in user and was processed successfully
*/
{
$userid=qa_get_logged_in_userid();
case 'hide':
if ((!$queued) && !qa_user_post_permit_error('permit_hide_show', $post)) {
qa_post_set_hidden($entityid, true, $userid);
return true;
}
break;
if ( (!QA_FINAL_EXTERNAL_USERS) && (($action=='userapprove') || ($action=='userblock')) ) { // approve/block moderated users
require_once QA_INCLUDE_DIR.'db/selects.php';
case 'reshow':
if ($post['hidden'] && !qa_user_post_permit_error('permit_hide_show', $post)) {
qa_post_set_hidden($entityid, false, $userid);
return true;
}
break;
$useraccount=qa_db_select_with_pending(qa_db_user_account_selectspec($entityid, true));
case 'delete':
if ($post['hidden'] && !qa_user_post_permit_error('permit_delete_hidden', $post)) {
qa_post_delete($entityid);
return true;
}
break;
if ( isset($useraccount) && (qa_get_logged_in_level()>=QA_USER_LEVEL_MODERATOR) )
switch ($action) {
case 'userapprove':
if ($useraccount['level']<=QA_USER_LEVEL_APPROVED) { // don't demote higher level users
require_once QA_INCLUDE_DIR.'app/users-edit.php';
qa_set_user_level($useraccount['userid'], $useraccount['handle'], QA_USER_LEVEL_APPROVED, $useraccount['level']);
return true;
}
break;
case 'clearflags':
require_once QA_INCLUDE_DIR.'app/votes.php';
case 'userblock':
require_once QA_INCLUDE_DIR.'app/users-edit.php';
qa_set_user_blocked($useraccount['userid'], $useraccount['handle'], true);
if (!qa_user_post_permit_error('permit_hide_show', $post)) {
qa_flags_clear_all($post, $userid, qa_get_logged_in_handle(), null);
return true;
break;
}
} else { // something to do with a post
require_once QA_INCLUDE_DIR.'app/posts.php';
$post=qa_post_get_full($entityid);
if (isset($post)) {
$queued=(substr($post['type'], 1)=='_QUEUED');
switch ($action) {
case 'approve':
if ($queued && !qa_user_post_permit_error('permit_moderate', $post)) {
qa_post_set_hidden($entityid, false, $userid);
return true;
}
break;
case 'reject':
if ($queued && !qa_user_post_permit_error('permit_moderate', $post)) {
qa_post_set_hidden($entityid, true, $userid);
return true;
}
break;
case 'hide':
if ((!$queued) && !qa_user_post_permit_error('permit_hide_show', $post)) {
qa_post_set_hidden($entityid, true, $userid);
return true;
}
break;
case 'reshow':
if ($post['hidden'] && !qa_user_post_permit_error('permit_hide_show', $post)) {
qa_post_set_hidden($entityid, false, $userid);
return true;
}
break;
case 'delete':
if ($post['hidden'] && !qa_user_post_permit_error('permit_delete_hidden', $post)) {
qa_post_delete($entityid);
return true;
}
break;
case 'clearflags':
require_once QA_INCLUDE_DIR.'app/votes.php';
if (!qa_user_post_permit_error('permit_hide_show', $post)) {
qa_flags_clear_all($post, $userid, qa_get_logged_in_handle(), null);
return true;
}
break;
}
}
break;
}
}
return false;
}
function qa_admin_check_clicks()
/*
Checks for a POSTed click on an admin (hidden/flagged/approve/moderate) page, and refresh the page if processed successfully (non Ajax)
*/
{
if (qa_is_http_post())
foreach ($_POST as $field => $value)
if (strpos($field, 'admin_')===0) {
@list($dummy, $entityid, $action)=explode('_', $field);
if (strlen($entityid) && strlen($action)) {
if (!qa_check_form_security_code('admin/click', qa_post_text('code')))
return qa_lang_html('misc/form_security_again');
elseif (qa_admin_single_click($entityid, $action))
qa_redirect(qa_request());
}
return false;
}
/**
* Checks for a POSTed click on an admin (hidden/flagged/approve/moderate) page, and refresh the page if processed successfully (non Ajax)
*/
function qa_admin_check_clicks()
{
if (qa_is_http_post())
foreach ($_POST as $field => $value)
if (strpos($field, 'admin_')===0) {
@list($dummy, $entityid, $action)=explode('_', $field);
if (strlen($entityid) && strlen($action)) {
if (!qa_check_form_security_code('admin/click', qa_post_text('code')))
return qa_lang_html('misc/form_security_again');
elseif (qa_admin_single_click($entityid, $action))
qa_redirect(qa_request());
}
}
return null;
}
return null;
}
/**
* Retrieve metadata information from the $contents of a qa-theme.php or qa-plugin.php file, mapping via $fields.
*
* @deprecated Deprecated from 1.7; use `qa_addon_metadata($contents, $type)` instead.
*/
function qa_admin_addon_metadata($contents, $fields)
{
$metadata=array();
/**
* Retrieve metadata information from the $contents of a qa-theme.php or qa-plugin.php file, mapping via $fields.
*
* @deprecated Deprecated from 1.7; use `qa_addon_metadata($contents, $type)` instead.
*/
function qa_admin_addon_metadata($contents, $fields)
{
$metadata=array();
foreach ($fields as $key => $field)
if (preg_match('/'.str_replace(' ', '[ \t]*', preg_quote($field, '/')).':[ \t]*([^\n\f]*)[\n\f]/i', $contents, $matches))
$metadata[$key]=trim($matches[1]);
foreach ($fields as $key => $field)
if (preg_match('/'.str_replace(' ', '[ \t]*', preg_quote($field, '/')).':[ \t]*([^\n\f]*)[\n\f]/i', $contents, $matches))
$metadata[$key]=trim($matches[1]);
return $metadata;
}
/**
* Return the hash code for the plugin in $directory (without trailing slash), used for in-page navigation on admin/plugins page
*/
function qa_admin_plugin_directory_hash($directory)
{
$pluginManager = new Q2A_Plugin_PluginManager();
$hashes = $pluginManager->getHashesForPlugins(array($directory));
return $metadata;
}
return reset($hashes);
}
/**
* Return the hash code for the plugin in $directory (without trailing slash), used for in-page navigation on admin/plugins page
*/
function qa_admin_plugin_directory_hash($directory)
{
$pluginManager = new Q2A_Plugin_PluginManager();
$hashes = $pluginManager->getHashesForPlugins(array($directory));
/**
* Return the URL (relative to the current page) to navigate to the options panel for the plugin in $directory (without trailing slash)
*/
function qa_admin_plugin_options_path($directory)
{
$hash = qa_admin_plugin_directory_hash($directory);
return qa_path_html('admin/plugins', array('show' => $hash), null, null, $hash);
}
return reset($hashes);
}
/**
* Return the URL (relative to the current page) to navigate to the options panel for plugin module $name of $type
*/
function qa_admin_module_options_path($type, $name)
{
$info = qa_get_module_info($type, $name);
$dir = rtrim($info['directory'], '/');
/**
* Return the URL (relative to the current page) to navigate to the options panel for the plugin in $directory (without trailing slash)
*/
function qa_admin_plugin_options_path($directory)
{
$hash = qa_admin_plugin_directory_hash($directory);
return qa_path_html('admin/plugins', array('show' => $hash), null, null, $hash);
}
return qa_admin_plugin_options_path($dir);
}
/**
* Return the URL (relative to the current page) to navigate to the options panel for plugin module $name of $type
*/
function qa_admin_module_options_path($type, $name)
{
$info = qa_get_module_info($type, $name);
$dir = rtrim($info['directory'], '/');
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
return qa_admin_plugin_options_path($dir);
}
......@@ -20,255 +20,314 @@
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;
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../');
exit;
}
require_once QA_INCLUDE_DIR . 'db/maxima.php';
require_once QA_INCLUDE_DIR . 'db/post-create.php';
require_once QA_INCLUDE_DIR . 'db/points.php';
require_once QA_INCLUDE_DIR . 'db/hotness.php';
require_once QA_INCLUDE_DIR . 'util/string.php';
/**
* Return value to store in database combining $notify and $email values entered by user $userid (or null for anonymous)
* @param $userid
* @param $notify
* @param $email
* @return null|string
*/
function qa_combine_notify_email($userid, $notify, $email)
{
return $notify ? (empty($email) ? (isset($userid) ? '@' : null) : $email) : null;
}
/**
* Add a question (application level) - create record, update appropriate counts, index it, send notifications.
* If question is follow-on from an answer, $followanswer should contain answer database record, otherwise null.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $followanswer
* @param $userid
* @param $handle
* @param $cookieid
* @param $title
* @param $content
* @param $format
* @param $text
* @param $tagstring
* @param $notify
* @param $email
* @param $categoryid
* @param $extravalue
* @param bool $queued
* @param $name
* @return mixed
*/
function qa_question_create($followanswer, $userid, $handle, $cookieid, $title, $content, $format, $text, $tagstring, $notify, $email,
$categoryid = null, $extravalue = null, $queued = false, $name = null)
{
require_once QA_INCLUDE_DIR . 'db/selects.php';
$postid = qa_db_post_create($queued ? 'Q_QUEUED' : 'Q', @$followanswer['postid'], $userid, isset($userid) ? null : $cookieid,
qa_remote_ip_address(), $title, $content, $format, $tagstring, qa_combine_notify_email($userid, $notify, $email),
$categoryid, isset($userid) ? null : $name);
if (isset($extravalue)) {
require_once QA_INCLUDE_DIR . 'db/metas.php';
qa_db_postmeta_set($postid, 'qa_q_extra', $extravalue);
}
require_once QA_INCLUDE_DIR.'db/maxima.php';
require_once QA_INCLUDE_DIR.'db/post-create.php';
require_once QA_INCLUDE_DIR.'db/points.php';
require_once QA_INCLUDE_DIR.'db/hotness.php';
require_once QA_INCLUDE_DIR.'util/string.php';
qa_db_posts_calc_category_path($postid);
qa_db_hotness_update($postid);
if ($queued) {
qa_db_queuedcount_update();
function qa_combine_notify_email($userid, $notify, $email)
/*
Return value to store in database combining $notify and $email values entered by user $userid (or null for anonymous)
*/
{
return $notify ? (empty($email) ? (isset($userid) ? '@' : null) : $email) : null;
} else {
qa_post_index($postid, 'Q', $postid, @$followanswer['postid'], $title, $content, $format, $text, $tagstring, $categoryid);
qa_update_counts_for_q($postid);
qa_db_points_update_ifuser($userid, 'qposts');
}
function qa_question_create($followanswer, $userid, $handle, $cookieid, $title, $content, $format, $text, $tagstring, $notify, $email,
$categoryid=null, $extravalue=null, $queued=false, $name=null)
/*
Add a question (application level) - create record, update appropriate counts, index it, send notifications.
If question is follow-on from an answer, $followanswer should contain answer database record, otherwise null.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
require_once QA_INCLUDE_DIR.'db/selects.php';
$postid=qa_db_post_create($queued ? 'Q_QUEUED' : 'Q', @$followanswer['postid'], $userid, isset($userid) ? null : $cookieid,
qa_remote_ip_address(), $title, $content, $format, $tagstring, qa_combine_notify_email($userid, $notify, $email),
$categoryid, isset($userid) ? null : $name);
if (isset($extravalue)) {
require_once QA_INCLUDE_DIR.'db/metas.php';
qa_db_postmeta_set($postid, 'qa_q_extra', $extravalue);
}
qa_db_posts_calc_category_path($postid);
qa_db_hotness_update($postid);
if ($queued) {
qa_db_queuedcount_update();
} else {
qa_post_index($postid, 'Q', $postid, @$followanswer['postid'], $title, $content, $format, $text, $tagstring, $categoryid);
qa_update_counts_for_q($postid);
qa_db_points_update_ifuser($userid, 'qposts');
}
qa_report_event($queued ? 'q_queue' : 'q_post', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => @$followanswer['postid'],
'parent' => $followanswer,
'title' => $title,
'content' => $content,
'format' => $format,
'text' => $text,
'tags' => $tagstring,
'categoryid' => $categoryid,
'extra' => $extravalue,
'name' => $name,
'notify' => $notify,
'email' => $email,
));
return $postid;
qa_report_event($queued ? 'q_queue' : 'q_post', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => @$followanswer['postid'],
'parent' => $followanswer,
'title' => $title,
'content' => $content,
'format' => $format,
'text' => $text,
'tags' => $tagstring,
'categoryid' => $categoryid,
'extra' => $extravalue,
'name' => $name,
'notify' => $notify,
'email' => $email,
));
return $postid;
}
/**
* Perform various common cached count updating operations to reflect changes in the question whose id is $postid
* @param $postid
*/
function qa_update_counts_for_q($postid)
{
if (isset($postid)) // post might no longer exist
qa_db_category_path_qcount_update(qa_db_post_get_category_path($postid));
qa_db_qcount_update();
qa_db_unaqcount_update();
qa_db_unselqcount_update();
qa_db_unupaqcount_update();
}
/**
* Return an array containing the elements of $inarray whose key is in $keys
* @param $inarray
* @param $keys
* @return array
*/
function qa_array_filter_by_keys($inarray, $keys)
{
$outarray = array();
foreach ($keys as $key) {
if (isset($inarray[$key]))
$outarray[$key] = $inarray[$key];
}
function qa_update_counts_for_q($postid)
/*
Perform various common cached count updating operations to reflect changes in the question whose id is $postid
*/
{
if (isset($postid)) // post might no longer exist
qa_db_category_path_qcount_update(qa_db_post_get_category_path($postid));
qa_db_qcount_update();
qa_db_unaqcount_update();
qa_db_unselqcount_update();
qa_db_unupaqcount_update();
return $outarray;
}
/**
* Suspend the indexing (and unindexing) of posts via qa_post_index(...) and qa_post_unindex(...)
* if $suspend is true, otherwise reinstate it. A counter is kept to allow multiple calls.
* @param bool $suspend
*/
function qa_suspend_post_indexing($suspend = true)
{
global $qa_post_indexing_suspended;
$qa_post_indexing_suspended += ($suspend ? 1 : -1);
}
/**
* Add post $postid (which comes under $questionid) of $type (Q/A/C) to the database index, with $title, $text,
* $tagstring and $categoryid. Calls through to all installed search modules.
* @param $postid
* @param $type
* @param $questionid
* @param $parentid
* @param $title
* @param $content
* @param $format
* @param $text
* @param $tagstring
* @param $categoryid
*/
function qa_post_index($postid, $type, $questionid, $parentid, $title, $content, $format, $text, $tagstring, $categoryid)
{
global $qa_post_indexing_suspended;
if ($qa_post_indexing_suspended > 0)
return;
// Send through to any search modules for indexing
$searches = qa_load_modules_with('search', 'index_post');
foreach ($searches as $search)
$search->index_post($postid, $type, $questionid, $parentid, $title, $content, $format, $text, $tagstring, $categoryid);
}
/**
* Add an answer (application level) - create record, update appropriate counts, index it, send notifications.
* $question should contain database record for the question this is an answer to.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $userid
* @param $handle
* @param $cookieid
* @param $content
* @param $format
* @param $text
* @param $notify
* @param $email
* @param $question
* @param bool $queued
* @param $name
* @return mixed
*/
function qa_answer_create($userid, $handle, $cookieid, $content, $format, $text, $notify, $email, $question, $queued = false, $name = null)
{
$postid = qa_db_post_create($queued ? 'A_QUEUED' : 'A', $question['postid'], $userid, isset($userid) ? null : $cookieid,
qa_remote_ip_address(), null, $content, $format, null, qa_combine_notify_email($userid, $notify, $email),
$question['categoryid'], isset($userid) ? null : $name);
qa_db_posts_calc_category_path($postid);
if ($queued) {
qa_db_queuedcount_update();
} else {
if ($question['type'] == 'Q') // don't index answer if parent question is hidden or queued
qa_post_index($postid, 'A', $question['postid'], $question['postid'], null, $content, $format, $text, null, $question['categoryid']);
qa_update_q_counts_for_a($question['postid']);
qa_db_points_update_ifuser($userid, 'aposts');
}
function qa_array_filter_by_keys($inarray, $keys)
/*
Return an array containing the elements of $inarray whose key is in $keys
*/
{
$outarray=array();
foreach ($keys as $key)
if (isset($inarray[$key]))
$outarray[$key]=$inarray[$key];
return $outarray;
qa_report_event($queued ? 'a_queue' : 'a_post', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => $question['postid'],
'parent' => $question,
'content' => $content,
'format' => $format,
'text' => $text,
'categoryid' => $question['categoryid'],
'name' => $name,
'notify' => $notify,
'email' => $email,
));
return $postid;
}
/**
* Perform various common cached count updating operations to reflect changes in an answer of question $questionid
* @param $questionid
*/
function qa_update_q_counts_for_a($questionid)
{
qa_db_post_acount_update($questionid);
qa_db_hotness_update($questionid);
qa_db_acount_update();
qa_db_unaqcount_update();
qa_db_unupaqcount_update();
}
/**
* Add a comment (application level) - create record, update appropriate counts, index it, send notifications.
* $question should contain database record for the question this is part of (as direct or comment on Q's answer).
* If this is a comment on an answer, $answer should contain database record for the answer, otherwise null.
* $commentsfollows should contain database records for all previous comments on the same question or answer,
* but it can also contain other records that are ignored.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $userid
* @param $handle
* @param $cookieid
* @param $content
* @param $format
* @param $text
* @param $notify
* @param $email
* @param $question
* @param $parent
* @param $commentsfollows
* @param bool $queued
* @param $name
* @return mixed
*/
function qa_comment_create($userid, $handle, $cookieid, $content, $format, $text, $notify, $email, $question, $parent, $commentsfollows, $queued = false, $name = null)
{
require_once QA_INCLUDE_DIR . 'app/emails.php';
require_once QA_INCLUDE_DIR . 'app/options.php';
require_once QA_INCLUDE_DIR . 'app/format.php';
require_once QA_INCLUDE_DIR . 'util/string.php';
if (!isset($parent))
$parent = $question; // for backwards compatibility with old answer parameter
$postid = qa_db_post_create($queued ? 'C_QUEUED' : 'C', $parent['postid'], $userid, isset($userid) ? null : $cookieid,
qa_remote_ip_address(), null, $content, $format, null, qa_combine_notify_email($userid, $notify, $email),
$question['categoryid'], isset($userid) ? null : $name);
qa_db_posts_calc_category_path($postid);
if ($queued) {
qa_db_queuedcount_update();
} else {
if (($question['type'] == 'Q') && (($parent['type'] == 'Q') || ($parent['type'] == 'A'))) // only index if antecedents fully visible
qa_post_index($postid, 'C', $question['postid'], $parent['postid'], null, $content, $format, $text, null, $question['categoryid']);
qa_db_points_update_ifuser($userid, 'cposts');
qa_db_ccount_update();
}
$thread = array();
function qa_suspend_post_indexing($suspend=true)
/*
Suspend the indexing (and unindexing) of posts via qa_post_index(...) and qa_post_unindex(...)
if $suspend is true, otherwise reinstate it. A counter is kept to allow multiple calls.
*/
{
global $qa_post_indexing_suspended;
$qa_post_indexing_suspended+=($suspend ? 1 : -1);
foreach ($commentsfollows as $comment) {
if (($comment['type'] == 'C') && ($comment['parentid'] == $parent['postid'])) // find just those for this parent, fully visible
$thread[] = $comment;
}
function qa_post_index($postid, $type, $questionid, $parentid, $title, $content, $format, $text, $tagstring, $categoryid)
/*
Add post $postid (which comes under $questionid) of $type (Q/A/C) to the database index, with $title, $text,
$tagstring and $categoryid. Calls through to all installed search modules.
*/
{
global $qa_post_indexing_suspended;
if ($qa_post_indexing_suspended>0)
return;
// Send through to any search modules for indexing
$searches=qa_load_modules_with('search', 'index_post');
foreach ($searches as $search)
$search->index_post($postid, $type, $questionid, $parentid, $title, $content, $format, $text, $tagstring, $categoryid);
}
function qa_answer_create($userid, $handle, $cookieid, $content, $format, $text, $notify, $email, $question, $queued=false, $name=null)
/*
Add an answer (application level) - create record, update appropriate counts, index it, send notifications.
$question should contain database record for the question this is an answer to.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
$postid=qa_db_post_create($queued ? 'A_QUEUED' : 'A', $question['postid'], $userid, isset($userid) ? null : $cookieid,
qa_remote_ip_address(), null, $content, $format, null, qa_combine_notify_email($userid, $notify, $email),
$question['categoryid'], isset($userid) ? null : $name);
qa_db_posts_calc_category_path($postid);
if ($queued) {
qa_db_queuedcount_update();
} else {
if ($question['type']=='Q') // don't index answer if parent question is hidden or queued
qa_post_index($postid, 'A', $question['postid'], $question['postid'], null, $content, $format, $text, null, $question['categoryid']);
qa_update_q_counts_for_a($question['postid']);
qa_db_points_update_ifuser($userid, 'aposts');
}
qa_report_event($queued ? 'a_queue' : 'a_post', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => $question['postid'],
'parent' => $question,
'content' => $content,
'format' => $format,
'text' => $text,
'categoryid' => $question['categoryid'],
'name' => $name,
'notify' => $notify,
'email' => $email,
));
return $postid;
}
function qa_update_q_counts_for_a($questionid)
/*
Perform various common cached count updating operations to reflect changes in an answer of question $questionid
*/
{
qa_db_post_acount_update($questionid);
qa_db_hotness_update($questionid);
qa_db_acount_update();
qa_db_unaqcount_update();
qa_db_unupaqcount_update();
}
function qa_comment_create($userid, $handle, $cookieid, $content, $format, $text, $notify, $email, $question, $parent, $commentsfollows, $queued=false, $name=null)
/*
Add a comment (application level) - create record, update appropriate counts, index it, send notifications.
$question should contain database record for the question this is part of (as direct or comment on Q's answer).
If this is a comment on an answer, $answer should contain database record for the answer, otherwise null.
$commentsfollows should contain database records for all previous comments on the same question or answer,
but it can also contain other records that are ignored.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
require_once QA_INCLUDE_DIR.'app/emails.php';
require_once QA_INCLUDE_DIR.'app/options.php';
require_once QA_INCLUDE_DIR.'app/format.php';
require_once QA_INCLUDE_DIR.'util/string.php';
if (!isset($parent))
$parent=$question; // for backwards compatibility with old answer parameter
$postid=qa_db_post_create($queued ? 'C_QUEUED' : 'C', $parent['postid'], $userid, isset($userid) ? null : $cookieid,
qa_remote_ip_address(), null, $content, $format, null, qa_combine_notify_email($userid, $notify, $email),
$question['categoryid'], isset($userid) ? null : $name);
qa_db_posts_calc_category_path($postid);
if ($queued) {
qa_db_queuedcount_update();
} else {
if ( ($question['type']=='Q') && (($parent['type']=='Q') || ($parent['type']=='A')) ) // only index if antecedents fully visible
qa_post_index($postid, 'C', $question['postid'], $parent['postid'], null, $content, $format, $text, null, $question['categoryid']);
qa_db_points_update_ifuser($userid, 'cposts');
qa_db_ccount_update();
}
$thread=array();
foreach ($commentsfollows as $comment)
if (($comment['type']=='C') && ($comment['parentid']==$parent['postid'])) // find just those for this parent, fully visible
$thread[]=$comment;
qa_report_event($queued ? 'c_queue' : 'c_post', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => $parent['postid'],
'parenttype' => $parent['basetype'],
'parent' => $parent,
'questionid' => $question['postid'],
'question' => $question,
'thread' => $thread,
'content' => $content,
'format' => $format,
'text' => $text,
'categoryid' => $question['categoryid'],
'name' => $name,
'notify' => $notify,
'email' => $email,
));
return $postid;
}
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
qa_report_event($queued ? 'c_queue' : 'c_post', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => $parent['postid'],
'parenttype' => $parent['basetype'],
'parent' => $parent,
'questionid' => $question['postid'],
'question' => $question,
'thread' => $thread,
'content' => $content,
'format' => $format,
'text' => $text,
'categoryid' => $question['categoryid'],
'name' => $name,
'notify' => $notify,
'email' => $email,
));
return $postid;
}
......@@ -20,1069 +20,1249 @@
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;
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../');
exit;
}
require_once QA_INCLUDE_DIR . 'app/post-create.php';
require_once QA_INCLUDE_DIR . 'app/updates.php';
require_once QA_INCLUDE_DIR . 'db/post-create.php';
require_once QA_INCLUDE_DIR . 'db/post-update.php';
require_once QA_INCLUDE_DIR . 'db/points.php';
require_once QA_INCLUDE_DIR . 'db/hotness.php';
define('QA_POST_STATUS_NORMAL', 0);
define('QA_POST_STATUS_HIDDEN', 1);
define('QA_POST_STATUS_QUEUED', 2);
/**
* Change the fields of a question (application level) to $title, $content, $format, $tagstring, $notify, $extravalue
* and $name, then reindex based on $text. For backwards compatibility if $name is null then the name will not be
* changed. Pass the question's database record before changes in $oldquestion and details of the user doing this in
* $userid, $handle and $cookieid. Set $remoderate to true if the question should be requeued for moderation if
* modified. Set $silent to true to not mark the question as edited. Reports event as appropriate. See qa-app-posts.php
* for a higher-level function which is easier to use.
* @param $oldquestion
* @param $title
* @param $content
* @param $format
* @param $text
* @param $tagstring
* @param $notify
* @param $userid
* @param $handle
* @param $cookieid
* @param $extravalue
* @param $name
* @param bool $remoderate
* @param bool $silent
*/
function qa_question_set_content($oldquestion, $title, $content, $format, $text, $tagstring, $notify, $userid, $handle, $cookieid, $extravalue = null, $name = null, $remoderate = false, $silent = false)
{
qa_post_unindex($oldquestion['postid']);
$wasqueued = ($oldquestion['type'] == 'Q_QUEUED');
$titlechanged = strcmp($oldquestion['title'], $title) !== 0;
$contentchanged = strcmp($oldquestion['content'], $content) !== 0 || strcmp($oldquestion['format'], $format) !== 0;
$tagschanged = strcmp($oldquestion['tags'], $tagstring) !== 0;
$setupdated = ($titlechanged || $contentchanged || $tagschanged) && (!$wasqueued) && !$silent;
qa_db_post_set_content($oldquestion['postid'], $title, $content, $format, $tagstring, $notify,
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null,
($titlechanged || $contentchanged) ? QA_UPDATE_CONTENT : QA_UPDATE_TAGS, $name);
if (isset($extravalue)) {
require_once QA_INCLUDE_DIR . 'db/metas.php';
qa_db_postmeta_set($oldquestion['postid'], 'qa_q_extra', $extravalue);
}
require_once QA_INCLUDE_DIR.'app/post-create.php';
require_once QA_INCLUDE_DIR.'app/updates.php';
require_once QA_INCLUDE_DIR.'db/post-create.php';
require_once QA_INCLUDE_DIR.'db/post-update.php';
require_once QA_INCLUDE_DIR.'db/points.php';
require_once QA_INCLUDE_DIR.'db/hotness.php';
if ($setupdated && $remoderate) {
require_once QA_INCLUDE_DIR . 'app/posts.php';
$answers = qa_post_get_question_answers($oldquestion['postid']);
$commentsfollows = qa_post_get_question_commentsfollows($oldquestion['postid']);
$closepost = qa_post_get_question_closepost($oldquestion['postid']);
define('QA_POST_STATUS_NORMAL', 0);
define('QA_POST_STATUS_HIDDEN', 1);
define('QA_POST_STATUS_QUEUED', 2);
foreach ($answers as $answer)
qa_post_unindex($answer['postid']);
function qa_question_set_content($oldquestion, $title, $content, $format, $text, $tagstring, $notify, $userid, $handle, $cookieid, $extravalue=null, $name=null, $remoderate=false, $silent=false)
/*
Change the fields of a question (application level) to $title, $content, $format, $tagstring, $notify, $extravalue
and $name, then reindex based on $text. For backwards compatibility if $name is null then the name will not be
changed. Pass the question's database record before changes in $oldquestion and details of the user doing this in
$userid, $handle and $cookieid. Set $remoderate to true if the question should be requeued for moderation if
modified. Set $silent to true to not mark the question as edited. Reports event as appropriate. See qa-app-posts.php
for a higher-level function which is easier to use.
*/
{
qa_post_unindex($oldquestion['postid']);
$wasqueued = ($oldquestion['type'] == 'Q_QUEUED');
$titlechanged = strcmp($oldquestion['title'], $title) !== 0;
$contentchanged = strcmp($oldquestion['content'], $content) !== 0 || strcmp($oldquestion['format'], $format) !== 0;
$tagschanged = strcmp($oldquestion['tags'], $tagstring) !== 0;
$setupdated = ($titlechanged || $contentchanged || $tagschanged) && (!$wasqueued) && !$silent;
qa_db_post_set_content($oldquestion['postid'], $title, $content, $format, $tagstring, $notify,
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null,
($titlechanged || $contentchanged) ? QA_UPDATE_CONTENT : QA_UPDATE_TAGS, $name);
if (isset($extravalue)) {
require_once QA_INCLUDE_DIR.'db/metas.php';
qa_db_postmeta_set($oldquestion['postid'], 'qa_q_extra', $extravalue);
foreach ($commentsfollows as $comment) {
if ($comment['basetype'] == 'C')
qa_post_unindex($comment['postid']);
}
if ($setupdated && $remoderate) {
require_once QA_INCLUDE_DIR.'app/posts.php';
$answers = qa_post_get_question_answers($oldquestion['postid']);
$commentsfollows = qa_post_get_question_commentsfollows($oldquestion['postid']);
$closepost = qa_post_get_question_closepost($oldquestion['postid']);
foreach ($answers as $answer)
qa_post_unindex($answer['postid']);
foreach ($commentsfollows as $comment) {
if ($comment['basetype'] == 'C')
qa_post_unindex($comment['postid']);
}
if (@$closepost['parentid'] == $oldquestion['postid'])
qa_post_unindex($closepost['postid']);
if (@$closepost['parentid'] == $oldquestion['postid'])
qa_post_unindex($closepost['postid']);
qa_db_post_set_type($oldquestion['postid'], 'Q_QUEUED');
qa_update_counts_for_q($oldquestion['postid']);
qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects'));
qa_db_post_set_type($oldquestion['postid'], 'Q_QUEUED');
qa_update_counts_for_q($oldquestion['postid']);
qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects'));
if ($oldquestion['flagcount'])
qa_db_flaggedcount_update();
if ($oldquestion['flagcount'])
qa_db_flaggedcount_update();
}
elseif ($oldquestion['type'] == 'Q') { // not hidden or queued
qa_post_index($oldquestion['postid'], 'Q', $oldquestion['postid'], $oldquestion['parentid'], $title, $content, $format, $text, $tagstring, $oldquestion['categoryid']);
}
} elseif ($oldquestion['type'] == 'Q') { // not hidden or queued
qa_post_index($oldquestion['postid'], 'Q', $oldquestion['postid'], $oldquestion['parentid'], $title, $content, $format, $text, $tagstring, $oldquestion['categoryid']);
}
$eventparams=array(
'postid' => $oldquestion['postid'],
'title' => $title,
'content' => $content,
'format' => $format,
'text' => $text,
'tags' => $tagstring,
'extra' => $extravalue,
'name' => $name,
'oldquestion' => $oldquestion,
);
qa_report_event('q_edit', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent,
'oldtitle' => $oldquestion['title'],
'oldcontent' => $oldquestion['content'],
'oldformat' => $oldquestion['format'],
'oldtags' => $oldquestion['tags'],
'titlechanged' => $titlechanged,
'contentchanged' => $contentchanged,
'tagschanged' => $tagschanged,
$eventparams = array(
'postid' => $oldquestion['postid'],
'title' => $title,
'content' => $content,
'format' => $format,
'text' => $text,
'tags' => $tagstring,
'extra' => $extravalue,
'name' => $name,
'oldquestion' => $oldquestion,
);
qa_report_event('q_edit', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent,
'oldtitle' => $oldquestion['title'],
'oldcontent' => $oldquestion['content'],
'oldformat' => $oldquestion['format'],
'oldtags' => $oldquestion['tags'],
'titlechanged' => $titlechanged,
'contentchanged' => $contentchanged,
'tagschanged' => $tagschanged,
));
if ($setupdated && $remoderate)
qa_report_event('q_requeue', $userid, $handle, $cookieid, $eventparams);
}
/**
* Set the selected answer (application level) of $oldquestion to $selchildid. Pass details of the user doing this
* in $userid, $handle and $cookieid, and the database records for the selected and deselected answers in $answers.
* Handles user points values and notifications.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $userid
* @param $handle
* @param $cookieid
* @param $oldquestion
* @param $selchildid
* @param $answers
*/
function qa_question_set_selchildid($userid, $handle, $cookieid, $oldquestion, $selchildid, $answers)
{
$oldselchildid = $oldquestion['selchildid'];
qa_db_post_set_selchildid($oldquestion['postid'], isset($selchildid) ? $selchildid : null, $userid, qa_remote_ip_address());
qa_db_points_update_ifuser($oldquestion['userid'], 'aselects');
qa_db_unselqcount_update();
if (isset($oldselchildid) && isset($answers[$oldselchildid])) {
qa_db_points_update_ifuser($answers[$oldselchildid]['userid'], 'aselecteds');
qa_report_event('a_unselect', $userid, $handle, $cookieid, array(
'parentid' => $oldquestion['postid'],
'parent' => $oldquestion,
'postid' => $oldselchildid,
'answer' => $answers[$oldselchildid],
));
if ($setupdated && $remoderate)
qa_report_event('q_requeue', $userid, $handle, $cookieid, $eventparams);
}
if (isset($selchildid)) {
qa_db_points_update_ifuser($answers[$selchildid]['userid'], 'aselecteds');
function qa_question_set_selchildid($userid, $handle, $cookieid, $oldquestion, $selchildid, $answers)
/*
Set the selected answer (application level) of $oldquestion to $selchildid. Pass details of the user doing this
in $userid, $handle and $cookieid, and the database records for the selected and deselected answers in $answers.
Handles user points values and notifications.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
$oldselchildid=$oldquestion['selchildid'];
qa_db_post_set_selchildid($oldquestion['postid'], isset($selchildid) ? $selchildid : null, $userid, qa_remote_ip_address());
qa_db_points_update_ifuser($oldquestion['userid'], 'aselects');
qa_db_unselqcount_update();
if (isset($oldselchildid) && isset($answers[$oldselchildid])) {
qa_db_points_update_ifuser($answers[$oldselchildid]['userid'], 'aselecteds');
qa_report_event('a_unselect', $userid, $handle, $cookieid, array(
'parentid' => $oldquestion['postid'],
'parent' => $oldquestion,
'postid' => $oldselchildid,
'answer' => $answers[$oldselchildid],
));
}
if (isset($selchildid)) {
qa_db_points_update_ifuser($answers[$selchildid]['userid'], 'aselecteds');
qa_report_event('a_select', $userid, $handle, $cookieid, array(
'parentid' => $oldquestion['postid'],
'parent' => $oldquestion,
'postid' => $selchildid,
'answer' => $answers[$selchildid],
));
}
qa_report_event('a_select', $userid, $handle, $cookieid, array(
'parentid' => $oldquestion['postid'],
'parent' => $oldquestion,
'postid' => $selchildid,
'answer' => $answers[$selchildid],
));
}
function qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid)
/*
Reopen $oldquestion if it was closed. Pass details of the user doing this in $userid, $handle and $cookieid, and the
$oldclosepost (to match $oldquestion['closedbyid']) if any.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
if (isset($oldquestion['closedbyid'])) {
qa_db_post_set_closed($oldquestion['postid'], null, $userid, qa_remote_ip_address());
if (isset($oldclosepost) && ($oldclosepost['parentid']==$oldquestion['postid'])) {
qa_post_unindex($oldclosepost['postid']);
qa_db_post_delete($oldclosepost['postid']);
}
qa_report_event('q_reopen', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
));
}
/**
* Reopen $oldquestion if it was closed. Pass details of the user doing this in $userid, $handle and $cookieid, and the
* $oldclosepost (to match $oldquestion['closedbyid']) if any.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldquestion
* @param $oldclosepost
* @param $userid
* @param $handle
* @param $cookieid
*/
function qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid)
{
if (isset($oldquestion['closedbyid'])) {
qa_db_post_set_closed($oldquestion['postid'], null, $userid, qa_remote_ip_address());
if (isset($oldclosepost) && ($oldclosepost['parentid'] == $oldquestion['postid'])) {
qa_post_unindex($oldclosepost['postid']);
qa_db_post_delete($oldclosepost['postid']);
}
}
function qa_question_close_duplicate($oldquestion, $oldclosepost, $originalpostid, $userid, $handle, $cookieid)
/*
Close $oldquestion as a duplicate of the question with id $originalpostid. Pass details of the user doing this in
$userid, $handle and $cookieid, and the $oldclosepost (to match $oldquestion['closedbyid']) if any. See
qa-app-posts.php for a higher-level function which is easier to use.
*/
{
qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid);
qa_db_post_set_closed($oldquestion['postid'], $originalpostid, $userid, qa_remote_ip_address());
qa_report_event('q_close', $userid, $handle, $cookieid, array(
qa_report_event('q_reopen', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
'reason' => 'duplicate',
'originalid' => $originalpostid,
));
}
function qa_question_close_other($oldquestion, $oldclosepost, $note, $userid, $handle, $cookieid)
/*
Close $oldquestion with the reason given in $note. Pass details of the user doing this in $userid, $handle and
$cookieid, and the $oldclosepost (to match $oldquestion['closedbyid']) if any.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid);
$postid=qa_db_post_create('NOTE', $oldquestion['postid'], $userid, isset($userid) ? null : $cookieid,
qa_remote_ip_address(), null, $note, '', null, null, $oldquestion['categoryid']);
qa_db_posts_calc_category_path($postid);
if ($oldquestion['type']=='Q')
qa_post_index($postid, 'NOTE', $oldquestion['postid'], $oldquestion['postid'], null, $note, '', $note, null, $oldquestion['categoryid']);
qa_db_post_set_closed($oldquestion['postid'], $postid, $userid, qa_remote_ip_address());
qa_report_event('q_close', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
'reason' => 'other',
'note' => $note,
));
}
/**
* Close $oldquestion as a duplicate of the question with id $originalpostid. Pass details of the user doing this in
* $userid, $handle and $cookieid, and the $oldclosepost (to match $oldquestion['closedbyid']) if any. See
* qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldquestion
* @param $oldclosepost
* @param $originalpostid
* @param $userid
* @param $handle
* @param $cookieid
*/
function qa_question_close_duplicate($oldquestion, $oldclosepost, $originalpostid, $userid, $handle, $cookieid)
{
qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid);
qa_db_post_set_closed($oldquestion['postid'], $originalpostid, $userid, qa_remote_ip_address());
qa_report_event('q_close', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
'reason' => 'duplicate',
'originalid' => $originalpostid,
));
}
/**
* Close $oldquestion with the reason given in $note. Pass details of the user doing this in $userid, $handle and
* $cookieid, and the $oldclosepost (to match $oldquestion['closedbyid']) if any.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldquestion
* @param $oldclosepost
* @param $note
* @param $userid
* @param $handle
* @param $cookieid
*/
function qa_question_close_other($oldquestion, $oldclosepost, $note, $userid, $handle, $cookieid)
{
qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid);
$postid = qa_db_post_create('NOTE', $oldquestion['postid'], $userid, isset($userid) ? null : $cookieid,
qa_remote_ip_address(), null, $note, '', null, null, $oldquestion['categoryid']);
qa_db_posts_calc_category_path($postid);
if ($oldquestion['type'] == 'Q')
qa_post_index($postid, 'NOTE', $oldquestion['postid'], $oldquestion['postid'], null, $note, '', $note, null, $oldquestion['categoryid']);
qa_db_post_set_closed($oldquestion['postid'], $postid, $userid, qa_remote_ip_address());
qa_report_event('q_close', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
'reason' => 'other',
'note' => $note,
));
}
/**
* Set $oldquestion to hidden if $hidden is true, visible/normal if otherwise. All other parameters are as for qa_question_set_status(...)
* This function is included mainly for backwards compatibility.
* @param $oldquestion
* @param $hidden
* @param $userid
* @param $handle
* @param $cookieid
* @param $answers
* @param $commentsfollows
* @param $closepost
*/
function qa_question_set_hidden($oldquestion, $hidden, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost = null)
{
qa_question_set_status($oldquestion, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost);
}
/**
* Set the status (application level) of $oldquestion to $status, one of the QA_POST_STATUS_* constants above. Pass
* details of the user doing this in $userid, $handle and $cookieid, the database records for all answers to the
* question in $answers, the database records for all comments on the question or the question's answers in
* $commentsfollows ($commentsfollows can also contain records for follow-on questions which are ignored), and
* $closepost to match $oldquestion['closedbyid'] (if any). Handles indexing, user points, cached counts and event
* reports. See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldquestion
* @param $status
* @param $userid
* @param $handle
* @param $cookieid
* @param $answers
* @param $commentsfollows
* @param $closepost
*/
function qa_question_set_status($oldquestion, $status, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost = null)
{
require_once QA_INCLUDE_DIR . 'app/format.php';
require_once QA_INCLUDE_DIR . 'app/updates.php';
$washidden = ($oldquestion['type'] == 'Q_HIDDEN');
$wasqueued = ($oldquestion['type'] == 'Q_QUEUED');
$wasrequeued = $wasqueued && isset($oldquestion['updated']);
qa_post_unindex($oldquestion['postid']);
foreach ($answers as $answer) {
qa_post_unindex($answer['postid']);
}
function qa_question_set_hidden($oldquestion, $hidden, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost=null)
/*
Set $oldquestion to hidden if $hidden is true, visible/normal if otherwise. All other parameters are as for qa_question_set_status(...)
This function is included mainly for backwards compatibility.
*/
{
qa_question_set_status($oldquestion, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost);
foreach ($commentsfollows as $comment) {
if ($comment['basetype'] == 'C')
qa_post_unindex($comment['postid']);
}
if (@$closepost['parentid'] == $oldquestion['postid'])
qa_post_unindex($closepost['postid']);
function qa_question_set_status($oldquestion, $status, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost=null)
/*
Set the status (application level) of $oldquestion to $status, one of the QA_POST_STATUS_* constants above. Pass
details of the user doing this in $userid, $handle and $cookieid, the database records for all answers to the
question in $answers, the database records for all comments on the question or the question's answers in
$commentsfollows ($commentsfollows can also contain records for follow-on questions which are ignored), and
$closepost to match $oldquestion['closedbyid'] (if any). Handles indexing, user points, cached counts and event
reports. See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
require_once QA_INCLUDE_DIR.'app/format.php';
require_once QA_INCLUDE_DIR.'app/updates.php';
$setupdated = false;
$event = null;
$washidden=($oldquestion['type']=='Q_HIDDEN');
$wasqueued=($oldquestion['type']=='Q_QUEUED');
$wasrequeued=$wasqueued && isset($oldquestion['updated']);
if ($status == QA_POST_STATUS_QUEUED) {
$newtype = 'Q_QUEUED';
if (!$wasqueued)
$event = 'q_requeue'; // same event whether it was hidden or shown before
qa_post_unindex($oldquestion['postid']);
foreach ($answers as $answer)
qa_post_unindex($answer['postid']);
foreach ($commentsfollows as $comment)
if ($comment['basetype']=='C')
qa_post_unindex($comment['postid']);
if (@$closepost['parentid']==$oldquestion['postid'])
qa_post_unindex($closepost['postid']);
$setupdated=false;
$event=null;
if ($status==QA_POST_STATUS_QUEUED) {
$newtype='Q_QUEUED';
} elseif ($status == QA_POST_STATUS_HIDDEN) {
$newtype = 'Q_HIDDEN';
if (!$washidden) {
$event = $wasqueued ? 'q_reject' : 'q_hide';
if (!$wasqueued)
$event='q_requeue'; // same event whether it was hidden or shown before
} elseif ($status==QA_POST_STATUS_HIDDEN) {
$newtype='Q_HIDDEN';
if (!$washidden) {
$event=$wasqueued ? 'q_reject' : 'q_hide';
if (!$wasqueued)
$setupdated=true;
}
$setupdated = true;
}
} elseif ($status==QA_POST_STATUS_NORMAL) {
$newtype='Q';
if ($wasqueued)
$event='q_approve';
elseif ($washidden) {
$event='q_reshow';
$setupdated=true;
}
} elseif ($status == QA_POST_STATUS_NORMAL) {
$newtype = 'Q';
if ($wasqueued)
$event = 'q_approve';
elseif ($washidden) {
$event = 'q_reshow';
$setupdated = true;
}
} else
qa_fatal_error('Unknown status in qa_question_set_status(): '.$status);
} else
qa_fatal_error('Unknown status in qa_question_set_status(): ' . $status);
qa_db_post_set_type($oldquestion['postid'], $newtype, $setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_VISIBLE);
qa_db_post_set_type($oldquestion['postid'], $newtype, $setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_VISIBLE);
if ( $wasqueued && ($status==QA_POST_STATUS_NORMAL) && qa_opt('moderate_update_time') ) { // ... for approval of a post, can set time to now instead
if ($wasrequeued) // reset edit time to now if there was one, since we're approving the edit...
qa_db_post_set_updated($oldquestion['postid'], null);
if ($wasqueued && $status == QA_POST_STATUS_NORMAL && qa_opt('moderate_update_time')) { // ... for approval of a post, can set time to now instead
if ($wasrequeued) // reset edit time to now if there was one, since we're approving the edit...
qa_db_post_set_updated($oldquestion['postid'], null);
else { // ... otherwise we're approving original created post
qa_db_post_set_created($oldquestion['postid'], null);
qa_db_hotness_update($oldquestion['postid']);
}
else { // ... otherwise we're approving original created post
qa_db_post_set_created($oldquestion['postid'], null);
qa_db_hotness_update($oldquestion['postid']);
}
}
qa_update_counts_for_q($oldquestion['postid']);
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects'));
qa_update_counts_for_q($oldquestion['postid']);
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects'));
if ($wasqueued || ($status==QA_POST_STATUS_QUEUED))
qa_db_queuedcount_update();
if ($wasqueued || ($status == QA_POST_STATUS_QUEUED))
qa_db_queuedcount_update();
if ($oldquestion['flagcount'])
qa_db_flaggedcount_update();
if ($oldquestion['flagcount'])
qa_db_flaggedcount_update();
if ($status==QA_POST_STATUS_NORMAL) {
qa_post_index($oldquestion['postid'], 'Q', $oldquestion['postid'], $oldquestion['parentid'], $oldquestion['title'], $oldquestion['content'],
$oldquestion['format'], qa_viewer_text($oldquestion['content'], $oldquestion['format']), $oldquestion['tags'], $oldquestion['categoryid']);
if ($status == QA_POST_STATUS_NORMAL) {
qa_post_index($oldquestion['postid'], 'Q', $oldquestion['postid'], $oldquestion['parentid'], $oldquestion['title'], $oldquestion['content'],
$oldquestion['format'], qa_viewer_text($oldquestion['content'], $oldquestion['format']), $oldquestion['tags'], $oldquestion['categoryid']);
foreach ($answers as $answer)
if ($answer['type']=='A') // even if question visible, don't index hidden or queued answers
qa_post_index($answer['postid'], $answer['type'], $oldquestion['postid'], $answer['parentid'], null,
$answer['content'], $answer['format'], qa_viewer_text($answer['content'], $answer['format']), null, $answer['categoryid']);
foreach ($answers as $answer) {
if ($answer['type'] == 'A') { // even if question visible, don't index hidden or queued answers
qa_post_index($answer['postid'], $answer['type'], $oldquestion['postid'], $answer['parentid'], null,
$answer['content'], $answer['format'], qa_viewer_text($answer['content'], $answer['format']), null, $answer['categoryid']);
}
}
foreach ($commentsfollows as $comment)
if ($comment['type']=='C') {
$answer=@$answers[$comment['parentid']];
foreach ($commentsfollows as $comment) {
if ($comment['type'] == 'C') {
$answer = @$answers[$comment['parentid']];
if ( (!isset($answer)) || ($answer['type']=='A') ) // don't index comment if it or its parent is hidden
qa_post_index($comment['postid'], $comment['type'], $oldquestion['postid'], $comment['parentid'], null,
$comment['content'], $comment['format'], qa_viewer_text($comment['content'], $comment['format']), null, $comment['categoryid']);
if ((!isset($answer)) || ($answer['type'] == 'A')) { // don't index comment if it or its parent is hidden
qa_post_index($comment['postid'], $comment['type'], $oldquestion['postid'], $comment['parentid'], null,
$comment['content'], $comment['format'], qa_viewer_text($comment['content'], $comment['format']), null, $comment['categoryid']);
}
}
}
if ($closepost['parentid']==$oldquestion['postid'])
qa_post_index($closepost['postid'], $closepost['type'], $oldquestion['postid'], $closepost['parentid'], null,
$closepost['content'], $closepost['format'], qa_viewer_text($closepost['content'], $closepost['format']), null, $closepost['categoryid']);
if ($closepost['parentid'] == $oldquestion['postid']) {
qa_post_index($closepost['postid'], $closepost['type'], $oldquestion['postid'], $closepost['parentid'], null,
$closepost['content'], $closepost['format'], qa_viewer_text($closepost['content'], $closepost['format']), null, $closepost['categoryid']);
}
}
$eventparams=array(
'postid' => $oldquestion['postid'],
'parentid' => $oldquestion['parentid'],
'parent' => isset($oldquestion['parentid']) ? qa_db_single_select(qa_db_full_post_selectspec(null, $oldquestion['parentid'])) : null,
'title' => $oldquestion['title'],
'content' => $oldquestion['content'],
'format' => $oldquestion['format'],
'text' => qa_viewer_text($oldquestion['content'], $oldquestion['format']),
'tags' => $oldquestion['tags'],
'categoryid' => $oldquestion['categoryid'],
'name' => $oldquestion['name'],
);
if (isset($event))
qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array(
$eventparams = array(
'postid' => $oldquestion['postid'],
'parentid' => $oldquestion['parentid'],
'parent' => isset($oldquestion['parentid']) ? qa_db_single_select(qa_db_full_post_selectspec(null, $oldquestion['parentid'])) : null,
'title' => $oldquestion['title'],
'content' => $oldquestion['content'],
'format' => $oldquestion['format'],
'text' => qa_viewer_text($oldquestion['content'], $oldquestion['format']),
'tags' => $oldquestion['tags'],
'categoryid' => $oldquestion['categoryid'],
'name' => $oldquestion['name'],
);
if (isset($event)) {
qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array(
'oldquestion' => $oldquestion,
));
if ($wasqueued && ($status==QA_POST_STATUS_NORMAL) && !$wasrequeued) {
require_once QA_INCLUDE_DIR.'db/selects.php';
require_once QA_INCLUDE_DIR.'util/string.php';
qa_report_event('q_post', $oldquestion['userid'], $oldquestion['handle'], $oldquestion['cookieid'], $eventparams + array(
'notify' => isset($oldquestion['notify']),
'email' => qa_email_validate($oldquestion['notify']) ? $oldquestion['notify'] : null,
'delayed' => $oldquestion['created'],
));
}
}
if ($wasqueued && ($status == QA_POST_STATUS_NORMAL) && !$wasrequeued) {
require_once QA_INCLUDE_DIR . 'db/selects.php';
require_once QA_INCLUDE_DIR . 'util/string.php';
function qa_question_set_category($oldquestion, $categoryid, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost=null, $silent=false)
/*
Sets the category (application level) of $oldquestion to $categoryid. Pass details of the user doing this in
$userid, $handle and $cookieid, the database records for all answers to the question in $answers, the database
records for all comments on the question or the question's answers in $commentsfollows ($commentsfollows can also
contain records for follow-on questions which are ignored), and $closepost to match $oldquestion['closedbyid'] (if any).
Set $silent to true to not mark the question as edited. Handles cached counts and event reports and will reset category
IDs and paths for all answers and comments. See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
$oldpath=qa_db_post_get_category_path($oldquestion['postid']);
qa_db_post_set_category($oldquestion['postid'], $categoryid, $silent ? null : $userid, $silent ? null : qa_remote_ip_address());
qa_db_posts_calc_category_path($oldquestion['postid']);
$newpath=qa_db_post_get_category_path($oldquestion['postid']);
qa_db_category_path_qcount_update($oldpath);
qa_db_category_path_qcount_update($newpath);
$otherpostids=array();
foreach ($answers as $answer)
$otherpostids[]=$answer['postid'];
foreach ($commentsfollows as $comment)
if ($comment['basetype']=='C')
$otherpostids[]=$comment['postid'];
if (@$closepost['parentid']==$oldquestion['postid'])
$otherpostids[]=$closepost['postid'];
qa_db_posts_set_category_path($otherpostids, $newpath);
$searchmodules=qa_load_modules_with('search', 'move_post');
foreach ($searchmodules as $searchmodule) {
$searchmodule->move_post($oldquestion['postid'], $categoryid);
foreach ($otherpostids as $otherpostid)
$searchmodule->move_post($otherpostid, $categoryid);
}
qa_report_event('q_move', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
'categoryid' => $categoryid,
'oldcategoryid' => $oldquestion['categoryid'],
qa_report_event('q_post', $oldquestion['userid'], $oldquestion['handle'], $oldquestion['cookieid'], $eventparams + array(
'notify' => isset($oldquestion['notify']),
'email' => qa_email_validate($oldquestion['notify']) ? $oldquestion['notify'] : null,
'delayed' => $oldquestion['created'],
));
}
}
/**
* Sets the category (application level) of $oldquestion to $categoryid. Pass details of the user doing this in
* $userid, $handle and $cookieid, the database records for all answers to the question in $answers, the database
* records for all comments on the question or the question's answers in $commentsfollows ($commentsfollows can also
* contain records for follow-on questions which are ignored), and $closepost to match $oldquestion['closedbyid'] (if any).
* Set $silent to true to not mark the question as edited. Handles cached counts and event reports and will reset category
* IDs and paths for all answers and comments. See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldquestion
* @param $categoryid
* @param $userid
* @param $handle
* @param $cookieid
* @param $answers
* @param $commentsfollows
* @param $closepost
* @param bool $silent
*/
function qa_question_set_category($oldquestion, $categoryid, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost = null, $silent = false)
{
$oldpath = qa_db_post_get_category_path($oldquestion['postid']);
qa_db_post_set_category($oldquestion['postid'], $categoryid, $silent ? null : $userid, $silent ? null : qa_remote_ip_address());
qa_db_posts_calc_category_path($oldquestion['postid']);
$newpath = qa_db_post_get_category_path($oldquestion['postid']);
qa_db_category_path_qcount_update($oldpath);
qa_db_category_path_qcount_update($newpath);
$otherpostids = array();
foreach ($answers as $answer) {
$otherpostids[] = $answer['postid'];
}
foreach ($commentsfollows as $comment) {
if ($comment['basetype'] == 'C')
$otherpostids[] = $comment['postid'];
}
function qa_question_delete($oldquestion, $userid, $handle, $cookieid, $oldclosepost=null)
/*
Permanently delete a question (application level) from the database. The question must not have any answers or
comments on it. Pass details of the user doing this in $userid, $handle and $cookieid, and $closepost to match
$oldquestion['closedbyid'] (if any). Handles unindexing, votes, points, cached counts and event reports.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
require_once QA_INCLUDE_DIR.'db/votes.php';
if ($oldquestion['type']!='Q_HIDDEN')
qa_fatal_error('Tried to delete a non-hidden question');
$params = array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
);
if (@$closepost['parentid'] == $oldquestion['postid'])
$otherpostids[] = $closepost['postid'];
qa_report_event('q_delete_before', $userid, $handle, $cookieid, $params);
qa_db_posts_set_category_path($otherpostids, $newpath);
if (isset($oldclosepost) && ($oldclosepost['parentid']==$oldquestion['postid'])) {
qa_db_post_set_closed($oldquestion['postid'], null); // for foreign key constraint
qa_post_unindex($oldclosepost['postid']);
qa_db_post_delete($oldclosepost['postid']);
$searchmodules = qa_load_modules_with('search', 'move_post');
foreach ($searchmodules as $searchmodule) {
$searchmodule->move_post($oldquestion['postid'], $categoryid);
foreach ($otherpostids as $otherpostid) {
$searchmodule->move_post($otherpostid, $categoryid);
}
$useridvotes=qa_db_uservote_post_get($oldquestion['postid']);
$oldpath=qa_db_post_get_category_path($oldquestion['postid']);
qa_post_unindex($oldquestion['postid']);
qa_db_post_delete($oldquestion['postid']); // also deletes any related voteds due to foreign key cascading
qa_update_counts_for_q(null);
qa_db_category_path_qcount_update($oldpath); // don't do inside qa_update_counts_for_q() since post no longer exists
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects', 'qvoteds', 'upvoteds', 'downvoteds'));
foreach ($useridvotes as $voteruserid => $vote)
qa_db_points_update_ifuser($voteruserid, ($vote>0) ? 'qupvotes' : 'qdownvotes');
// could do this in one query like in qa_db_users_recalc_points() but this will do for now - unlikely to be many votes
qa_report_event('q_delete', $userid, $handle, $cookieid, $params);
}
function qa_question_set_userid($oldquestion, $userid, $handle, $cookieid)
/*
Set the author (application level) of $oldquestion to $userid and also pass $handle and $cookieid
of user. Updates points and reports events as appropriate.
*/
{
require_once QA_INCLUDE_DIR.'db/votes.php';
$postid = $oldquestion['postid'];
qa_db_post_set_userid($postid, $userid);
qa_db_uservote_remove_own($postid);
qa_db_post_recount_votes($postid);
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects', 'qvoteds', 'upvoteds', 'downvoteds'));
qa_db_points_update_ifuser($userid, array('qposts', 'aselects', 'qvoteds', 'qupvotes', 'qdownvotes', 'upvoteds', 'downvoteds'));
qa_report_event('q_claim', $userid, $handle, $cookieid, array(
'postid' => $postid,
'oldquestion' => $oldquestion,
));
qa_report_event('q_move', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
'categoryid' => $categoryid,
'oldcategoryid' => $oldquestion['categoryid'],
));
}
/**
* Permanently delete a question (application level) from the database. The question must not have any answers or
* comments on it. Pass details of the user doing this in $userid, $handle and $cookieid, and $closepost to match
* $oldquestion['closedbyid'] (if any). Handles unindexing, votes, points, cached counts and event reports.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldquestion
* @param $userid
* @param $handle
* @param $cookieid
* @param $oldclosepost
*/
function qa_question_delete($oldquestion, $userid, $handle, $cookieid, $oldclosepost = null)
{
require_once QA_INCLUDE_DIR . 'db/votes.php';
if ($oldquestion['type'] != 'Q_HIDDEN')
qa_fatal_error('Tried to delete a non-hidden question');
$params = array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
);
qa_report_event('q_delete_before', $userid, $handle, $cookieid, $params);
if (isset($oldclosepost) && ($oldclosepost['parentid'] == $oldquestion['postid'])) {
qa_db_post_set_closed($oldquestion['postid'], null); // for foreign key constraint
qa_post_unindex($oldclosepost['postid']);
qa_db_post_delete($oldclosepost['postid']);
}
$useridvotes = qa_db_uservote_post_get($oldquestion['postid']);
$oldpath = qa_db_post_get_category_path($oldquestion['postid']);
function qa_post_unindex($postid)
/*
Remove post $postid from our index and update appropriate word counts. Calls through to all search modules.
*/
{
global $qa_post_indexing_suspended;
if ($qa_post_indexing_suspended>0)
return;
qa_post_unindex($oldquestion['postid']);
qa_db_post_delete($oldquestion['postid']); // also deletes any related voteds due to foreign key cascading
qa_update_counts_for_q(null);
qa_db_category_path_qcount_update($oldpath); // don't do inside qa_update_counts_for_q() since post no longer exists
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects', 'qvoteds', 'upvoteds', 'downvoteds'));
// Send through to any search modules for unindexing
$searchmodules=qa_load_modules_with('search', 'unindex_post');
foreach ($searchmodules as $searchmodule)
$searchmodule->unindex_post($postid);
foreach ($useridvotes as $voteruserid => $vote) {
// could do this in one query like in qa_db_users_recalc_points() but this will do for now - unlikely to be many votes
qa_db_points_update_ifuser($voteruserid, ($vote > 0) ? 'qupvotes' : 'qdownvotes');
}
qa_report_event('q_delete', $userid, $handle, $cookieid, $params);
}
function qa_answer_set_content($oldanswer, $content, $format, $text, $notify, $userid, $handle, $cookieid, $question, $name=null, $remoderate=false, $silent=false)
/*
Change the fields of an answer (application level) to $content, $format, $notify and $name, then reindex based on
$text. For backwards compatibility if $name is null then the name will not be changed. Pass the answer's database
record before changes in $oldanswer, the question's in $question, and details of the user doing this in $userid,
$handle and $cookieid. Set $remoderate to true if the question should be requeued for moderation if modified. Set
$silent to true to not mark the question as edited. Handle indexing and event reports as appropriate. See
qa-app-posts.php for a higher-level function which is easier to use.
*/
{
qa_post_unindex($oldanswer['postid']);
$wasqueued=($oldanswer['type']=='A_QUEUED');
$contentchanged=strcmp($oldanswer['content'], $content) || strcmp($oldanswer['format'], $format);
$setupdated=$contentchanged && (!$wasqueued) && !$silent;
/**
* Set the author (application level) of $oldquestion to $userid and also pass $handle and $cookieid
* of user. Updates points and reports events as appropriate.
* @param $oldquestion
* @param $userid
* @param $handle
* @param $cookieid
*/
function qa_question_set_userid($oldquestion, $userid, $handle, $cookieid)
{
require_once QA_INCLUDE_DIR . 'db/votes.php';
qa_db_post_set_content($oldanswer['postid'], $oldanswer['title'], $content, $format, $oldanswer['tags'], $notify,
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_CONTENT, $name);
$postid = $oldquestion['postid'];
if ($setupdated && $remoderate) {
require_once QA_INCLUDE_DIR.'app/posts.php';
qa_db_post_set_userid($postid, $userid);
qa_db_uservote_remove_own($postid);
qa_db_post_recount_votes($postid);
$commentsfollows=qa_post_get_answer_commentsfollows($oldanswer['postid']);
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects', 'qvoteds', 'upvoteds', 'downvoteds'));
qa_db_points_update_ifuser($userid, array('qposts', 'aselects', 'qvoteds', 'qupvotes', 'qdownvotes', 'upvoteds', 'downvoteds'));
foreach ($commentsfollows as $comment)
if ( ($comment['basetype']=='C') && ($comment['parentid']==$oldanswer['postid']) )
qa_post_unindex($comment['postid']);
qa_report_event('q_claim', $userid, $handle, $cookieid, array(
'postid' => $postid,
'oldquestion' => $oldquestion,
));
}
qa_db_post_set_type($oldanswer['postid'], 'A_QUEUED');
qa_update_q_counts_for_a($question['postid']);
qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds'));
if ($oldanswer['flagcount'])
qa_db_flaggedcount_update();
/**
* Remove post $postid from our index and update appropriate word counts. Calls through to all search modules.
* @param $postid
*/
function qa_post_unindex($postid)
{
global $qa_post_indexing_suspended;
} elseif ( ($oldanswer['type']=='A') && ($question['type']=='Q') ) { // don't index if question or answer are hidden/queued
qa_post_index($oldanswer['postid'], 'A', $question['postid'], $oldanswer['parentid'], null, $content, $format, $text, null, $oldanswer['categoryid']);
}
if ($qa_post_indexing_suspended > 0)
return;
$eventparams=array(
'postid' => $oldanswer['postid'],
'parentid' => $oldanswer['parentid'],
'parent' => $question,
'content' => $content,
'format' => $format,
'text' => $text,
'name' => $name,
'oldanswer' => $oldanswer,
);
qa_report_event('a_edit', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent,
'oldcontent' => $oldanswer['content'],
'oldformat' => $oldanswer['format'],
'contentchanged' => $contentchanged,
));
// Send through to any search modules for unindexing
if ($setupdated && $remoderate)
qa_report_event('a_requeue', $userid, $handle, $cookieid, $eventparams);
$searchmodules = qa_load_modules_with('search', 'unindex_post');
foreach ($searchmodules as $searchmodule) {
$searchmodule->unindex_post($postid);
}
function qa_answer_set_hidden($oldanswer, $hidden, $userid, $handle, $cookieid, $question, $commentsfollows)
/*
Set $oldanswer to hidden if $hidden is true, visible/normal if otherwise. All other parameters are as for qa_answer_set_status(...)
This function is included mainly for backwards compatibility.
*/
{
qa_answer_set_status($oldanswer, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $userid, $handle, $cookieid, $question, $commentsfollows);
}
function qa_answer_set_status($oldanswer, $status, $userid, $handle, $cookieid, $question, $commentsfollows)
/*
Set the status (application level) of $oldanswer to $status, one of the QA_POST_STATUS_* constants above. Pass
details of the user doing this in $userid, $handle and $cookieid, the database record for the question in $question,
and the database records for all comments on the answer in $commentsfollows ($commentsfollows can also contain other
records which are ignored). Handles indexing, user points, cached counts and event reports. See qa-app-posts.php for
a higher-level function which is easier to use.
*/
{
require_once QA_INCLUDE_DIR.'app/format.php';
$washidden=($oldanswer['type']=='A_HIDDEN');
$wasqueued=($oldanswer['type']=='A_QUEUED');
$wasrequeued=$wasqueued && isset($oldanswer['updated']);
qa_post_unindex($oldanswer['postid']);
foreach ($commentsfollows as $comment)
if ( ($comment['basetype']=='C') && ($comment['parentid']==$oldanswer['postid']) )
}
/**
* Change the fields of an answer (application level) to $content, $format, $notify and $name, then reindex based on
* $text. For backwards compatibility if $name is null then the name will not be changed. Pass the answer's database
* record before changes in $oldanswer, the question's in $question, and details of the user doing this in $userid,
* $handle and $cookieid. Set $remoderate to true if the question should be requeued for moderation if modified. Set
* $silent to true to not mark the question as edited. Handle indexing and event reports as appropriate. See
* qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldanswer
* @param $content
* @param $format
* @param $text
* @param $notify
* @param $userid
* @param $handle
* @param $cookieid
* @param $question
* @param $name
* @param bool $remoderate
* @param bool $silent
*/
function qa_answer_set_content($oldanswer, $content, $format, $text, $notify, $userid, $handle, $cookieid, $question, $name = null, $remoderate = false, $silent = false)
{
qa_post_unindex($oldanswer['postid']);
$wasqueued = ($oldanswer['type'] == 'A_QUEUED');
$contentchanged = strcmp($oldanswer['content'], $content) || strcmp($oldanswer['format'], $format);
$setupdated = $contentchanged && (!$wasqueued) && !$silent;
qa_db_post_set_content($oldanswer['postid'], $oldanswer['title'], $content, $format, $oldanswer['tags'], $notify,
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_CONTENT, $name);
if ($setupdated && $remoderate) {
require_once QA_INCLUDE_DIR . 'app/posts.php';
$commentsfollows = qa_post_get_answer_commentsfollows($oldanswer['postid']);
foreach ($commentsfollows as $comment) {
if (($comment['basetype'] == 'C') && ($comment['parentid'] == $oldanswer['postid']))
qa_post_unindex($comment['postid']);
$setupdated=false;
$event=null;
if ($status==QA_POST_STATUS_QUEUED) {
$newtype='A_QUEUED';
if (!$wasqueued)
$event='a_requeue'; // same event whether it was hidden or shown before
} elseif ($status==QA_POST_STATUS_HIDDEN) {
$newtype='A_HIDDEN';
if (!$washidden) {
$event=$wasqueued ? 'a_reject' : 'a_hide';
if (!$wasqueued)
$setupdated=true;
}
if ($question['selchildid'] == $oldanswer['postid']) { // remove selected answer
qa_question_set_selchildid(null, null, null, $question, null, array($oldanswer['postid'] => $oldanswer));
}
} elseif ($status==QA_POST_STATUS_NORMAL) {
$newtype='A';
if ($wasqueued)
$event='a_approve';
elseif ($washidden) {
$event='a_reshow';
$setupdated=true;
}
} else
qa_fatal_error('Unknown status in qa_answer_set_status(): '.$status);
qa_db_post_set_type($oldanswer['postid'], $newtype, $setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_VISIBLE);
if ( $wasqueued && ($status==QA_POST_STATUS_NORMAL) && qa_opt('moderate_update_time') ) { // ... for approval of a post, can set time to now instead
if ($wasrequeued)
qa_db_post_set_updated($oldanswer['postid'], null);
else
qa_db_post_set_created($oldanswer['postid'], null);
}
qa_db_post_set_type($oldanswer['postid'], 'A_QUEUED');
qa_update_q_counts_for_a($question['postid']);
qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds'));
if ($wasqueued || ($status==QA_POST_STATUS_QUEUED))
qa_db_queuedcount_update();
if ($oldanswer['flagcount'])
qa_db_flaggedcount_update();
if (($question['type']=='Q') && ($status==QA_POST_STATUS_NORMAL)) { // even if answer visible, don't index if question is hidden or queued
qa_post_index($oldanswer['postid'], 'A', $question['postid'], $oldanswer['parentid'], null, $oldanswer['content'],
$oldanswer['format'], qa_viewer_text($oldanswer['content'], $oldanswer['format']), null, $oldanswer['categoryid']);
foreach ($commentsfollows as $comment)
if ( ($comment['type']=='C') && ($comment['parentid']==$oldanswer['postid']) ) // and don't index hidden/queued comments
qa_post_index($comment['postid'], $comment['type'], $question['postid'], $comment['parentid'], null, $comment['content'],
$comment['format'], qa_viewer_text($comment['content'], $comment['format']), null, $comment['categoryid']);
}
$eventparams=array(
'postid' => $oldanswer['postid'],
'parentid' => $oldanswer['parentid'],
'parent' => $question,
'content' => $oldanswer['content'],
'format' => $oldanswer['format'],
'text' => qa_viewer_text($oldanswer['content'], $oldanswer['format']),
'categoryid' => $oldanswer['categoryid'],
'name' => $oldanswer['name'],
);
if (isset($event))
qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array(
'oldanswer' => $oldanswer,
));
if ($wasqueued && ($status==QA_POST_STATUS_NORMAL) && !$wasrequeued) {
require_once QA_INCLUDE_DIR.'util/string.php';
qa_report_event('a_post', $oldanswer['userid'], $oldanswer['handle'], $oldanswer['cookieid'], $eventparams + array(
'notify' => isset($oldanswer['notify']),
'email' => qa_email_validate($oldanswer['notify']) ? $oldanswer['notify'] : null,
'delayed' => $oldanswer['created'],
));
}
} elseif (($oldanswer['type'] == 'A') && ($question['type'] == 'Q')) { // don't index if question or answer are hidden/queued
qa_post_index($oldanswer['postid'], 'A', $question['postid'], $oldanswer['parentid'], null, $content, $format, $text, null, $oldanswer['categoryid']);
}
$eventparams = array(
'postid' => $oldanswer['postid'],
'parentid' => $oldanswer['parentid'],
'parent' => $question,
'content' => $content,
'format' => $format,
'text' => $text,
'name' => $name,
'oldanswer' => $oldanswer,
);
qa_report_event('a_edit', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent,
'oldcontent' => $oldanswer['content'],
'oldformat' => $oldanswer['format'],
'contentchanged' => $contentchanged,
));
if ($setupdated && $remoderate)
qa_report_event('a_requeue', $userid, $handle, $cookieid, $eventparams);
}
/**
* Set $oldanswer to hidden if $hidden is true, visible/normal if otherwise. All other parameters are as for qa_answer_set_status(...)
* This function is included mainly for backwards compatibility.
* @param $oldanswer
* @param $hidden
* @param $userid
* @param $handle
* @param $cookieid
* @param $question
* @param $commentsfollows
*/
function qa_answer_set_hidden($oldanswer, $hidden, $userid, $handle, $cookieid, $question, $commentsfollows)
{
qa_answer_set_status($oldanswer, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $userid, $handle, $cookieid, $question, $commentsfollows);
}
/**
* Set the status (application level) of $oldanswer to $status, one of the QA_POST_STATUS_* constants above. Pass
* details of the user doing this in $userid, $handle and $cookieid, the database record for the question in $question,
* and the database records for all comments on the answer in $commentsfollows ($commentsfollows can also contain other
* records which are ignored). Handles indexing, user points, cached counts and event reports. See qa-app-posts.php for
* a higher-level function which is easier to use.
* @param $oldanswer
* @param $status
* @param $userid
* @param $handle
* @param $cookieid
* @param $question
* @param $commentsfollows
*/
function qa_answer_set_status($oldanswer, $status, $userid, $handle, $cookieid, $question, $commentsfollows)
{
require_once QA_INCLUDE_DIR . 'app/format.php';
$washidden = ($oldanswer['type'] == 'A_HIDDEN');
$wasqueued = ($oldanswer['type'] == 'A_QUEUED');
$wasrequeued = $wasqueued && isset($oldanswer['updated']);
qa_post_unindex($oldanswer['postid']);
foreach ($commentsfollows as $comment) {
if (($comment['basetype'] == 'C') && ($comment['parentid'] == $oldanswer['postid']))
qa_post_unindex($comment['postid']);
}
function qa_answer_delete($oldanswer, $question, $userid, $handle, $cookieid)
/*
Permanently delete an answer (application level) from the database. The answer must not have any comments or
follow-on questions. Pass the database record for the question in $question and details of the user doing this
in $userid, $handle and $cookieid. Handles unindexing, votes, points, cached counts and event reports.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
require_once QA_INCLUDE_DIR.'db/votes.php';
if ($oldanswer['type']!='A_HIDDEN')
qa_fatal_error('Tried to delete a non-hidden answer');
$useridvotes=qa_db_uservote_post_get($oldanswer['postid']);
$setupdated = false;
$event = null;
$params = array(
'postid' => $oldanswer['postid'],
'parentid' => $oldanswer['parentid'],
'oldanswer' => $oldanswer,
);
if ($status == QA_POST_STATUS_QUEUED) {
$newtype = 'A_QUEUED';
if (!$wasqueued)
$event = 'a_requeue'; // same event whether it was hidden or shown before
qa_report_event('a_delete_before', $userid, $handle, $cookieid, $params);
} elseif ($status == QA_POST_STATUS_HIDDEN) {
$newtype = 'A_HIDDEN';
if (!$washidden) {
$event = $wasqueued ? 'a_reject' : 'a_hide';
if (!$wasqueued)
$setupdated = true;
}
qa_post_unindex($oldanswer['postid']);
qa_db_post_delete($oldanswer['postid']); // also deletes any related voteds due to cascading
if ($question['selchildid'] == $oldanswer['postid']) { // remove selected answer
qa_question_set_selchildid(null, null, null, $question, null, array($oldanswer['postid'] => $oldanswer));
}
if ($question['selchildid']==$oldanswer['postid']) {
qa_db_post_set_selchildid($question['postid'], null);
qa_db_points_update_ifuser($question['userid'], 'aselects');
qa_db_unselqcount_update();
} elseif ($status == QA_POST_STATUS_NORMAL) {
$newtype = 'A';
if ($wasqueued)
$event = 'a_approve';
elseif ($washidden) {
$event = 'a_reshow';
$setupdated = true;
}
qa_update_q_counts_for_a($question['postid']);
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'avoteds', 'upvoteds', 'downvoteds'));
} else
qa_fatal_error('Unknown status in qa_answer_set_status(): ' . $status);
foreach ($useridvotes as $voteruserid => $vote)
qa_db_points_update_ifuser($voteruserid, ($vote>0) ? 'aupvotes' : 'adownvotes');
// could do this in one query like in qa_db_users_recalc_points() but this will do for now - unlikely to be many votes
qa_db_post_set_type($oldanswer['postid'], $newtype, $setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_VISIBLE);
qa_report_event('a_delete', $userid, $handle, $cookieid, $params);
if ($wasqueued && ($status == QA_POST_STATUS_NORMAL) && qa_opt('moderate_update_time')) { // ... for approval of a post, can set time to now instead
if ($wasrequeued)
qa_db_post_set_updated($oldanswer['postid'], null);
else
qa_db_post_set_created($oldanswer['postid'], null);
}
qa_update_q_counts_for_a($question['postid']);
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds'));
function qa_answer_set_userid($oldanswer, $userid, $handle, $cookieid)
/*
Set the author (application level) of $oldanswer to $userid and also pass $handle and $cookieid
of user. Updates points and reports events as appropriate.
*/
{
require_once QA_INCLUDE_DIR.'db/votes.php';
$postid = $oldanswer['postid'];
if ($wasqueued || $status == QA_POST_STATUS_QUEUED)
qa_db_queuedcount_update();
qa_db_post_set_userid($postid, $userid);
qa_db_uservote_remove_own($postid);
qa_db_post_recount_votes($postid);
if ($oldanswer['flagcount'])
qa_db_flaggedcount_update();
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'avoteds', 'upvoteds', 'downvoteds'));
qa_db_points_update_ifuser($userid, array('aposts', 'aselecteds', 'avoteds', 'aupvotes', 'adownvotes', 'upvoteds', 'downvoteds'));
if (($question['type'] == 'Q') && ($status == QA_POST_STATUS_NORMAL)) { // even if answer visible, don't index if question is hidden or queued
qa_post_index($oldanswer['postid'], 'A', $question['postid'], $oldanswer['parentid'], null, $oldanswer['content'],
$oldanswer['format'], qa_viewer_text($oldanswer['content'], $oldanswer['format']), null, $oldanswer['categoryid']);
qa_report_event('a_claim', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => $oldanswer['parentid'],
'oldanswer' => $oldanswer,
));
foreach ($commentsfollows as $comment) {
if (($comment['type'] == 'C') && ($comment['parentid'] == $oldanswer['postid'])) { // and don't index hidden/queued comments
qa_post_index($comment['postid'], $comment['type'], $question['postid'], $comment['parentid'], null, $comment['content'],
$comment['format'], qa_viewer_text($comment['content'], $comment['format']), null, $comment['categoryid']);
}
}
}
$eventparams = array(
'postid' => $oldanswer['postid'],
'parentid' => $oldanswer['parentid'],
'parent' => $question,
'content' => $oldanswer['content'],
'format' => $oldanswer['format'],
'text' => qa_viewer_text($oldanswer['content'], $oldanswer['format']),
'categoryid' => $oldanswer['categoryid'],
'name' => $oldanswer['name'],
);
if (isset($event)) {
qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array(
'oldanswer' => $oldanswer,
));
}
function qa_comment_set_content($oldcomment, $content, $format, $text, $notify, $userid, $handle, $cookieid, $question, $parent, $name=null, $remoderate=false, $silent=false)
/*
Change the fields of a comment (application level) to $content, $format, $notify and $name, then reindex based on
$text. For backwards compatibility if $name is null then the name will not be changed. Pass the comment's database
record before changes in $oldcomment, details of the user doing this in $userid, $handle and $cookieid, the
antecedent question in $question and the answer's database record in $answer if this is a comment on an answer,
otherwise null. Set $remoderate to true if the question should be requeued for moderation if modified. Set $silent
to true to not mark the question as edited. Handles unindexing and event reports. See qa-app-posts.php for a
higher-level function which is easier to use.
*/
{
if (!isset($parent))
$parent=$question; // for backwards compatibility with old answer parameter
qa_post_unindex($oldcomment['postid']);
$wasqueued=($oldcomment['type']=='C_QUEUED');
$contentchanged=strcmp($oldcomment['content'], $content) || strcmp($oldcomment['format'], $format);
$setupdated=$contentchanged && (!$wasqueued) && !$silent;
qa_db_post_set_content($oldcomment['postid'], $oldcomment['title'], $content, $format, $oldcomment['tags'], $notify,
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_CONTENT, $name);
if ($setupdated && $remoderate) {
qa_db_post_set_type($oldcomment['postid'], 'C_QUEUED');
qa_db_ccount_update();
qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
if ($oldcomment['flagcount'])
qa_db_flaggedcount_update();
} elseif ( ($oldcomment['type']=='C') && ($question['type']=='Q') && (($parent['type']=='Q') || ($parent['type']=='A')) ) { // all must be visible
qa_post_index($oldcomment['postid'], 'C', $question['postid'], $oldcomment['parentid'], null, $content, $format, $text, null, $oldcomment['categoryid']);
}
$eventparams=array(
'postid' => $oldcomment['postid'],
'parentid' => $oldcomment['parentid'],
'parenttype' => $parent['basetype'],
'parent' => $parent,
'questionid' => $question['postid'],
'question' => $question,
'content' => $content,
'format' => $format,
'text' => $text,
'name' => $name,
'oldcomment' => $oldcomment,
);
if ($wasqueued && ($status == QA_POST_STATUS_NORMAL) && !$wasrequeued) {
require_once QA_INCLUDE_DIR . 'util/string.php';
qa_report_event('c_edit', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent,
'oldcontent' => $oldcomment['content'],
'oldformat' => $oldcomment['format'],
'contentchanged' => $contentchanged,
qa_report_event('a_post', $oldanswer['userid'], $oldanswer['handle'], $oldanswer['cookieid'], $eventparams + array(
'notify' => isset($oldanswer['notify']),
'email' => qa_email_validate($oldanswer['notify']) ? $oldanswer['notify'] : null,
'delayed' => $oldanswer['created'],
));
if ($setupdated && $remoderate)
qa_report_event('c_requeue', $userid, $handle, $cookieid, $eventparams);
}
}
/**
* Permanently delete an answer (application level) from the database. The answer must not have any comments or
* follow-on questions. Pass the database record for the question in $question and details of the user doing this
* in $userid, $handle and $cookieid. Handles unindexing, votes, points, cached counts and event reports.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldanswer
* @param $question
* @param $userid
* @param $handle
* @param $cookieid
*/
function qa_answer_delete($oldanswer, $question, $userid, $handle, $cookieid)
{
require_once QA_INCLUDE_DIR . 'db/votes.php';
if ($oldanswer['type'] != 'A_HIDDEN')
qa_fatal_error('Tried to delete a non-hidden answer');
$useridvotes = qa_db_uservote_post_get($oldanswer['postid']);
$params = array(
'postid' => $oldanswer['postid'],
'parentid' => $oldanswer['parentid'],
'oldanswer' => $oldanswer,
);
qa_report_event('a_delete_before', $userid, $handle, $cookieid, $params);
qa_post_unindex($oldanswer['postid']);
qa_db_post_delete($oldanswer['postid']); // also deletes any related voteds due to cascading
if ($question['selchildid'] == $oldanswer['postid']) {
qa_db_post_set_selchildid($question['postid'], null);
qa_db_points_update_ifuser($question['userid'], 'aselects');
qa_db_unselqcount_update();
}
qa_update_q_counts_for_a($question['postid']);
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'avoteds', 'upvoteds', 'downvoteds'));
function qa_answer_to_comment($oldanswer, $parentid, $content, $format, $text, $notify, $userid, $handle, $cookieid, $question, $answers, $commentsfollows, $name=null, $remoderate=false, $silent=false)
/*
Convert an answer to a comment (application level) and set its fields to $content, $format, $notify and $name. For
backwards compatibility if $name is null then the name will not be changed. Pass the answer's database record before
changes in $oldanswer, the new comment's $parentid to be, details of the user doing this in $userid, $handle and
$cookieid, the antecedent question's record in $question, the records for all answers to that question in $answers,
and the records for all comments on the (old) answer and questions following from the (old) answer in
$commentsfollows ($commentsfollows can also contain other records which are ignored). Set $remoderate to true if the
question should be requeued for moderation if modified. Set $silent to true to not mark the question as edited.
Handles indexing (based on $text), user points, cached counts and event reports.
*/
{
require_once QA_INCLUDE_DIR.'db/votes.php';
$parent=isset($answers[$parentid]) ? $answers[$parentid] : $question;
qa_post_unindex($oldanswer['postid']);
$wasqueued=($oldanswer['type']=='A_QUEUED');
$contentchanged=strcmp($oldanswer['content'], $content) || strcmp($oldanswer['format'], $format);
$setupdated=$contentchanged && (!$wasqueued) && !$silent;
if ($setupdated && $remoderate)
$newtype='C_QUEUED';
else
$newtype=substr_replace($oldanswer['type'], 'C', 0, 1);
qa_db_post_set_type($oldanswer['postid'], $newtype, ($wasqueued || $silent) ? null : $userid,
($wasqueued || $silent) ? null : qa_remote_ip_address(), QA_UPDATE_TYPE);
qa_db_post_set_parent($oldanswer['postid'], $parentid);
qa_db_post_set_content($oldanswer['postid'], $oldanswer['title'], $content, $format, $oldanswer['tags'], $notify,
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_CONTENT, $name);
foreach ($commentsfollows as $commentfollow)
if ($commentfollow['parentid']==$oldanswer['postid']) // do same thing for comments and follows
qa_db_post_set_parent($commentfollow['postid'], $parentid);
foreach ($useridvotes as $voteruserid => $vote) {
// could do this in one query like in qa_db_users_recalc_points() but this will do for now - unlikely to be many votes
qa_db_points_update_ifuser($voteruserid, ($vote > 0) ? 'aupvotes' : 'adownvotes');
}
qa_update_q_counts_for_a($question['postid']);
qa_report_event('a_delete', $userid, $handle, $cookieid, $params);
}
/**
* Set the author (application level) of $oldanswer to $userid and also pass $handle and $cookieid
* of user. Updates points and reports events as appropriate.
* @param $oldanswer
* @param $userid
* @param $handle
* @param $cookieid
*/
function qa_answer_set_userid($oldanswer, $userid, $handle, $cookieid)
{
require_once QA_INCLUDE_DIR . 'db/votes.php';
$postid = $oldanswer['postid'];
qa_db_post_set_userid($postid, $userid);
qa_db_uservote_remove_own($postid);
qa_db_post_recount_votes($postid);
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'avoteds', 'upvoteds', 'downvoteds'));
qa_db_points_update_ifuser($userid, array('aposts', 'aselecteds', 'avoteds', 'aupvotes', 'adownvotes', 'upvoteds', 'downvoteds'));
qa_report_event('a_claim', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => $oldanswer['parentid'],
'oldanswer' => $oldanswer,
));
}
/**
* Change the fields of a comment (application level) to $content, $format, $notify and $name, then reindex based on
* $text. For backwards compatibility if $name is null then the name will not be changed. Pass the comment's database
* record before changes in $oldcomment, details of the user doing this in $userid, $handle and $cookieid, the
* antecedent question in $question and the answer's database record in $answer if this is a comment on an answer,
* otherwise null. Set $remoderate to true if the question should be requeued for moderation if modified. Set $silent
* to true to not mark the question as edited. Handles unindexing and event reports. See qa-app-posts.php for a
* higher-level function which is easier to use.
* @param $oldcomment
* @param $content
* @param $format
* @param $text
* @param $notify
* @param $userid
* @param $handle
* @param $cookieid
* @param $question
* @param $parent
* @param $name
* @param bool $remoderate
* @param bool $silent
*/
function qa_comment_set_content($oldcomment, $content, $format, $text, $notify, $userid, $handle, $cookieid, $question, $parent, $name = null, $remoderate = false, $silent = false)
{
if (!isset($parent))
$parent = $question; // for backwards compatibility with old answer parameter
qa_post_unindex($oldcomment['postid']);
$wasqueued = ($oldcomment['type'] == 'C_QUEUED');
$contentchanged = strcmp($oldcomment['content'], $content) || strcmp($oldcomment['format'], $format);
$setupdated = $contentchanged && (!$wasqueued) && !$silent;
qa_db_post_set_content($oldcomment['postid'], $oldcomment['title'], $content, $format, $oldcomment['tags'], $notify,
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_CONTENT, $name);
if ($setupdated && $remoderate) {
qa_db_post_set_type($oldcomment['postid'], 'C_QUEUED');
qa_db_ccount_update();
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'cposts', 'avoteds'));
$useridvotes=qa_db_uservote_post_get($oldanswer['postid']);
foreach ($useridvotes as $voteruserid => $vote)
qa_db_points_update_ifuser($voteruserid, ($vote>0) ? 'aupvotes' : 'adownvotes');
// could do this in one query like in qa_db_users_recalc_points() but this will do for now - unlikely to be many votes
if ($setupdated && $remoderate) {
qa_db_queuedcount_update();
if ($oldanswer['flagcount'])
qa_db_flaggedcount_update();
} elseif ( ($oldanswer['type']=='A') && ($question['type']=='Q') && (($parent['type']=='Q') || ($parent['type']=='A')) ) // only if all fully visible
qa_post_index($oldanswer['postid'], 'C', $question['postid'], $parentid, null, $content, $format, $text, null, $oldanswer['categoryid']);
if ($question['selchildid'] == $oldanswer['postid']) { // remove selected answer
qa_question_set_selchildid(null, null, null, $question, null, array($oldanswer['postid'] => $oldanswer));
}
qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
$eventparams=array(
'postid' => $oldanswer['postid'],
'parentid' => $parentid,
'parenttype' => $parent['basetype'],
'parent' => $parent,
'questionid' => $question['postid'],
'question' => $question,
'content' => $content,
'format' => $format,
'text' => $text,
'name' => $name,
'oldanswer' => $oldanswer,
);
qa_report_event('a_to_c', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent,
'oldcontent' => $oldanswer['content'],
'oldformat' => $oldanswer['format'],
'contentchanged' => $contentchanged,
));
if ($oldcomment['flagcount'])
qa_db_flaggedcount_update();
if ($setupdated && $remoderate)
qa_report_event('c_requeue', $userid, $handle, $cookieid, $eventparams);
// a-to-c conversion can be detected by presence of $event['oldanswer'] instead of $event['oldcomment']
} elseif ($oldcomment['type'] == 'C' && $question['type'] == 'Q' && ($parent['type'] == 'Q' || $parent['type'] == 'A')) { // all must be visible
qa_post_index($oldcomment['postid'], 'C', $question['postid'], $oldcomment['parentid'], null, $content, $format, $text, null, $oldcomment['categoryid']);
}
function qa_comment_set_hidden($oldcomment, $hidden, $userid, $handle, $cookieid, $question, $parent)
/*
Set $oldcomment to hidden if $hidden is true, visible/normal if otherwise. All other parameters are as for qa_comment_set_status(...)
This function is included mainly for backwards compatibility.
*/
{
qa_comment_set_status($oldcomment, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $userid, $handle, $cookieid, $question, $parent);
$eventparams = array(
'postid' => $oldcomment['postid'],
'parentid' => $oldcomment['parentid'],
'parenttype' => $parent['basetype'],
'parent' => $parent,
'questionid' => $question['postid'],
'question' => $question,
'content' => $content,
'format' => $format,
'text' => $text,
'name' => $name,
'oldcomment' => $oldcomment,
);
qa_report_event('c_edit', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent,
'oldcontent' => $oldcomment['content'],
'oldformat' => $oldcomment['format'],
'contentchanged' => $contentchanged,
));
if ($setupdated && $remoderate)
qa_report_event('c_requeue', $userid, $handle, $cookieid, $eventparams);
}
/**
* Convert an answer to a comment (application level) and set its fields to $content, $format, $notify and $name. For
* backwards compatibility if $name is null then the name will not be changed. Pass the answer's database record before
* changes in $oldanswer, the new comment's $parentid to be, details of the user doing this in $userid, $handle and
* $cookieid, the antecedent question's record in $question, the records for all answers to that question in $answers,
* and the records for all comments on the (old) answer and questions following from the (old) answer in
* $commentsfollows ($commentsfollows can also contain other records which are ignored). Set $remoderate to true if the
* question should be requeued for moderation if modified. Set $silent to true to not mark the question as edited.
* Handles indexing (based on $text), user points, cached counts and event reports.
* @param $oldanswer
* @param $parentid
* @param $content
* @param $format
* @param $text
* @param $notify
* @param $userid
* @param $handle
* @param $cookieid
* @param $question
* @param $answers
* @param $commentsfollows
* @param $name
* @param bool $remoderate
* @param bool $silent
*/
function qa_answer_to_comment($oldanswer, $parentid, $content, $format, $text, $notify, $userid, $handle, $cookieid, $question, $answers, $commentsfollows, $name = null, $remoderate = false, $silent = false)
{
require_once QA_INCLUDE_DIR . 'db/votes.php';
$parent = isset($answers[$parentid]) ? $answers[$parentid] : $question;
qa_post_unindex($oldanswer['postid']);
$wasqueued = ($oldanswer['type'] == 'A_QUEUED');
$contentchanged = strcmp($oldanswer['content'], $content) || strcmp($oldanswer['format'], $format);
$setupdated = $contentchanged && (!$wasqueued) && !$silent;
if ($setupdated && $remoderate)
$newtype = 'C_QUEUED';
else
$newtype = substr_replace($oldanswer['type'], 'C', 0, 1);
qa_db_post_set_type($oldanswer['postid'], $newtype, ($wasqueued || $silent) ? null : $userid,
($wasqueued || $silent) ? null : qa_remote_ip_address(), QA_UPDATE_TYPE);
qa_db_post_set_parent($oldanswer['postid'], $parentid);
qa_db_post_set_content($oldanswer['postid'], $oldanswer['title'], $content, $format, $oldanswer['tags'], $notify,
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_CONTENT, $name);
foreach ($commentsfollows as $commentfollow) {
if ($commentfollow['parentid'] == $oldanswer['postid']) // do same thing for comments and follows
qa_db_post_set_parent($commentfollow['postid'], $parentid);
}
qa_update_q_counts_for_a($question['postid']);
qa_db_ccount_update();
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'cposts', 'avoteds'));
function qa_comment_set_status($oldcomment, $status, $userid, $handle, $cookieid, $question, $parent)
/*
Set the status (application level) of $oldcomment to $status, one of the QA_POST_STATUS_* constants above. Pass the
antecedent question's record in $question, details of the user doing this in $userid, $handle and $cookieid, and the
answer's database record in $answer if this is a comment on an answer, otherwise null. Handles indexing, user
points, cached counts and event reports. See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
require_once QA_INCLUDE_DIR.'app/format.php';
$useridvotes = qa_db_uservote_post_get($oldanswer['postid']);
foreach ($useridvotes as $voteruserid => $vote) {
// could do this in one query like in qa_db_users_recalc_points() but this will do for now - unlikely to be many votes
qa_db_points_update_ifuser($voteruserid, ($vote > 0) ? 'aupvotes' : 'adownvotes');
}
if (!isset($parent))
$parent=$question; // for backwards compatibility with old answer parameter
if ($setupdated && $remoderate) {
qa_db_queuedcount_update();
$washidden=($oldcomment['type']=='C_HIDDEN');
$wasqueued=($oldcomment['type']=='C_QUEUED');
$wasrequeued=$wasqueued && isset($oldcomment['updated']);
if ($oldanswer['flagcount'])
qa_db_flaggedcount_update();
qa_post_unindex($oldcomment['postid']);
} elseif (($oldanswer['type'] == 'A') && ($question['type'] == 'Q') && (($parent['type'] == 'Q') || ($parent['type'] == 'A'))) // only if all fully visible
qa_post_index($oldanswer['postid'], 'C', $question['postid'], $parentid, null, $content, $format, $text, null, $oldanswer['categoryid']);
$setupdated=false;
$event=null;
if ($question['selchildid'] == $oldanswer['postid']) { // remove selected answer
qa_question_set_selchildid(null, null, null, $question, null, array($oldanswer['postid'] => $oldanswer));
}
if ($status==QA_POST_STATUS_QUEUED) {
$newtype='C_QUEUED';
$eventparams = array(
'postid' => $oldanswer['postid'],
'parentid' => $parentid,
'parenttype' => $parent['basetype'],
'parent' => $parent,
'questionid' => $question['postid'],
'question' => $question,
'content' => $content,
'format' => $format,
'text' => $text,
'name' => $name,
'oldanswer' => $oldanswer,
);
qa_report_event('a_to_c', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent,
'oldcontent' => $oldanswer['content'],
'oldformat' => $oldanswer['format'],
'contentchanged' => $contentchanged,
));
if ($setupdated && $remoderate) {
// a-to-c conversion can be detected by presence of $event['oldanswer'] instead of $event['oldcomment']
qa_report_event('c_requeue', $userid, $handle, $cookieid, $eventparams);
}
}
/**
* Set $oldcomment to hidden if $hidden is true, visible/normal if otherwise. All other parameters are as for qa_comment_set_status(...)
* This function is included mainly for backwards compatibility.
* @param $oldcomment
* @param $hidden
* @param $userid
* @param $handle
* @param $cookieid
* @param $question
* @param $parent
*/
function qa_comment_set_hidden($oldcomment, $hidden, $userid, $handle, $cookieid, $question, $parent)
{
qa_comment_set_status($oldcomment, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $userid, $handle, $cookieid, $question, $parent);
}
/**
* Set the status (application level) of $oldcomment to $status, one of the QA_POST_STATUS_* constants above. Pass the
* antecedent question's record in $question, details of the user doing this in $userid, $handle and $cookieid, and the
* answer's database record in $answer if this is a comment on an answer, otherwise null. Handles indexing, user
* points, cached counts and event reports. See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldcomment
* @param $status
* @param $userid
* @param $handle
* @param $cookieid
* @param $question
* @param $parent
*/
function qa_comment_set_status($oldcomment, $status, $userid, $handle, $cookieid, $question, $parent)
{
require_once QA_INCLUDE_DIR . 'app/format.php';
if (!isset($parent))
$parent = $question; // for backwards compatibility with old answer parameter
$washidden = ($oldcomment['type'] == 'C_HIDDEN');
$wasqueued = ($oldcomment['type'] == 'C_QUEUED');
$wasrequeued = $wasqueued && isset($oldcomment['updated']);
qa_post_unindex($oldcomment['postid']);
$setupdated = false;
$event = null;
if ($status == QA_POST_STATUS_QUEUED) {
$newtype = 'C_QUEUED';
if (!$wasqueued)
$event = 'c_requeue'; // same event whether it was hidden or shown before
} elseif ($status == QA_POST_STATUS_HIDDEN) {
$newtype = 'C_HIDDEN';
if (!$washidden) {
$event = $wasqueued ? 'c_reject' : 'c_hide';
if (!$wasqueued)
$event='c_requeue'; // same event whether it was hidden or shown before
} elseif ($status==QA_POST_STATUS_HIDDEN) {
$newtype='C_HIDDEN';
if (!$washidden) {
$event=$wasqueued ? 'c_reject' : 'c_hide';
if (!$wasqueued)
$setupdated=true;
}
} elseif ($status==QA_POST_STATUS_NORMAL) {
$newtype='C';
if ($wasqueued)
$event='c_approve';
elseif ($washidden) {
$event='c_reshow';
$setupdated=true;
}
} else
qa_fatal_error('Unknown status in qa_comment_set_status(): '.$status);
qa_db_post_set_type($oldcomment['postid'], $newtype, $setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_VISIBLE);
if ( $wasqueued && ($status==QA_POST_STATUS_NORMAL) && qa_opt('moderate_update_time') ) { // ... for approval of a post, can set time to now instead
if ($wasrequeued)
qa_db_post_set_updated($oldcomment['postid'], null);
else
qa_db_post_set_created($oldcomment['postid'], null);
$setupdated = true;
}
qa_db_ccount_update();
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
} elseif ($status == QA_POST_STATUS_NORMAL) {
$newtype = 'C';
if ($wasqueued)
$event = 'c_approve';
elseif ($washidden) {
$event = 'c_reshow';
$setupdated = true;
}
if ($wasqueued || ($status==QA_POST_STATUS_QUEUED))
qa_db_queuedcount_update();
} else
qa_fatal_error('Unknown status in qa_comment_set_status(): ' . $status);
if ($oldcomment['flagcount'])
qa_db_flaggedcount_update();
qa_db_post_set_type($oldcomment['postid'], $newtype, $setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_VISIBLE);
if ( ($question['type']=='Q') && (($parent['type']=='Q') || ($parent['type']=='A')) && ($status==QA_POST_STATUS_NORMAL)) // only index if none of the things it depends on are hidden or queued
qa_post_index($oldcomment['postid'], 'C', $question['postid'], $oldcomment['parentid'], null, $oldcomment['content'],
$oldcomment['format'], qa_viewer_text($oldcomment['content'], $oldcomment['format']), null, $oldcomment['categoryid']);
$eventparams=array(
'postid' => $oldcomment['postid'],
'parentid' => $oldcomment['parentid'],
'parenttype' => $parent['basetype'],
'parent' => $parent,
'questionid' => $question['postid'],
'question' => $question,
'content' => $oldcomment['content'],
'format' => $oldcomment['format'],
'text' => qa_viewer_text($oldcomment['content'], $oldcomment['format']),
'categoryid' => $oldcomment['categoryid'],
'name' => $oldcomment['name'],
);
if (isset($event))
qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array(
'oldcomment' => $oldcomment,
));
if ($wasqueued && ($status == QA_POST_STATUS_NORMAL) && qa_opt('moderate_update_time')) { // ... for approval of a post, can set time to now instead
if ($wasrequeued)
qa_db_post_set_updated($oldcomment['postid'], null);
else
qa_db_post_set_created($oldcomment['postid'], null);
}
if ($wasqueued && ($status==QA_POST_STATUS_NORMAL) && !$wasrequeued) {
require_once QA_INCLUDE_DIR.'db/selects.php';
require_once QA_INCLUDE_DIR.'util/string.php';
qa_db_ccount_update();
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
$commentsfollows=qa_db_single_select(qa_db_full_child_posts_selectspec(null, $oldcomment['parentid']));
$thread=array();
if ($wasqueued || $status == QA_POST_STATUS_QUEUED)
qa_db_queuedcount_update();
foreach ($commentsfollows as $comment)
if (($comment['type']=='C') && ($comment['parentid']==$parent['postid']))
$thread[]=$comment;
if ($oldcomment['flagcount'])
qa_db_flaggedcount_update();
qa_report_event('c_post', $oldcomment['userid'], $oldcomment['handle'], $oldcomment['cookieid'], $eventparams + array(
'thread' => $thread,
'notify' => isset($oldcomment['notify']),
'email' => qa_email_validate($oldcomment['notify']) ? $oldcomment['notify'] : null,
'delayed' => $oldcomment['created'],
));
}
if ($question['type'] == 'Q' && ($parent['type'] == 'Q' || $parent['type'] == 'A') && $status == QA_POST_STATUS_NORMAL) {
// only index if none of the things it depends on are hidden or queued
qa_post_index($oldcomment['postid'], 'C', $question['postid'], $oldcomment['parentid'], null, $oldcomment['content'],
$oldcomment['format'], qa_viewer_text($oldcomment['content'], $oldcomment['format']), null, $oldcomment['categoryid']);
}
function qa_comment_delete($oldcomment, $question, $parent, $userid, $handle, $cookieid)
/*
Permanently delete a comment in $oldcomment (application level) from the database. Pass the database question in $question
and the answer's database record in $answer if this is a comment on an answer, otherwise null. Pass details of the user
doing this in $userid, $handle and $cookieid. Handles unindexing, points, cached counts and event reports.
See qa-app-posts.php for a higher-level function which is easier to use.
*/
{
if (!isset($parent))
$parent=$question; // for backwards compatibility with old answer parameter
if ($oldcomment['type']!='C_HIDDEN')
qa_fatal_error('Tried to delete a non-hidden comment');
$params = array(
'postid' => $oldcomment['postid'],
'parentid' => $oldcomment['parentid'],
$eventparams = array(
'postid' => $oldcomment['postid'],
'parentid' => $oldcomment['parentid'],
'parenttype' => $parent['basetype'],
'parent' => $parent,
'questionid' => $question['postid'],
'question' => $question,
'content' => $oldcomment['content'],
'format' => $oldcomment['format'],
'text' => qa_viewer_text($oldcomment['content'], $oldcomment['format']),
'categoryid' => $oldcomment['categoryid'],
'name' => $oldcomment['name'],
);
if (isset($event)) {
qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array(
'oldcomment' => $oldcomment,
'parenttype' => $parent['basetype'],
'questionid' => $question['postid'],
);
qa_report_event('c_delete_before', $userid, $handle, $cookieid, $params);
qa_post_unindex($oldcomment['postid']);
qa_db_post_delete($oldcomment['postid']);
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
qa_db_ccount_update();
qa_report_event('c_delete', $userid, $handle, $cookieid, $params);
));
}
if ($wasqueued && $status == QA_POST_STATUS_NORMAL && !$wasrequeued) {
require_once QA_INCLUDE_DIR . 'db/selects.php';
require_once QA_INCLUDE_DIR . 'util/string.php';
function qa_comment_set_userid($oldcomment, $userid, $handle, $cookieid)
/*
Set the author (application level) of $oldcomment to $userid and also pass $handle and $cookieid
of user. Updates points and reports events as appropriate.
*/
{
require_once QA_INCLUDE_DIR.'db/votes.php';
$postid = $oldcomment['postid'];
$commentsfollows = qa_db_single_select(qa_db_full_child_posts_selectspec(null, $oldcomment['parentid']));
$thread = array();
qa_db_post_set_userid($postid, $userid);
qa_db_uservote_remove_own($postid);
qa_db_post_recount_votes($postid);
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
qa_db_points_update_ifuser($userid, array('cposts'));
foreach ($commentsfollows as $comment) {
if ($comment['type'] == 'C' && $comment['parentid'] == $parent['postid'])
$thread[] = $comment;
}
qa_report_event('c_claim', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => $oldcomment['parentid'],
'oldcomment' => $oldcomment,
qa_report_event('c_post', $oldcomment['userid'], $oldcomment['handle'], $oldcomment['cookieid'], $eventparams + array(
'thread' => $thread,
'notify' => isset($oldcomment['notify']),
'email' => qa_email_validate($oldcomment['notify']) ? $oldcomment['notify'] : null,
'delayed' => $oldcomment['created'],
));
}
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
}
/**
* Permanently delete a comment in $oldcomment (application level) from the database. Pass the database question in $question
* and the answer's database record in $answer if this is a comment on an answer, otherwise null. Pass details of the user
* doing this in $userid, $handle and $cookieid. Handles unindexing, points, cached counts and event reports.
* See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldcomment
* @param $question
* @param $parent
* @param $userid
* @param $handle
* @param $cookieid
*/
function qa_comment_delete($oldcomment, $question, $parent, $userid, $handle, $cookieid)
{
if (!isset($parent))
$parent = $question; // for backwards compatibility with old answer parameter
if ($oldcomment['type'] != 'C_HIDDEN')
qa_fatal_error('Tried to delete a non-hidden comment');
$params = array(
'postid' => $oldcomment['postid'],
'parentid' => $oldcomment['parentid'],
'oldcomment' => $oldcomment,
'parenttype' => $parent['basetype'],
'questionid' => $question['postid'],
);
qa_report_event('c_delete_before', $userid, $handle, $cookieid, $params);
qa_post_unindex($oldcomment['postid']);
qa_db_post_delete($oldcomment['postid']);
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
qa_db_ccount_update();
qa_report_event('c_delete', $userid, $handle, $cookieid, $params);
}
/**
* Set the author (application level) of $oldcomment to $userid and also pass $handle and $cookieid
* of user. Updates points and reports events as appropriate.
* @param $oldcomment
* @param $userid
* @param $handle
* @param $cookieid
*/
function qa_comment_set_userid($oldcomment, $userid, $handle, $cookieid)
{
require_once QA_INCLUDE_DIR . 'db/votes.php';
$postid = $oldcomment['postid'];
qa_db_post_set_userid($postid, $userid);
qa_db_uservote_remove_own($postid);
qa_db_post_recount_votes($postid);
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
qa_db_points_update_ifuser($userid, array('cposts'));
qa_report_event('c_claim', $userid, $handle, $cookieid, array(
'postid' => $postid,
'parentid' => $oldcomment['parentid'],
'oldcomment' => $oldcomment,
));
}
......@@ -56,698 +56,693 @@
[but these are not entirely redundant since they can contain historical information no longer in ^posts]
*/
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../');
exit;
}
require_once QA_INCLUDE_DIR.'db/recalc.php';
require_once QA_INCLUDE_DIR.'db/post-create.php';
require_once QA_INCLUDE_DIR.'db/points.php';
require_once QA_INCLUDE_DIR.'db/selects.php';
require_once QA_INCLUDE_DIR.'db/admin.php';
require_once QA_INCLUDE_DIR.'db/users.php';
require_once QA_INCLUDE_DIR.'app/options.php';
require_once QA_INCLUDE_DIR.'app/post-create.php';
require_once QA_INCLUDE_DIR.'app/post-update.php';
function qa_recalc_perform_step(&$state)
/*
Advance the recalculation operation represented by $state by a single step.
$state can also be the name of a recalculation operation on its own.
*/
{
$continue=false;
@list($operation, $length, $next, $done)=explode("\t", $state);
switch ($operation) {
case 'doreindexcontent':
qa_recalc_transition($state, 'doreindexcontent_pagereindex');
break;
case 'doreindexcontent_pagereindex':
$pages=qa_db_pages_get_for_reindexing($next, 10);
if (count($pages)) {
require_once QA_INCLUDE_DIR.'app/format.php';
$lastpageid=max(array_keys($pages));
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../');
exit;
}
require_once QA_INCLUDE_DIR.'db/recalc.php';
require_once QA_INCLUDE_DIR.'db/post-create.php';
require_once QA_INCLUDE_DIR.'db/points.php';
require_once QA_INCLUDE_DIR.'db/selects.php';
require_once QA_INCLUDE_DIR.'db/admin.php';
require_once QA_INCLUDE_DIR.'db/users.php';
require_once QA_INCLUDE_DIR.'app/options.php';
require_once QA_INCLUDE_DIR.'app/post-create.php';
require_once QA_INCLUDE_DIR.'app/post-update.php';
/**
* Advance the recalculation operation represented by $state by a single step.
* $state can also be the name of a recalculation operation on its own.
*/
function qa_recalc_perform_step(&$state)
{
$continue=false;
@list($operation, $length, $next, $done)=explode("\t", $state);
switch ($operation) {
case 'doreindexcontent':
qa_recalc_transition($state, 'doreindexcontent_pagereindex');
break;
case 'doreindexcontent_pagereindex':
$pages=qa_db_pages_get_for_reindexing($next, 10);
if (count($pages)) {
require_once QA_INCLUDE_DIR.'app/format.php';
$lastpageid=max(array_keys($pages));
foreach ($pages as $pageid => $page)
if (!($page['flags'] & QA_PAGE_FLAGS_EXTERNAL)) {
$searchmodules=qa_load_modules_with('search', 'unindex_page');
foreach ($searchmodules as $searchmodule)
$searchmodule->unindex_page($pageid);
$searchmodules=qa_load_modules_with('search', 'index_page');
if (count($searchmodules)) {
$indextext=qa_viewer_text($page['content'], 'html');
foreach ($pages as $pageid => $page)
if (!($page['flags'] & QA_PAGE_FLAGS_EXTERNAL)) {
$searchmodules=qa_load_modules_with('search', 'unindex_page');
foreach ($searchmodules as $searchmodule)
$searchmodule->unindex_page($pageid);
$searchmodules=qa_load_modules_with('search', 'index_page');
if (count($searchmodules)) {
$indextext=qa_viewer_text($page['content'], 'html');
foreach ($searchmodules as $searchmodule)
$searchmodule->index_page($pageid, $page['tags'], $page['heading'], $page['content'], 'html', $indextext);
}
$searchmodule->index_page($pageid, $page['tags'], $page['heading'], $page['content'], 'html', $indextext);
}
}
$next=1+$lastpageid;
$done+=count($pages);
$continue=true;
} else
qa_recalc_transition($state, 'doreindexcontent_postcount');
break;
$next=1+$lastpageid;
$done+=count($pages);
$continue=true;
case 'doreindexcontent_postcount':
qa_db_qcount_update();
qa_db_acount_update();
qa_db_ccount_update();
} else
qa_recalc_transition($state, 'doreindexcontent_postcount');
break;
qa_recalc_transition($state, 'doreindexcontent_postreindex');
break;
case 'doreindexcontent_postcount':
qa_db_qcount_update();
qa_db_acount_update();
qa_db_ccount_update();
case 'doreindexcontent_postreindex':
$posts=qa_db_posts_get_for_reindexing($next, 10);
qa_recalc_transition($state, 'doreindexcontent_postreindex');
break;
if (count($posts)) {
require_once QA_INCLUDE_DIR.'app/format.php';
case 'doreindexcontent_postreindex':
$posts=qa_db_posts_get_for_reindexing($next, 10);
$lastpostid=max(array_keys($posts));
if (count($posts)) {
require_once QA_INCLUDE_DIR.'app/format.php';
qa_db_prepare_for_reindexing($next, $lastpostid);
qa_suspend_update_counts();
$lastpostid=max(array_keys($posts));
foreach ($posts as $postid => $post) {
qa_post_unindex($postid);
qa_post_index($postid, $post['type'], $post['questionid'], $post['parentid'], $post['title'], $post['content'],
$post['format'], qa_viewer_text($post['content'], $post['format']), $post['tags'], $post['categoryid']);
}
qa_db_prepare_for_reindexing($next, $lastpostid);
qa_suspend_update_counts();
$next=1+$lastpostid;
$done+=count($posts);
$continue=true;
} else {
qa_db_truncate_indexes($next);
qa_recalc_transition($state, 'doreindexposts_wordcount');
foreach ($posts as $postid => $post) {
qa_post_unindex($postid);
qa_post_index($postid, $post['type'], $post['questionid'], $post['parentid'], $post['title'], $post['content'],
$post['format'], qa_viewer_text($post['content'], $post['format']), $post['tags'], $post['categoryid']);
}
break;
case 'doreindexposts_wordcount':
$wordids=qa_db_words_prepare_for_recounting($next, 1000);
if (count($wordids)) {
$lastwordid=max($wordids);
$next=1+$lastpostid;
$done+=count($posts);
$continue=true;
qa_db_words_recount($next, $lastwordid);
} else {
qa_db_truncate_indexes($next);
qa_recalc_transition($state, 'doreindexposts_wordcount');
}
break;
$next=1+$lastwordid;
$done+=count($wordids);
$continue=true;
case 'doreindexposts_wordcount':
$wordids=qa_db_words_prepare_for_recounting($next, 1000);
} else {
qa_db_tagcount_update(); // this is quick so just do it here
qa_recalc_transition($state, 'doreindexposts_complete');
}
break;
if (count($wordids)) {
$lastwordid=max($wordids);
case 'dorecountposts':
qa_recalc_transition($state, 'dorecountposts_postcount');
break;
qa_db_words_recount($next, $lastwordid);
case 'dorecountposts_postcount':
qa_db_qcount_update();
qa_db_acount_update();
qa_db_ccount_update();
qa_db_unaqcount_update();
qa_db_unselqcount_update();
$next=1+$lastwordid;
$done+=count($wordids);
$continue=true;
qa_recalc_transition($state, 'dorecountposts_votecount');
break;
} else {
qa_db_tagcount_update(); // this is quick so just do it here
qa_recalc_transition($state, 'doreindexposts_complete');
}
break;
case 'dorecountposts_votecount':
$postids=qa_db_posts_get_for_recounting($next, 1000);
case 'dorecountposts':
qa_recalc_transition($state, 'dorecountposts_postcount');
break;
if (count($postids)) {
$lastpostid=max($postids);
case 'dorecountposts_postcount':
qa_db_qcount_update();
qa_db_acount_update();
qa_db_ccount_update();
qa_db_unaqcount_update();
qa_db_unselqcount_update();
qa_db_posts_votes_recount($next, $lastpostid);
qa_recalc_transition($state, 'dorecountposts_votecount');
break;
$next=1+$lastpostid;
$done+=count($postids);
$continue=true;
case 'dorecountposts_votecount':
$postids=qa_db_posts_get_for_recounting($next, 1000);
} else
qa_recalc_transition($state, 'dorecountposts_acount');
break;
if (count($postids)) {
$lastpostid=max($postids);
case 'dorecountposts_acount':
$postids=qa_db_posts_get_for_recounting($next, 1000);
qa_db_posts_votes_recount($next, $lastpostid);
if (count($postids)) {
$lastpostid=max($postids);
$next=1+$lastpostid;
$done+=count($postids);
$continue=true;
qa_db_posts_answers_recount($next, $lastpostid);
} else
qa_recalc_transition($state, 'dorecountposts_acount');
break;
$next=1+$lastpostid;
$done+=count($postids);
$continue=true;
case 'dorecountposts_acount':
$postids=qa_db_posts_get_for_recounting($next, 1000);
} else {
qa_db_unupaqcount_update();
qa_recalc_transition($state, 'dorecountposts_complete');
}
break;
case 'dorecalcpoints':
qa_recalc_transition($state, 'dorecalcpoints_usercount');
break;
case 'dorecalcpoints_usercount':
qa_db_userpointscount_update(); // for progress update - not necessarily accurate
qa_db_uapprovecount_update(); // needs to be somewhere and this is the most appropriate place
qa_recalc_transition($state, 'dorecalcpoints_recalc');
break;
case 'dorecalcpoints_recalc':
$recalccount=10;
$userids=qa_db_users_get_for_recalc_points($next, $recalccount+1); // get one extra so we know where to start from next
$gotcount=count($userids);
$recalccount=min($recalccount, $gotcount); // can't recalc more than we got
if ($recalccount>0) {
$lastuserid=$userids[$recalccount-1];
qa_db_users_recalc_points($next, $lastuserid);
$done+=$recalccount;
} else
$lastuserid=$next; // for truncation
if ($gotcount>$recalccount) { // more left to do
$next=$userids[$recalccount]; // start next round at first one not recalculated
$continue=true;
} else {
qa_db_truncate_userpoints($lastuserid);
qa_db_userpointscount_update(); // quick so just do it here
qa_recalc_transition($state, 'dorecalcpoints_complete');
}
break;
if (count($postids)) {
$lastpostid=max($postids);
case 'dorefillevents':
qa_recalc_transition($state, 'dorefillevents_qcount');
break;
qa_db_posts_answers_recount($next, $lastpostid);
case 'dorefillevents_qcount':
qa_db_qcount_update();
qa_recalc_transition($state, 'dorefillevents_refill');
break;
$next=1+$lastpostid;
$done+=count($postids);
$continue=true;
case 'dorefillevents_refill':
$questionids=qa_db_qs_get_for_event_refilling($next, 1);
} else {
qa_db_unupaqcount_update();
qa_recalc_transition($state, 'dorecountposts_complete');
}
break;
if (count($questionids)) {
require_once QA_INCLUDE_DIR.'app/events.php';
require_once QA_INCLUDE_DIR.'app/updates.php';
require_once QA_INCLUDE_DIR.'util/sort.php';
case 'dorecalcpoints':
qa_recalc_transition($state, 'dorecalcpoints_usercount');
break;
$lastquestionid=max($questionids);
case 'dorecalcpoints_usercount':
qa_db_userpointscount_update(); // for progress update - not necessarily accurate
qa_db_uapprovecount_update(); // needs to be somewhere and this is the most appropriate place
qa_recalc_transition($state, 'dorecalcpoints_recalc');
break;
foreach ($questionids as $questionid) {
case 'dorecalcpoints_recalc':
$recalccount=10;
$userids=qa_db_users_get_for_recalc_points($next, $recalccount+1); // get one extra so we know where to start from next
$gotcount=count($userids);
$recalccount=min($recalccount, $gotcount); // can't recalc more than we got
// Retrieve all posts relating to this question
if ($recalccount>0) {
$lastuserid=$userids[$recalccount-1];
qa_db_users_recalc_points($next, $lastuserid);
$done+=$recalccount;
list($question, $childposts, $achildposts)=qa_db_select_with_pending(
qa_db_full_post_selectspec(null, $questionid),
qa_db_full_child_posts_selectspec(null, $questionid),
qa_db_full_a_child_posts_selectspec(null, $questionid)
);
} else
$lastuserid=$next; // for truncation
// Merge all posts while preserving keys as postids
if ($gotcount>$recalccount) { // more left to do
$next=$userids[$recalccount]; // start next round at first one not recalculated
$continue=true;
$posts=array($questionid => $question);
} else {
qa_db_truncate_userpoints($lastuserid);
qa_db_userpointscount_update(); // quick so just do it here
qa_recalc_transition($state, 'dorecalcpoints_complete');
}
break;
foreach ($childposts as $postid => $post)
$posts[$postid]=$post;
case 'dorefillevents':
qa_recalc_transition($state, 'dorefillevents_qcount');
break;
foreach ($achildposts as $postid => $post)
$posts[$postid]=$post;
case 'dorefillevents_qcount':
qa_db_qcount_update();
qa_recalc_transition($state, 'dorefillevents_refill');
break;
// Creation and editing of each post
case 'dorefillevents_refill':
$questionids=qa_db_qs_get_for_event_refilling($next, 1);
foreach ($posts as $postid => $post) {
$followonq=($post['basetype']=='Q') && ($postid!=$questionid);
if (count($questionids)) {
require_once QA_INCLUDE_DIR.'app/events.php';
require_once QA_INCLUDE_DIR.'app/updates.php';
require_once QA_INCLUDE_DIR.'util/sort.php';
if ($followonq)
$updatetype=QA_UPDATE_FOLLOWS;
elseif ( ($post['basetype']=='C') && (@$posts[$post['parentid']]['basetype']=='Q') )
$updatetype=QA_UPDATE_C_FOR_Q;
elseif ( ($post['basetype']=='C') && (@$posts[$post['parentid']]['basetype']=='A') )
$updatetype=QA_UPDATE_C_FOR_A;
else
$updatetype=null;
$lastquestionid=max($questionids);
qa_create_event_for_q_user($questionid, $postid, $updatetype, $post['userid'], @$posts[$post['parentid']]['userid'], $post['created']);
foreach ($questionids as $questionid) {
if (isset($post['updated']) && !$followonq)
qa_create_event_for_q_user($questionid, $postid, $post['updatetype'], $post['lastuserid'], $post['userid'], $post['updated']);
}
// Retrieve all posts relating to this question
// Tags and categories of question
list($question, $childposts, $achildposts)=qa_db_select_with_pending(
qa_db_full_post_selectspec(null, $questionid),
qa_db_full_child_posts_selectspec(null, $questionid),
qa_db_full_a_child_posts_selectspec(null, $questionid)
);
qa_create_event_for_tags($question['tags'], $questionid, null, $question['userid'], $question['created']);
qa_create_event_for_category($question['categoryid'], $questionid, null, $question['userid'], $question['created']);
// Merge all posts while preserving keys as postids
// Collect comment threads
$posts=array($questionid => $question);
$parentidcomments=array();
foreach ($childposts as $postid => $post)
$posts[$postid]=$post;
foreach ($posts as $postid => $post)
if ($post['basetype']=='C')
$parentidcomments[$post['parentid']][$postid]=$post;
foreach ($achildposts as $postid => $post)
$posts[$postid]=$post;
// For each comment thread, notify all previous comment authors of each comment in the thread (could get slow)
// Creation and editing of each post
foreach ($parentidcomments as $parentid => $comments) {
$keyuserids=array();
foreach ($posts as $postid => $post) {
$followonq=($post['basetype']=='Q') && ($postid!=$questionid);
qa_sort_by($comments, 'created');
if ($followonq)
$updatetype=QA_UPDATE_FOLLOWS;
elseif ( ($post['basetype']=='C') && (@$posts[$post['parentid']]['basetype']=='Q') )
$updatetype=QA_UPDATE_C_FOR_Q;
elseif ( ($post['basetype']=='C') && (@$posts[$post['parentid']]['basetype']=='A') )
$updatetype=QA_UPDATE_C_FOR_A;
else
$updatetype=null;
foreach ($comments as $comment) {
foreach ($keyuserids as $keyuserid => $dummy)
if ( ($keyuserid != $comment['userid']) && ($keyuserid != @$posts[$parentid]['userid']) )
qa_db_event_create_not_entity($keyuserid, $questionid, $comment['postid'], QA_UPDATE_FOLLOWS, $comment['userid'], $comment['created']);
qa_create_event_for_q_user($questionid, $postid, $updatetype, $post['userid'], @$posts[$post['parentid']]['userid'], $post['created']);
if (isset($comment['userid']))
$keyuserids[$comment['userid']]=true;
}
}
if (isset($post['updated']) && !$followonq)
qa_create_event_for_q_user($questionid, $postid, $post['updatetype'], $post['lastuserid'], $post['userid'], $post['updated']);
}
$next=1+$lastquestionid;
$done+=count($questionids);
$continue=true;
// Tags and categories of question
} else
qa_recalc_transition($state, 'dorefillevents_complete');
break;
qa_create_event_for_tags($question['tags'], $questionid, null, $question['userid'], $question['created']);
qa_create_event_for_category($question['categoryid'], $questionid, null, $question['userid'], $question['created']);
case 'dorecalccategories':
qa_recalc_transition($state, 'dorecalccategories_postcount');
break;
// Collect comment threads
case 'dorecalccategories_postcount':
qa_db_acount_update();
qa_db_ccount_update();
$parentidcomments=array();
qa_recalc_transition($state, 'dorecalccategories_postupdate');
break;
foreach ($posts as $postid => $post)
if ($post['basetype']=='C')
$parentidcomments[$post['parentid']][$postid]=$post;
case 'dorecalccategories_postupdate':
$postids=qa_db_posts_get_for_recategorizing($next, 100);
// For each comment thread, notify all previous comment authors of each comment in the thread (could get slow)
if (count($postids)) {
$lastpostid=max($postids);
foreach ($parentidcomments as $parentid => $comments) {
$keyuserids=array();
qa_db_posts_recalc_categoryid($next, $lastpostid);
qa_db_posts_calc_category_path($next, $lastpostid);
qa_sort_by($comments, 'created');
$next=1+$lastpostid;
$done+=count($postids);
$continue=true;
foreach ($comments as $comment) {
foreach ($keyuserids as $keyuserid => $dummy)
if ( ($keyuserid != $comment['userid']) && ($keyuserid != @$posts[$parentid]['userid']) )
qa_db_event_create_not_entity($keyuserid, $questionid, $comment['postid'], QA_UPDATE_FOLLOWS, $comment['userid'], $comment['created']);
} else {
qa_recalc_transition($state, 'dorecalccategories_recount');
if (isset($comment['userid']))
$keyuserids[$comment['userid']]=true;
}
}
}
break;
case 'dorecalccategories_recount':
$categoryids=qa_db_categories_get_for_recalcs($next, 10);
$next=1+$lastquestionid;
$done+=count($questionids);
$continue=true;
if (count($categoryids)) {
$lastcategoryid=max($categoryids);
} else
qa_recalc_transition($state, 'dorefillevents_complete');
break;
foreach ($categoryids as $categoryid)
qa_db_ifcategory_qcount_update($categoryid);
case 'dorecalccategories':
qa_recalc_transition($state, 'dorecalccategories_postcount');
break;
$next=1+$lastcategoryid;
$done+=count($categoryids);
$continue=true;
case 'dorecalccategories_postcount':
qa_db_acount_update();
qa_db_ccount_update();
} else {
qa_recalc_transition($state, 'dorecalccategories_backpaths');
}
break;
qa_recalc_transition($state, 'dorecalccategories_postupdate');
break;
case 'dorecalccategories_backpaths':
$categoryids=qa_db_categories_get_for_recalcs($next, 10);
case 'dorecalccategories_postupdate':
$postids=qa_db_posts_get_for_recategorizing($next, 100);
if (count($categoryids)) {
$lastcategoryid=max($categoryids);
if (count($postids)) {
$lastpostid=max($postids);
qa_db_categories_recalc_backpaths($next, $lastcategoryid);
qa_db_posts_recalc_categoryid($next, $lastpostid);
qa_db_posts_calc_category_path($next, $lastpostid);
$next=1+$lastcategoryid;
$done+=count($categoryids);
$continue=true;
$next=1+$lastpostid;
$done+=count($postids);
$continue=true;
} else {
qa_recalc_transition($state, 'dorecalccategories_complete');
}
break;
} else {
qa_recalc_transition($state, 'dorecalccategories_recount');
}
break;
case 'dodeletehidden':
qa_recalc_transition($state, 'dodeletehidden_comments');
break;
case 'dorecalccategories_recount':
$categoryids=qa_db_categories_get_for_recalcs($next, 10);
case 'dodeletehidden_comments':
$posts=qa_db_posts_get_for_deleting('C', $next, 1);
if (count($categoryids)) {
$lastcategoryid=max($categoryids);
if (count($posts)) {
require_once QA_INCLUDE_DIR.'app/posts.php';
foreach ($categoryids as $categoryid)
qa_db_ifcategory_qcount_update($categoryid);
$postid=$posts[0];
$next=1+$lastcategoryid;
$done+=count($categoryids);
$continue=true;
qa_post_delete($postid);
} else {
qa_recalc_transition($state, 'dorecalccategories_backpaths');
}
break;
$next=1+$postid;
$done++;
$continue=true;
case 'dorecalccategories_backpaths':
$categoryids=qa_db_categories_get_for_recalcs($next, 10);
} else
qa_recalc_transition($state, 'dodeletehidden_answers');
break;
if (count($categoryids)) {
$lastcategoryid=max($categoryids);
case 'dodeletehidden_answers':
$posts=qa_db_posts_get_for_deleting('A', $next, 1);
qa_db_categories_recalc_backpaths($next, $lastcategoryid);
if (count($posts)) {
require_once QA_INCLUDE_DIR.'app/posts.php';
$next=1+$lastcategoryid;
$done+=count($categoryids);
$continue=true;
$postid=$posts[0];
} else {
qa_recalc_transition($state, 'dorecalccategories_complete');
}
break;
qa_post_delete($postid);
case 'dodeletehidden':
qa_recalc_transition($state, 'dodeletehidden_comments');
break;
$next=1+$postid;
$done++;
$continue=true;
case 'dodeletehidden_comments':
$posts=qa_db_posts_get_for_deleting('C', $next, 1);
} else
qa_recalc_transition($state, 'dodeletehidden_questions');
break;
if (count($posts)) {
require_once QA_INCLUDE_DIR.'app/posts.php';
case 'dodeletehidden_questions':
$posts=qa_db_posts_get_for_deleting('Q', $next, 1);
$postid=$posts[0];
if (count($posts)) {
require_once QA_INCLUDE_DIR.'app/posts.php';
qa_post_delete($postid);
$postid=$posts[0];
$next=1+$postid;
$done++;
$continue=true;
qa_post_delete($postid);
} else
qa_recalc_transition($state, 'dodeletehidden_answers');
break;
$next=1+$postid;
$done++;
$continue=true;
case 'dodeletehidden_answers':
$posts=qa_db_posts_get_for_deleting('A', $next, 1);
} else
qa_recalc_transition($state, 'dodeletehidden_complete');
break;
if (count($posts)) {
require_once QA_INCLUDE_DIR.'app/posts.php';
case 'doblobstodisk':
qa_recalc_transition($state, 'doblobstodisk_move');
break;
$postid=$posts[0];
case 'doblobstodisk_move':
$blob=qa_db_get_next_blob_in_db($next);
qa_post_delete($postid);
if (isset($blob)) {
require_once QA_INCLUDE_DIR.'app/blobs.php';
require_once QA_INCLUDE_DIR.'db/blobs.php';
$next=1+$postid;
$done++;
$continue=true;
if (qa_write_blob_file($blob['blobid'], $blob['content'], $blob['format']))
qa_db_blob_set_content($blob['blobid'], null);
} else
qa_recalc_transition($state, 'dodeletehidden_questions');
break;
$next=1+$blob['blobid'];
$done++;
$continue=true;
case 'dodeletehidden_questions':
$posts=qa_db_posts_get_for_deleting('Q', $next, 1);
} else
qa_recalc_transition($state, 'doblobstodisk_complete');
break;
if (count($posts)) {
require_once QA_INCLUDE_DIR.'app/posts.php';
case 'doblobstodb':
qa_recalc_transition($state, 'doblobstodb_move');
break;
$postid=$posts[0];
case 'doblobstodb_move':
$blob=qa_db_get_next_blob_on_disk($next);
qa_post_delete($postid);
if (isset($blob)) {
require_once QA_INCLUDE_DIR.'app/blobs.php';
require_once QA_INCLUDE_DIR.'db/blobs.php';
$next=1+$postid;
$done++;
$continue=true;
$content=qa_read_blob_file($blob['blobid'], $blob['format']);
qa_db_blob_set_content($blob['blobid'], $content);
qa_delete_blob_file($blob['blobid'], $blob['format']);
} else
qa_recalc_transition($state, 'dodeletehidden_complete');
break;
$next=1+$blob['blobid'];
$done++;
$continue=true;
case 'doblobstodisk':
qa_recalc_transition($state, 'doblobstodisk_move');
break;
} else
qa_recalc_transition($state, 'doblobstodb_complete');
break;
case 'doblobstodisk_move':
$blob=qa_db_get_next_blob_in_db($next);
default:
$state='';
break;
}
if (isset($blob)) {
require_once QA_INCLUDE_DIR.'app/blobs.php';
require_once QA_INCLUDE_DIR.'db/blobs.php';
if ($continue)
$state=$operation."\t".$length."\t".$next."\t".$done;
if (qa_write_blob_file($blob['blobid'], $blob['content'], $blob['format']))
qa_db_blob_set_content($blob['blobid'], null);
return $continue && ($done<$length);
}
$next=1+$blob['blobid'];
$done++;
$continue=true;
} else
qa_recalc_transition($state, 'doblobstodisk_complete');
break;
function qa_recalc_transition(&$state, $operation)
/*
Change the $state to represent the beginning of a new $operation
*/
{
$length=qa_recalc_stage_length($operation);
$next=(QA_FINAL_EXTERNAL_USERS && ($operation=='dorecalcpoints_recalc')) ? '' : 0;
$done=0;
case 'doblobstodb':
qa_recalc_transition($state, 'doblobstodb_move');
break;
$state=$operation."\t".$length."\t".$next."\t".$done;
}
case 'doblobstodb_move':
$blob=qa_db_get_next_blob_on_disk($next);
if (isset($blob)) {
require_once QA_INCLUDE_DIR.'app/blobs.php';
require_once QA_INCLUDE_DIR.'db/blobs.php';
function qa_recalc_stage_length($operation)
/*
Return how many steps there will be in recalculation $operation
*/
{
switch ($operation) {
case 'doreindexcontent_pagereindex':
$length=qa_db_count_pages();
break;
case 'doreindexcontent_postreindex':
$length=qa_opt('cache_qcount')+qa_opt('cache_acount')+qa_opt('cache_ccount');
break;
case 'doreindexposts_wordcount':
$length=qa_db_count_words();
break;
case 'dorecalcpoints_recalc':
$length=qa_opt('cache_userpointscount');
break;
case 'dorecountposts_votecount':
case 'dorecountposts_acount':
case 'dorecalccategories_postupdate':
$length=qa_db_count_posts();
break;
case 'dorefillevents_refill':
$length=qa_opt('cache_qcount')+qa_db_count_posts('Q_HIDDEN');
break;
case 'dorecalccategories_recount':
case 'dorecalccategories_backpaths':
$length=qa_db_count_categories();
break;
case 'dodeletehidden_comments':
$length=count(qa_db_posts_get_for_deleting('C'));
break;
case 'dodeletehidden_answers':
$length=count(qa_db_posts_get_for_deleting('A'));
break;
case 'dodeletehidden_questions':
$length=count(qa_db_posts_get_for_deleting('Q'));
break;
case 'doblobstodisk_move':
$length=qa_db_count_blobs_in_db();
break;
case 'doblobstodb_move':
$length=qa_db_count_blobs_on_disk();
break;
default:
$length=0;
break;
}
return $length;
}
$content=qa_read_blob_file($blob['blobid'], $blob['format']);
qa_db_blob_set_content($blob['blobid'], $content);
qa_delete_blob_file($blob['blobid'], $blob['format']);
$next=1+$blob['blobid'];
$done++;
$continue=true;
/**
* Return the translated language ID string replacing the progress and total in it.
* @access private
* @param string $langId Language string ID that contains 2 placeholders (^1 and ^2)
* @param int $progress Amount of processed elements
* @param int $total Total amount of elements
*
* @return string Returns the language string ID with their placeholders replaced with
* the formatted progress and total numbers
*/
function qa_recalc_progress_lang($langId, $progress, $total)
{
return strtr(qa_lang($langId), array(
'^1' => qa_format_number($progress),
'^2' => qa_format_number($total)
));
}
} else
qa_recalc_transition($state, 'doblobstodb_complete');
break;
default:
$state='';
break;
}
function qa_recalc_get_message($state)
/*
Return a string which gives a user-viewable version of $state
*/
{
require_once QA_INCLUDE_DIR . 'app/format.php';
if ($continue)
$state=$operation."\t".$length."\t".$next."\t".$done;
@list($operation, $length, $next, $done) = explode("\t", $state);
$done = (int) $done;
$length = (int) $length;
switch ($operation) {
case 'doreindexcontent_postcount':
case 'dorecountposts_postcount':
case 'dorecalccategories_postcount':
case 'dorefillevents_qcount':
$message = qa_lang('admin/recalc_posts_count');
break;
case 'doreindexcontent_pagereindex':
$message = qa_recalc_progress_lang('admin/reindex_pages_reindexed', $done, $length);
break;
case 'doreindexcontent_postreindex':
$message = qa_recalc_progress_lang('admin/reindex_posts_reindexed', $done, $length);
break;
case 'doreindexposts_complete':
$message = qa_lang('admin/reindex_posts_complete');
break;
case 'doreindexposts_wordcount':
$message = qa_recalc_progress_lang('admin/reindex_posts_wordcounted', $done, $length);
break;
case 'dorecountposts_votecount':
$message = qa_recalc_progress_lang('admin/recount_posts_votes_recounted', $done, $length);
break;
case 'dorecountposts_acount':
$message = qa_recalc_progress_lang('admin/recount_posts_as_recounted', $done, $length);
break;
case 'dorecountposts_complete':
$message = qa_lang('admin/recount_posts_complete');
break;
case 'dorecalcpoints_usercount':
$message = qa_lang('admin/recalc_points_usercount');
break;
case 'dorecalcpoints_recalc':
$message = qa_recalc_progress_lang('admin/recalc_points_recalced', $done, $length);
break;
case 'dorecalcpoints_complete':
$message = qa_lang('admin/recalc_points_complete');
break;
case 'dorefillevents_refill':
$message = qa_recalc_progress_lang('admin/refill_events_refilled', $done, $length);
break;
case 'dorefillevents_complete':
$message = qa_lang('admin/refill_events_complete');
break;
case 'dorecalccategories_postupdate':
$message = qa_recalc_progress_lang('admin/recalc_categories_updated', $done, $length);
break;
case 'dorecalccategories_recount':
$message = qa_recalc_progress_lang('admin/recalc_categories_recounting', $done, $length);
break;
case 'dorecalccategories_backpaths':
$message = qa_recalc_progress_lang('admin/recalc_categories_backpaths', $done, $length);
break;
case 'dorecalccategories_complete':
$message = qa_lang('admin/recalc_categories_complete');
break;
case 'dodeletehidden_comments':
$message = qa_recalc_progress_lang('admin/hidden_comments_deleted', $done, $length);
break;
case 'dodeletehidden_answers':
$message = qa_recalc_progress_lang('admin/hidden_answers_deleted', $done, $length);
break;
case 'dodeletehidden_questions':
$message = qa_recalc_progress_lang('admin/hidden_questions_deleted', $done, $length);
break;
case 'dodeletehidden_complete':
$message = qa_lang('admin/delete_hidden_complete');
break;
case 'doblobstodisk_move':
case 'doblobstodb_move':
$message = qa_recalc_progress_lang('admin/blobs_move_moved', $done, $length);
break;
case 'doblobstodisk_complete':
case 'doblobstodb_complete':
$message = qa_lang('admin/blobs_move_complete');
break;
default:
$message = '';
break;
}
return $message;
return $continue && ($done<$length);
}
/**
* Change the $state to represent the beginning of a new $operation
*/
function qa_recalc_transition(&$state, $operation)
{
$length=qa_recalc_stage_length($operation);
$next=(QA_FINAL_EXTERNAL_USERS && ($operation=='dorecalcpoints_recalc')) ? '' : 0;
$done=0;
$state=$operation."\t".$length."\t".$next."\t".$done;
}
/**
* Return how many steps there will be in recalculation $operation
*/
function qa_recalc_stage_length($operation)
{
switch ($operation) {
case 'doreindexcontent_pagereindex':
$length=qa_db_count_pages();
break;
case 'doreindexcontent_postreindex':
$length=qa_opt('cache_qcount')+qa_opt('cache_acount')+qa_opt('cache_ccount');
break;
case 'doreindexposts_wordcount':
$length=qa_db_count_words();
break;
case 'dorecalcpoints_recalc':
$length=qa_opt('cache_userpointscount');
break;
case 'dorecountposts_votecount':
case 'dorecountposts_acount':
case 'dorecalccategories_postupdate':
$length=qa_db_count_posts();
break;
case 'dorefillevents_refill':
$length=qa_opt('cache_qcount')+qa_db_count_posts('Q_HIDDEN');
break;
case 'dorecalccategories_recount':
case 'dorecalccategories_backpaths':
$length=qa_db_count_categories();
break;
case 'dodeletehidden_comments':
$length=count(qa_db_posts_get_for_deleting('C'));
break;
case 'dodeletehidden_answers':
$length=count(qa_db_posts_get_for_deleting('A'));
break;
case 'dodeletehidden_questions':
$length=count(qa_db_posts_get_for_deleting('Q'));
break;
case 'doblobstodisk_move':
$length=qa_db_count_blobs_in_db();
break;
case 'doblobstodb_move':
$length=qa_db_count_blobs_on_disk();
break;
default:
$length=0;
break;
}
return $length;
}
/**
* Return the translated language ID string replacing the progress and total in it.
* @access private
* @param string $langId Language string ID that contains 2 placeholders (^1 and ^2)
* @param int $progress Amount of processed elements
* @param int $total Total amount of elements
*
* @return string Returns the language string ID with their placeholders replaced with
* the formatted progress and total numbers
*/
function qa_recalc_progress_lang($langId, $progress, $total)
{
return strtr(qa_lang($langId), array(
'^1' => qa_format_number($progress),
'^2' => qa_format_number($total)
));
}
/**
* Return a string which gives a user-viewable version of $state
*/
function qa_recalc_get_message($state)
{
require_once QA_INCLUDE_DIR . 'app/format.php';
@list($operation, $length, $next, $done) = explode("\t", $state);
$done = (int) $done;
$length = (int) $length;
switch ($operation) {
case 'doreindexcontent_postcount':
case 'dorecountposts_postcount':
case 'dorecalccategories_postcount':
case 'dorefillevents_qcount':
$message = qa_lang('admin/recalc_posts_count');
break;
case 'doreindexcontent_pagereindex':
$message = qa_recalc_progress_lang('admin/reindex_pages_reindexed', $done, $length);
break;
case 'doreindexcontent_postreindex':
$message = qa_recalc_progress_lang('admin/reindex_posts_reindexed', $done, $length);
break;
case 'doreindexposts_complete':
$message = qa_lang('admin/reindex_posts_complete');
break;
case 'doreindexposts_wordcount':
$message = qa_recalc_progress_lang('admin/reindex_posts_wordcounted', $done, $length);
break;
case 'dorecountposts_votecount':
$message = qa_recalc_progress_lang('admin/recount_posts_votes_recounted', $done, $length);
break;
case 'dorecountposts_acount':
$message = qa_recalc_progress_lang('admin/recount_posts_as_recounted', $done, $length);
break;
case 'dorecountposts_complete':
$message = qa_lang('admin/recount_posts_complete');
break;
case 'dorecalcpoints_usercount':
$message = qa_lang('admin/recalc_points_usercount');
break;
case 'dorecalcpoints_recalc':
$message = qa_recalc_progress_lang('admin/recalc_points_recalced', $done, $length);
break;
case 'dorecalcpoints_complete':
$message = qa_lang('admin/recalc_points_complete');
break;
case 'dorefillevents_refill':
$message = qa_recalc_progress_lang('admin/refill_events_refilled', $done, $length);
break;
case 'dorefillevents_complete':
$message = qa_lang('admin/refill_events_complete');
break;
case 'dorecalccategories_postupdate':
$message = qa_recalc_progress_lang('admin/recalc_categories_updated', $done, $length);
break;
case 'dorecalccategories_recount':
$message = qa_recalc_progress_lang('admin/recalc_categories_recounting', $done, $length);
break;
case 'dorecalccategories_backpaths':
$message = qa_recalc_progress_lang('admin/recalc_categories_backpaths', $done, $length);
break;
case 'dorecalccategories_complete':
$message = qa_lang('admin/recalc_categories_complete');
break;
case 'dodeletehidden_comments':
$message = qa_recalc_progress_lang('admin/hidden_comments_deleted', $done, $length);
break;
case 'dodeletehidden_answers':
$message = qa_recalc_progress_lang('admin/hidden_answers_deleted', $done, $length);
break;
case 'dodeletehidden_questions':
$message = qa_recalc_progress_lang('admin/hidden_questions_deleted', $done, $length);
break;
case 'dodeletehidden_complete':
$message = qa_lang('admin/delete_hidden_complete');
break;
case 'doblobstodisk_move':
case 'doblobstodb_move':
$message = qa_recalc_progress_lang('admin/blobs_move_moved', $done, $length);
break;
case 'doblobstodisk_complete':
case 'doblobstodb_complete':
$message = qa_lang('admin/blobs_move_complete');
break;
default:
$message = '';
break;
}
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
return $message;
}
......@@ -20,1321 +20,1394 @@
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;
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../');
exit;
}
define('QA_USER_LEVEL_BASIC', 0);
define('QA_USER_LEVEL_APPROVED', 10);
define('QA_USER_LEVEL_EXPERT', 20);
define('QA_USER_LEVEL_EDITOR', 50);
define('QA_USER_LEVEL_MODERATOR', 80);
define('QA_USER_LEVEL_ADMIN', 100);
define('QA_USER_LEVEL_SUPER', 120);
define('QA_USER_FLAGS_EMAIL_CONFIRMED', 1);
define('QA_USER_FLAGS_USER_BLOCKED', 2);
define('QA_USER_FLAGS_SHOW_AVATAR', 4);
define('QA_USER_FLAGS_SHOW_GRAVATAR', 8);
define('QA_USER_FLAGS_NO_MESSAGES', 16);
define('QA_USER_FLAGS_NO_MAILINGS', 32);
define('QA_USER_FLAGS_WELCOME_NOTICE', 64);
define('QA_USER_FLAGS_MUST_CONFIRM', 128);
define('QA_USER_FLAGS_NO_WALL_POSTS', 256);
define('QA_USER_FLAGS_MUST_APPROVE', 512);
define('QA_FIELD_FLAGS_MULTI_LINE', 1);
define('QA_FIELD_FLAGS_LINK_URL', 2);
define('QA_FIELD_FLAGS_ON_REGISTER', 4);
@define('QA_FORM_EXPIRY_SECS', 86400); // how many seconds a form is valid for submission
@define('QA_FORM_KEY_LENGTH', 32);
if (QA_FINAL_EXTERNAL_USERS) {
// If we're using single sign-on integration (WordPress or otherwise), load PHP file for that
if (defined('QA_FINAL_WORDPRESS_INTEGRATE_PATH')) {
require_once QA_INCLUDE_DIR . 'util/external-users-wp.php';
} elseif (defined('QA_FINAL_JOOMLA_INTEGRATE_PATH')) {
require_once QA_INCLUDE_DIR . 'util/external-users-joomla.php';
} else {
require_once QA_EXTERNAL_DIR . 'qa-external-users.php';
}
define('QA_USER_LEVEL_BASIC', 0);
define('QA_USER_LEVEL_APPROVED', 10);
define('QA_USER_LEVEL_EXPERT', 20);
define('QA_USER_LEVEL_EDITOR', 50);
define('QA_USER_LEVEL_MODERATOR', 80);
define('QA_USER_LEVEL_ADMIN', 100);
define('QA_USER_LEVEL_SUPER', 120);
define('QA_USER_FLAGS_EMAIL_CONFIRMED', 1);
define('QA_USER_FLAGS_USER_BLOCKED', 2);
define('QA_USER_FLAGS_SHOW_AVATAR', 4);
define('QA_USER_FLAGS_SHOW_GRAVATAR', 8);
define('QA_USER_FLAGS_NO_MESSAGES', 16);
define('QA_USER_FLAGS_NO_MAILINGS', 32);
define('QA_USER_FLAGS_WELCOME_NOTICE', 64);
define('QA_USER_FLAGS_MUST_CONFIRM', 128);
define('QA_USER_FLAGS_NO_WALL_POSTS', 256);
define('QA_USER_FLAGS_MUST_APPROVE', 512);
define('QA_FIELD_FLAGS_MULTI_LINE', 1);
define('QA_FIELD_FLAGS_LINK_URL', 2);
define('QA_FIELD_FLAGS_ON_REGISTER', 4);
// Access functions for user information
@define('QA_FORM_EXPIRY_SECS', 86400); // how many seconds a form is valid for submission
@define('QA_FORM_KEY_LENGTH', 32);
if (QA_FINAL_EXTERNAL_USERS) {
/**
* Return array of information about the currently logged in user, cache to ensure only one call to external code
*/
function qa_get_logged_in_user_cache()
{
global $qa_cached_logged_in_user;
// If we're using single sign-on integration (WordPress or otherwise), load PHP file for that
if (!isset($qa_cached_logged_in_user)) {
$user = qa_get_logged_in_user();
if (defined('QA_FINAL_WORDPRESS_INTEGRATE_PATH')) {
require_once QA_INCLUDE_DIR.'util/external-users-wp.php';
}
elseif (defined('QA_FINAL_JOOMLA_INTEGRATE_PATH')) {
require_once QA_INCLUDE_DIR.'util/external-users-joomla.php';
}
else {
require_once QA_EXTERNAL_DIR.'qa-external-users.php';
if (isset($user)) {
$user['flags'] = isset($user['blocked']) ? QA_USER_FLAGS_USER_BLOCKED : 0;
$qa_cached_logged_in_user = $user;
} else
$qa_cached_logged_in_user = false;
}
// Access functions for user information
return @$qa_cached_logged_in_user;
}
function qa_get_logged_in_user_cache()
/*
Return array of information about the currently logged in user, cache to ensure only one call to external code
*/
{
global $qa_cached_logged_in_user;
if (!isset($qa_cached_logged_in_user)) {
$user = qa_get_logged_in_user();
/**
* Return $field of the currently logged in user, or null if not available
* @param $field
* @return null
*/
function qa_get_logged_in_user_field($field)
{
$user = qa_get_logged_in_user_cache();
if (isset($user)) {
$user['flags'] = isset($user['blocked']) ? QA_USER_FLAGS_USER_BLOCKED : 0;
$qa_cached_logged_in_user = $user;
}
else
$qa_cached_logged_in_user = false;
}
return isset($user[$field]) ? $user[$field] : null;
}
return @$qa_cached_logged_in_user;
}
/**
* Return the userid of the currently logged in user, or null if none
*/
function qa_get_logged_in_userid()
{
return qa_get_logged_in_user_field('userid');
}
function qa_get_logged_in_user_field($field)
/*
Return $field of the currently logged in user, or null if not available
*/
{
$user=qa_get_logged_in_user_cache();
return isset($user[$field]) ? $user[$field] : null;
}
/**
* Return the number of points of the currently logged in user, or null if none is logged in
*/
function qa_get_logged_in_points()
{
global $qa_cached_logged_in_points;
if (!isset($qa_cached_logged_in_points)) {
require_once QA_INCLUDE_DIR . 'db/selects.php';
function qa_get_logged_in_userid()
/*
Return the userid of the currently logged in user, or null if none
*/
{
return qa_get_logged_in_user_field('userid');
$qa_cached_logged_in_points = qa_db_select_with_pending(qa_db_user_points_selectspec(qa_get_logged_in_userid(), true));
}
return $qa_cached_logged_in_points['points'];
}
function qa_get_logged_in_points()
/*
Return the number of points of the currently logged in user, or null if none is logged in
*/
{
global $qa_cached_logged_in_points;
if (!isset($qa_cached_logged_in_points)) {
require_once QA_INCLUDE_DIR.'db/selects.php';
/**
* Return HTML to display for the avatar of $userid, constrained to $size pixels, with optional $padding to that size
* @param $userid
* @param $size
* @param bool $padding
* @return mixed|null|string
*/
function qa_get_external_avatar_html($userid, $size, $padding = false)
{
if (function_exists('qa_avatar_html_from_userid'))
return qa_avatar_html_from_userid($userid, $size, $padding);
else
return null;
}
$qa_cached_logged_in_points=qa_db_select_with_pending(qa_db_user_points_selectspec(qa_get_logged_in_userid(), true));
}
return $qa_cached_logged_in_points['points'];
}
} else {
/**
* Open a PHP session if one isn't opened already
*/
function qa_start_session()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
function qa_get_external_avatar_html($userid, $size, $padding=false)
/*
Return HTML to display for the avatar of $userid, constrained to $size pixels, with optional $padding to that size
*/
{
if (function_exists('qa_avatar_html_from_userid'))
return qa_avatar_html_from_userid($userid, $size, $padding);
else
return null;
}
@ini_set('session.gc_maxlifetime', 86400); // worth a try, but won't help in shared hosting environment
@ini_set('session.use_trans_sid', false); // sessions need cookies to work, since we redirect after login
@ini_set('session.cookie_domain', QA_COOKIE_DOMAIN);
if (!isset($_SESSION))
session_start();
}
} else {
function qa_start_session()
/*
Open a PHP session if one isn't opened already
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Returns a suffix to be used for names of session variables to prevent them being shared between multiple Q2A sites on the same server
*/
function qa_session_var_suffix()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
@ini_set('session.gc_maxlifetime', 86400); // worth a try, but won't help in shared hosting environment
@ini_set('session.use_trans_sid', false); // sessions need cookies to work, since we redirect after login
@ini_set('session.cookie_domain', QA_COOKIE_DOMAIN);
global $qa_session_suffix;
if (!isset($_SESSION))
session_start();
if (!$qa_session_suffix) {
$prefix = defined('QA_MYSQL_USERS_PREFIX') ? QA_MYSQL_USERS_PREFIX : QA_MYSQL_TABLE_PREFIX;
$qa_session_suffix = md5(QA_FINAL_MYSQL_HOSTNAME . '/' . QA_FINAL_MYSQL_USERNAME . '/' . QA_FINAL_MYSQL_PASSWORD . '/' . QA_FINAL_MYSQL_DATABASE . '/' . $prefix);
}
function qa_session_var_suffix()
/*
Returns a suffix to be used for names of session variables to prevent them being shared between multiple Q2A sites on the same server
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
global $qa_session_suffix;
if (!$qa_session_suffix) {
$prefix=defined('QA_MYSQL_USERS_PREFIX') ? QA_MYSQL_USERS_PREFIX : QA_MYSQL_TABLE_PREFIX;
$qa_session_suffix = md5(QA_FINAL_MYSQL_HOSTNAME.'/'.QA_FINAL_MYSQL_USERNAME.'/'.QA_FINAL_MYSQL_PASSWORD.'/'.QA_FINAL_MYSQL_DATABASE.'/'.$prefix);
}
return $qa_session_suffix;
}
return $qa_session_suffix;
}
function qa_session_verify_code($userid)
/*
Returns a verification code used to ensure that a user session can't be generated by another PHP script running on the same server
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Returns a verification code used to ensure that a user session can't be generated by another PHP script running on the same server
* @param $userid
* @return mixed|string
*/
function qa_session_verify_code($userid)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
return sha1($userid.'/'.QA_MYSQL_TABLE_PREFIX.'/'.QA_FINAL_MYSQL_DATABASE.'/'.QA_FINAL_MYSQL_PASSWORD.'/'.QA_FINAL_MYSQL_USERNAME.'/'.QA_FINAL_MYSQL_HOSTNAME);
}
return sha1($userid . '/' . QA_MYSQL_TABLE_PREFIX . '/' . QA_FINAL_MYSQL_DATABASE . '/' . QA_FINAL_MYSQL_PASSWORD . '/' . QA_FINAL_MYSQL_USERNAME . '/' . QA_FINAL_MYSQL_HOSTNAME);
}
function qa_set_session_cookie($handle, $sessioncode, $remember)
/*
Set cookie in browser for username $handle with $sessioncode (in database).
Pass true if user checked 'Remember me' (either now or previously, as learned from cookie).
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Set cookie in browser for username $handle with $sessioncode (in database).
* Pass true if user checked 'Remember me' (either now or previously, as learned from cookie).
* @param $handle
* @param $sessioncode
* @param $remember
* @return mixed
*/
function qa_set_session_cookie($handle, $sessioncode, $remember)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
// if $remember is true, store in browser for a month, otherwise store only until browser is closed
setcookie('qa_session', $handle.'/'.$sessioncode.'/'.($remember ? 1 : 0), $remember ? (time()+2592000) : 0, '/', QA_COOKIE_DOMAIN, (bool)ini_get('session.cookie_secure'), true);
}
// if $remember is true, store in browser for a month, otherwise store only until browser is closed
setcookie('qa_session', $handle . '/' . $sessioncode . '/' . ($remember ? 1 : 0), $remember ? (time() + 2592000) : 0, '/', QA_COOKIE_DOMAIN, (bool)ini_get('session.cookie_secure'), true);
}
function qa_clear_session_cookie()
/*
Remove session cookie from browser
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Remove session cookie from browser
*/
function qa_clear_session_cookie()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
setcookie('qa_session', false, 0, '/', QA_COOKIE_DOMAIN, (bool)ini_get('session.cookie_secure'), true);
}
setcookie('qa_session', false, 0, '/', QA_COOKIE_DOMAIN, (bool)ini_get('session.cookie_secure'), true);
}
function qa_set_session_user($userid, $source)
/*
Set the session variables to indicate that $userid is logged in from $source
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Set the session variables to indicate that $userid is logged in from $source
* @param $userid
* @param $source
* @return mixed
*/
function qa_set_session_user($userid, $source)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$suffix=qa_session_var_suffix();
$suffix = qa_session_var_suffix();
$_SESSION['qa_session_userid_'.$suffix]=$userid;
$_SESSION['qa_session_source_'.$suffix]=$source;
$_SESSION['qa_session_verify_'.$suffix]=qa_session_verify_code($userid);
// prevents one account on a shared server being able to create a log in a user to Q2A on another account on same server
}
$_SESSION['qa_session_userid_' . $suffix] = $userid;
$_SESSION['qa_session_source_' . $suffix] = $source;
// prevents one account on a shared server being able to create a log in a user to Q2A on another account on same server
$_SESSION['qa_session_verify_' . $suffix] = qa_session_verify_code($userid);
}
function qa_clear_session_user()
/*
Clear the session variables indicating that a user is logged in
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Clear the session variables indicating that a user is logged in
*/
function qa_clear_session_user()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$suffix=qa_session_var_suffix();
$suffix = qa_session_var_suffix();
unset($_SESSION['qa_session_userid_'.$suffix]);
unset($_SESSION['qa_session_source_'.$suffix]);
unset($_SESSION['qa_session_verify_'.$suffix]);
}
unset($_SESSION['qa_session_userid_' . $suffix]);
unset($_SESSION['qa_session_source_' . $suffix]);
unset($_SESSION['qa_session_verify_' . $suffix]);
}
function qa_set_logged_in_user($userid, $handle='', $remember=false, $source=null)
/*
Call for successful log in by $userid and $handle or successful log out with $userid=null.
$remember states if 'Remember me' was checked in the login form.
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Call for successful log in by $userid and $handle or successful log out with $userid=null.
* $remember states if 'Remember me' was checked in the login form.
* @param $userid
* @param string $handle
* @param bool $remember
* @param $source
* @return mixed
*/
function qa_set_logged_in_user($userid, $handle = '', $remember = false, $source = null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
require_once QA_INCLUDE_DIR.'app/cookies.php';
require_once QA_INCLUDE_DIR . 'app/cookies.php';
qa_start_session();
qa_start_session();
if (isset($userid)) {
qa_set_session_user($userid, $source);
if (isset($userid)) {
qa_set_session_user($userid, $source);
// PHP sessions time out too quickly on the server side, so we also set a cookie as backup.
// Logging in from a second browser will make the previous browser's 'Remember me' no longer
// work - I'm not sure if this is the right behavior - could see it either way.
// PHP sessions time out too quickly on the server side, so we also set a cookie as backup.
// Logging in from a second browser will make the previous browser's 'Remember me' no longer
// work - I'm not sure if this is the right behavior - could see it either way.
require_once QA_INCLUDE_DIR.'db/selects.php';
require_once QA_INCLUDE_DIR . 'db/selects.php';
$userinfo=qa_db_single_select(qa_db_user_account_selectspec($userid, true));
$userinfo = qa_db_single_select(qa_db_user_account_selectspec($userid, true));
// if we have logged in before, and are logging in the same way as before, we don't need to change the sessioncode/source
// this means it will be possible to automatically log in (via cookies) to the same account from more than one browser
// if we have logged in before, and are logging in the same way as before, we don't need to change the sessioncode/source
// this means it will be possible to automatically log in (via cookies) to the same account from more than one browser
if (empty($userinfo['sessioncode']) || ($source!==$userinfo['sessionsource'])) {
$sessioncode=qa_db_user_rand_sessioncode();
qa_db_user_set($userid, 'sessioncode', $sessioncode);
qa_db_user_set($userid, 'sessionsource', $source);
} else
$sessioncode=$userinfo['sessioncode'];
if (empty($userinfo['sessioncode']) || ($source !== $userinfo['sessionsource'])) {
$sessioncode = qa_db_user_rand_sessioncode();
qa_db_user_set($userid, 'sessioncode', $sessioncode);
qa_db_user_set($userid, 'sessionsource', $source);
} else
$sessioncode = $userinfo['sessioncode'];
qa_db_user_logged_in($userid, qa_remote_ip_address());
qa_set_session_cookie($handle, $sessioncode, $remember);
qa_db_user_logged_in($userid, qa_remote_ip_address());
qa_set_session_cookie($handle, $sessioncode, $remember);
qa_report_event('u_login', $userid, $userinfo['handle'], qa_cookie_get());
qa_report_event('u_login', $userid, $userinfo['handle'], qa_cookie_get());
} else {
$olduserid=qa_get_logged_in_userid();
$oldhandle=qa_get_logged_in_handle();
} else {
$olduserid = qa_get_logged_in_userid();
$oldhandle = qa_get_logged_in_handle();
qa_clear_session_cookie();
qa_clear_session_user();
qa_clear_session_cookie();
qa_clear_session_user();
qa_report_event('u_logout', $olduserid, $oldhandle, qa_cookie_get());
}
qa_report_event('u_logout', $olduserid, $oldhandle, qa_cookie_get());
}
}
function qa_log_in_external_user($source, $identifier, $fields)
/*
Call to log in a user based on an external identity provider $source with external $identifier
A new user is created based on $fields if it's a new combination of $source and $identifier
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Call to log in a user based on an external identity provider $source with external $identifier
* A new user is created based on $fields if it's a new combination of $source and $identifier
* @param $source
* @param $identifier
* @param $fields
* @return mixed
*/
function qa_log_in_external_user($source, $identifier, $fields)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
require_once QA_INCLUDE_DIR.'db/users.php';
require_once QA_INCLUDE_DIR . 'db/users.php';
$users=qa_db_user_login_find($source, $identifier);
$countusers=count($users);
$users = qa_db_user_login_find($source, $identifier);
$countusers = count($users);
if ($countusers>1)
qa_fatal_error('External login mapped to more than one user'); // should never happen
if ($countusers > 1)
qa_fatal_error('External login mapped to more than one user'); // should never happen
if ($countusers) // user exists so log them in
qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
if ($countusers) // user exists so log them in
qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
else { // create and log in user
require_once QA_INCLUDE_DIR.'app/users-edit.php';
else { // create and log in user
require_once QA_INCLUDE_DIR . 'app/users-edit.php';
qa_db_user_login_sync(true);
qa_db_user_login_sync(true);
$users=qa_db_user_login_find($source, $identifier); // check again after table is locked
$users = qa_db_user_login_find($source, $identifier); // check again after table is locked
if (count($users)==1) {
qa_db_user_login_sync(false);
qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
if (count($users) == 1) {
qa_db_user_login_sync(false);
qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
} else {
$handle=qa_handle_make_valid(@$fields['handle']);
if (strlen(@$fields['email'])) { // remove email address if it will cause a duplicate
$emailusers=qa_db_user_find_by_email($fields['email']);
if (count($emailusers)) {
qa_redirect('login', array('e' => $fields['email'], 'ee' => '1'));
unset($fields['email']);
unset($fields['confirmed']);
}
} else {
$handle = qa_handle_make_valid(@$fields['handle']);
if (strlen(@$fields['email'])) { // remove email address if it will cause a duplicate
$emailusers = qa_db_user_find_by_email($fields['email']);
if (count($emailusers)) {
qa_redirect('login', array('e' => $fields['email'], 'ee' => '1'));
unset($fields['email']);
unset($fields['confirmed']);
}
$userid=qa_create_new_user((string)@$fields['email'], null /* no password */, $handle,
isset($fields['level']) ? $fields['level'] : QA_USER_LEVEL_BASIC, @$fields['confirmed']);
qa_db_user_login_add($userid, $source, $identifier);
qa_db_user_login_sync(false);
$profilefields=array('name', 'location', 'website', 'about');
foreach ($profilefields as $fieldname)
if (strlen(@$fields[$fieldname]))
qa_db_user_profile_set($userid, $fieldname, $fields[$fieldname]);
if (strlen(@$fields['avatar']))
qa_set_user_avatar($userid, $fields['avatar']);
qa_set_logged_in_user($userid, $handle, false, $source);
}
}
}
function qa_get_logged_in_userid()
/*
Return the userid of the currently logged in user, or null if none logged in
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
global $qa_logged_in_userid_checked;
$suffix=qa_session_var_suffix();
if (!$qa_logged_in_userid_checked) { // only check once
qa_start_session(); // this will load logged in userid from the native PHP session, but that's not enough
$sessionuserid=@$_SESSION['qa_session_userid_'.$suffix];
$userid = qa_create_new_user((string)@$fields['email'], null /* no password */, $handle,
isset($fields['level']) ? $fields['level'] : QA_USER_LEVEL_BASIC, @$fields['confirmed']);
if (isset($sessionuserid)) // check verify code matches
if (!hash_equals(qa_session_verify_code($sessionuserid), @$_SESSION['qa_session_verify_'.$suffix]))
qa_clear_session_user();
qa_db_user_login_add($userid, $source, $identifier);
qa_db_user_login_sync(false);
if (!empty($_COOKIE['qa_session'])) {
@list($handle, $sessioncode, $remember)=explode('/', $_COOKIE['qa_session']);
$profilefields = array('name', 'location', 'website', 'about');
if ($remember)
qa_set_session_cookie($handle, $sessioncode, $remember); // extend 'remember me' cookies each time
$sessioncode=trim($sessioncode); // trim to prevent passing in blank values to match uninitiated DB rows
// Try to recover session from the database if PHP session has timed out
if ( (!isset($_SESSION['qa_session_userid_'.$suffix])) && (!empty($handle)) && (!empty($sessioncode)) ) {
require_once QA_INCLUDE_DIR.'db/selects.php';
$userinfo=qa_db_single_select(qa_db_user_account_selectspec($handle, false)); // don't get any pending
if (strtolower(trim($userinfo['sessioncode'])) == strtolower($sessioncode))
qa_set_session_user($userinfo['userid'], $userinfo['sessionsource']);
else
qa_clear_session_cookie(); // if cookie not valid, remove it to save future checks
}
foreach ($profilefields as $fieldname) {
if (strlen(@$fields[$fieldname]))
qa_db_user_profile_set($userid, $fieldname, $fields[$fieldname]);
}
$qa_logged_in_userid_checked=true;
}
return @$_SESSION['qa_session_userid_'.$suffix];
}
function qa_get_logged_in_source()
/*
Get the source of the currently logged in user, from call to qa_log_in_external_user() or null if logged in normally
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$userid=qa_get_logged_in_userid();
$suffix=qa_session_var_suffix();
if (isset($userid))
return @$_SESSION['qa_session_source_'.$suffix];
}
/**
* Return array of information about the currently logged in user, cache to ensure only one call to external code
*/
function qa_get_logged_in_user_cache()
{
global $qa_cached_logged_in_user;
if (strlen(@$fields['avatar']))
qa_set_user_avatar($userid, $fields['avatar']);
if (!isset($qa_cached_logged_in_user)) {
$userid = qa_get_logged_in_userid();
if (isset($userid)) {
require_once QA_INCLUDE_DIR.'db/selects.php';
$qa_cached_logged_in_user = qa_db_get_pending_result('loggedinuser', qa_db_user_account_selectspec($userid, true));
if (!isset($qa_cached_logged_in_user)) {
// the user can no longer be found (should only apply to deleted users)
qa_clear_session_user();
qa_redirect(''); // implicit exit;
}
}
qa_set_logged_in_user($userid, $handle, false, $source);
}
return $qa_cached_logged_in_user;
}
}
/**
* Return $field of the currently logged in user
*/
function qa_get_logged_in_user_field($field)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$usercache = qa_get_logged_in_user_cache();
/**
* Return the userid of the currently logged in user, or null if none logged in
*/
function qa_get_logged_in_userid()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
return isset($usercache[$field]) ? $usercache[$field] : null;
}
global $qa_logged_in_userid_checked;
$suffix = qa_session_var_suffix();
function qa_get_logged_in_points()
/*
Return the number of points of the currently logged in user, or null if none is logged in
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if (!$qa_logged_in_userid_checked) { // only check once
qa_start_session(); // this will load logged in userid from the native PHP session, but that's not enough
return qa_get_logged_in_user_field('points');
}
$sessionuserid = @$_SESSION['qa_session_userid_' . $suffix];
if (isset($sessionuserid)) // check verify code matches
if (!hash_equals(qa_session_verify_code($sessionuserid), @$_SESSION['qa_session_verify_' . $suffix]))
qa_clear_session_user();
function qa_get_mysql_user_column_type()
/*
Return column type to use for users (if not using single sign-on integration)
*/
{
return 'INT UNSIGNED';
}
if (!empty($_COOKIE['qa_session'])) {
@list($handle, $sessioncode, $remember) = explode('/', $_COOKIE['qa_session']);
if ($remember)
qa_set_session_cookie($handle, $sessioncode, $remember); // extend 'remember me' cookies each time
/**
* Return the URL to the $blobId with a stored size of $width and $height.
* Constrain the image to $size (width AND height)
*
* @param string $blobId The blob ID from the image
* @param int|null $size The resulting image's size. If omitted the original image size will be used. If the
* size is present it must be greater than 0
* @param bool $absolute Whether the link returned should be absolute or relative
* @return string|null The URL to the avatar or null if the $blobId was empty or the $size not valid
*/
function qa_get_avatar_blob_url($blobId, $size = null, $absolute = false)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$sessioncode = trim($sessioncode); // trim to prevent passing in blank values to match uninitiated DB rows
require_once QA_INCLUDE_DIR . 'util/image.php';
// Try to recover session from the database if PHP session has timed out
if ((!isset($_SESSION['qa_session_userid_' . $suffix])) && (!empty($handle)) && (!empty($sessioncode))) {
require_once QA_INCLUDE_DIR . 'db/selects.php';
if (strlen($blobId) == 0 || (isset($size) && (int)$size <= 0)) {
return null;
}
$userinfo = qa_db_single_select(qa_db_user_account_selectspec($handle, false)); // don't get any pending
$params = array('qa_blobid' => $blobId);
if (isset($size)) {
$params['qa_size'] = $size;
if (strtolower(trim($userinfo['sessioncode'])) == strtolower($sessioncode))
qa_set_session_user($userinfo['userid'], $userinfo['sessionsource']);
else
qa_clear_session_cookie(); // if cookie not valid, remove it to save future checks
}
}
$rootUrl = $absolute ? qa_opt('site_url') : null;
return qa_path('image', $params, $rootUrl, QA_URL_FORMAT_PARAMS);
}
/**
* Get HTML to display a username, linked to their user page.
*
* @param string $handle The username.
* @param bool $microdata Whether to include microdata.
* @param bool $favorited Show the user as favorited.
* @return string The user HTML.
*/
function qa_get_one_user_html($handle, $microdata=false, $favorited=false)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if (!strlen($handle))
return '';
$url = qa_path_html('user/'.$handle);
$favclass = $favorited ? ' qa-user-favorited' : '';
$mfAttr = $microdata ? ' itemprop="name"' : '';
$mfPrefix = $microdata ? '<span itemprop="author" itemscope itemtype="http://schema.org/Person">' : '';
$mfSuffix = $microdata ? '</span>' : '';
return $mfPrefix . '<a href="'.$url.'" class="qa-user-link'.$favclass.'"'.$mfAttr.'>'.qa_html($handle).'</a>' . $mfSuffix;
$qa_logged_in_userid_checked = true;
}
return @$_SESSION['qa_session_userid_' . $suffix];
}
/**
* Return the URL for the Gravatar corresponding to $email, constrained to $size
*
* @param string $email The email of the Gravatar to return
* @param int|null $size The size of the Gravatar to return. If omitted the default size will be used
* @return string The URL to the Gravatar of the user
*/
function qa_get_gravatar_url($email, $size = null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$link = 'https://www.gravatar.com/avatar/%s';
$params = array(md5(strtolower(trim($email))));
$size = (int)$size;
if ($size > 0) {
$link .= '?s=%d';
$params[] = $size;
}
return vsprintf($link, $params);
}
/**
* Get the source of the currently logged in user, from call to qa_log_in_external_user() or null if logged in normally
*/
function qa_get_logged_in_source()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$userid = qa_get_logged_in_userid();
$suffix = qa_session_var_suffix();
/**
* Return where the avatar will be fetched from for the given user flags. The possible return values are
* 'gravatar' for an avatar that will be fetched from Gravatar, 'local-user' for an avatar fetched locally from
* the user's profile, 'local-default' for an avatar fetched locally from the default avatar blob ID, and NULL
* if the avatar could not be fetched from any of these sources
*
* @param int $flags The user's flags
* @param string|null $email The user's email
* @param string|null $blobId The blob ID for a locally stored avatar.
* @return string|null The source of the avatar: 'gravatar', 'local-user', 'local-default' and null
*/
function qa_get_user_avatar_source($flags, $email, $blobId)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if (qa_opt('avatar_allow_gravatar') && (($flags & QA_USER_FLAGS_SHOW_GRAVATAR) > 0) && isset($email)) {
return 'gravatar';
} elseif (qa_opt('avatar_allow_upload') && (($flags & QA_USER_FLAGS_SHOW_AVATAR) > 0) && isset($blobId)) {
return 'local-user';
} elseif ((qa_opt('avatar_allow_gravatar') || qa_opt('avatar_allow_upload')) && qa_opt('avatar_default_show') && strlen(qa_opt('avatar_default_blobid') > 0)) {
return 'local-default';
} else {
return null;
}
}
if (isset($userid))
return @$_SESSION['qa_session_source_' . $suffix];
}
/**
* Return the avatar URL, either Gravatar or from a blob ID, constrained to $size pixels.
*
* @param int $flags The user's flags
* @param string $email The user's email. Only needed to return the Gravatar link
* @param string $blobId The blob ID. Only needed to return the locally stored avatar
* @param int $size The size to constrain the final image
* @param bool $absolute Whether the link returned should be absolute or relative
* @return null|string The URL to the user's avatar or null if none could be found (not even as a default site avatar)
*/
function qa_get_user_avatar_url($flags, $email, $blobId, $size = null, $absolute = false)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$avatarSource = qa_get_user_avatar_source($flags, $email, $blobId);
switch ($avatarSource) {
case 'gravatar':
return qa_get_gravatar_url($email, $size);
case 'local-user':
return qa_get_avatar_blob_url($blobId, $size, $absolute);
case 'local-default':
return qa_get_avatar_blob_url(qa_opt('avatar_default_blobid'), $size, $absolute);
default: // NULL
return null;
}
}
/**
* Return array of information about the currently logged in user, cache to ensure only one call to external code
*/
function qa_get_logged_in_user_cache()
{
global $qa_cached_logged_in_user;
if (!isset($qa_cached_logged_in_user)) {
$userid = qa_get_logged_in_userid();
/**
* Return HTML to display for the user's avatar, constrained to $size pixels, with optional $padding to that size
*
* @param int $flags The user's flags
* @param string $email The user's email. Only needed to return the Gravatar HTML
* @param string $blobId The blob ID. Only needed to return the locally stored avatar HTML
* @param string $handle The handle of the user that the avatar will link to
* @param string $blobId The blob ID. Only needed to return the locally stored avatar
* @param int $width The width to constrain the image
* @param int $height The height to constrain the image
* @param int $size The size to constrain the final image
* @param bool $padding HTML padding to add to the image
* @return string|null The HTML to the user's avatar or null if no valid source for the avatar could be found
*/
function qa_get_user_avatar_html($flags, $email, $handle, $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 . 'app/format.php';
if (strlen($handle) == 0) {
return null;
}
if (isset($userid)) {
require_once QA_INCLUDE_DIR . 'db/selects.php';
$qa_cached_logged_in_user = qa_db_get_pending_result('loggedinuser', qa_db_user_account_selectspec($userid, true));
$avatarSource = qa_get_user_avatar_source($flags, $email, $blobId);
switch ($avatarSource) {
case 'gravatar':
$html = qa_get_gravatar_html($email, $size);
break;
case 'local-user':
$html = qa_get_avatar_blob_html($blobId, $width, $height, $size, $padding);
break;
case 'local-default':
$html = qa_get_avatar_blob_html(qa_opt('avatar_default_blobid'), qa_opt('avatar_default_width'), qa_opt('avatar_default_height'), $size, $padding);
break;
default: // NULL
return null;
if (!isset($qa_cached_logged_in_user)) {
// the user can no longer be found (should only apply to deleted users)
qa_clear_session_user();
qa_redirect(''); // implicit exit;
}
}
return sprintf('<a href="%s" class="qa-avatar-link">%s</a>', qa_path_html('user/' . $handle), $html);
}
function qa_get_user_email($userid)
/*
Return email address for user $userid (if not using single sign-on integration)
*/
{
$userinfo=qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true));
return $userinfo['email'];
}
function qa_user_report_action($userid, $action)
/*
Called after a database write $action performed by a user $userid
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
require_once QA_INCLUDE_DIR.'db/users.php';
qa_db_user_written($userid, qa_remote_ip_address());
}
function qa_user_level_string($level)
/*
Return textual representation of the user $level
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if ($level>=QA_USER_LEVEL_SUPER)
$string='users/level_super';
elseif ($level>=QA_USER_LEVEL_ADMIN)
$string='users/level_admin';
elseif ($level>=QA_USER_LEVEL_MODERATOR)
$string='users/level_moderator';
elseif ($level>=QA_USER_LEVEL_EDITOR)
$string='users/level_editor';
elseif ($level>=QA_USER_LEVEL_EXPERT)
$string='users/level_expert';
elseif ($level>=QA_USER_LEVEL_APPROVED)
$string='users/approved_user';
else
$string='users/registered_user';
return qa_lang($string);
}
function qa_get_login_links($rooturl, $tourl)
/*
Return an array of links to login, register, email confirm and logout pages (if not using single sign-on integration)
*/
{
return array(
'login' => qa_path('login', isset($tourl) ? array('to' => $tourl) : null, $rooturl),
'register' => qa_path('register', isset($tourl) ? array('to' => $tourl) : null, $rooturl),
'confirm' => qa_path('confirm', null, $rooturl),
'logout' => qa_path('logout', null, $rooturl),
);
}
} // end of: if (QA_FINAL_EXTERNAL_USERS) { ... } else { ... }
function qa_is_logged_in()
/*
Return whether someone is logged in at the moment
*/
{
$userid=qa_get_logged_in_userid();
return isset($userid);
return $qa_cached_logged_in_user;
}
function qa_get_logged_in_handle()
/*
Return displayable handle/username of currently logged in user, or null if none
*/
/**
* Return $field of the currently logged in user
* @param $field
* @return mixed|null
*/
function qa_get_logged_in_user_field($field)
{
return qa_get_logged_in_user_field(QA_FINAL_EXTERNAL_USERS ? 'publicusername' : 'handle');
}
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$usercache = qa_get_logged_in_user_cache();
function qa_get_logged_in_email()
/*
Return email of currently logged in user, or null if none
*/
{
return qa_get_logged_in_user_field('email');
return isset($usercache[$field]) ? $usercache[$field] : null;
}
function qa_get_logged_in_level()
/*
Return level of currently logged in user, or null if none
*/
/**
* Return the number of points of the currently logged in user, or null if none is logged in
*/
function qa_get_logged_in_points()
{
return qa_get_logged_in_user_field('level');
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
return qa_get_logged_in_user_field('points');
}
function qa_get_logged_in_flags()
/*
Return flags (see QA_USER_FLAGS_*) of currently logged in user, or null if none
*/
/**
* Return column type to use for users (if not using single sign-on integration)
*/
function qa_get_mysql_user_column_type()
{
if (QA_FINAL_EXTERNAL_USERS)
return qa_get_logged_in_user_field('blocked') ? QA_USER_FLAGS_USER_BLOCKED : 0;
else
return qa_get_logged_in_user_field('flags');
return 'INT UNSIGNED';
}
function qa_get_logged_in_levels()
/*
Return an array of all the specific (e.g. per category) level privileges for the logged in user, retrieving from the database if necessary
*/
/**
* Return the URL to the $blobId with a stored size of $width and $height.
* Constrain the image to $size (width AND height)
*
* @param string $blobId The blob ID from the image
* @param int|null $size The resulting image's size. If omitted the original image size will be used. If the
* size is present it must be greater than 0
* @param bool $absolute Whether the link returned should be absolute or relative
* @return string|null The URL to the avatar or null if the $blobId was empty or the $size not valid
*/
function qa_get_avatar_blob_url($blobId, $size = null, $absolute = false)
{
require_once QA_INCLUDE_DIR.'db/selects.php';
return qa_db_get_pending_result('userlevels', qa_db_user_levels_selectspec(qa_get_logged_in_userid(), true));
}
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
require_once QA_INCLUDE_DIR . 'util/image.php';
function qa_userids_to_handles($userids)
/*
Return an array mapping each userid in $userids to that user's handle (public username), or to null if not found
*/
{
if (QA_FINAL_EXTERNAL_USERS)
$rawuseridhandles=qa_get_public_from_userids($userids);
if (strlen($blobId) == 0 || (isset($size) && (int)$size <= 0)) {
return null;
}
else {
require_once QA_INCLUDE_DIR.'db/users.php';
$rawuseridhandles=qa_db_user_get_userid_handles($userids);
$params = array('qa_blobid' => $blobId);
if (isset($size)) {
$params['qa_size'] = $size;
}
$gotuseridhandles=array();
foreach ($userids as $userid)
$gotuseridhandles[$userid]=@$rawuseridhandles[$userid];
$rootUrl = $absolute ? qa_opt('site_url') : null;
return $gotuseridhandles;
return qa_path('image', $params, $rootUrl, QA_URL_FORMAT_PARAMS);
}
function qa_userid_to_handle($userid)
/*
Return an string mapping the received userid to that user's handle (public username), or to null if not found
*/
/**
* Get HTML to display a username, linked to their user page.
*
* @param string $handle The username.
* @param bool $microdata Whether to include microdata.
* @param bool $favorited Show the user as favorited.
* @return string The user HTML.
*/
function qa_get_one_user_html($handle, $microdata = false, $favorited = false)
{
$handles=qa_userids_to_handles(array($userid));
return empty($handles) ? null : $handles[$userid];
}
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if (!strlen($handle))
return '';
function qa_handles_to_userids($handles, $exactonly=false)
/*
Return an array mapping each handle in $handles the user's userid, or null if not found. If $exactonly is true then
$handles must have the correct case and accents. Otherwise, handles are case- and accent-insensitive, and the keys
of the returned array will match the $handles provided, not necessary those in the DB.
*/
{
require_once QA_INCLUDE_DIR.'util/string.php';
$url = qa_path_html('user/' . $handle);
$favclass = $favorited ? ' qa-user-favorited' : '';
$mfAttr = $microdata ? ' itemprop="name"' : '';
$mfPrefix = $microdata ? '<span itemprop="author" itemscope itemtype="http://schema.org/Person">' : '';
$mfSuffix = $microdata ? '</span>' : '';
if (QA_FINAL_EXTERNAL_USERS)
$rawhandleuserids=qa_get_userids_from_public($handles);
return $mfPrefix . '<a href="' . $url . '" class="qa-user-link' . $favclass . '"' . $mfAttr . '>' . qa_html($handle) . '</a>' . $mfSuffix;
}
else {
require_once QA_INCLUDE_DIR.'db/users.php';
$rawhandleuserids=qa_db_user_get_handle_userids($handles);
}
$gothandleuserids=array();
/**
* Return the URL for the Gravatar corresponding to $email, constrained to $size
*
* @param string $email The email of the Gravatar to return
* @param int|null $size The size of the Gravatar to return. If omitted the default size will be used
* @return string The URL to the Gravatar of the user
*/
function qa_get_gravatar_url($email, $size = null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if ($exactonly) { // only take the exact matches
foreach ($handles as $handle)
$gothandleuserids[$handle]=@$rawhandleuserids[$handle];
$link = 'https://www.gravatar.com/avatar/%s';
} else { // normalize to lowercase without accents, and then find matches
$normhandleuserids=array();
foreach ($rawhandleuserids as $handle => $userid)
$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))]=$userid;
$params = array(md5(strtolower(trim($email))));
foreach ($handles as $handle)
$gothandleuserids[$handle]=@$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))];
$size = (int)$size;
if ($size > 0) {
$link .= '?s=%d';
$params[] = $size;
}
return $gothandleuserids;
return vsprintf($link, $params);
}
function qa_handle_to_userid($handle)
/*
Return the userid corresponding to $handle (not case- or accent-sensitive)
*/
/**
* Return where the avatar will be fetched from for the given user flags. The possible return values are
* 'gravatar' for an avatar that will be fetched from Gravatar, 'local-user' for an avatar fetched locally from
* the user's profile, 'local-default' for an avatar fetched locally from the default avatar blob ID, and NULL
* if the avatar could not be fetched from any of these sources
*
* @param int $flags The user's flags
* @param string|null $email The user's email
* @param string|null $blobId The blob ID for a locally stored avatar.
* @return string|null The source of the avatar: 'gravatar', 'local-user', 'local-default' and null
*/
function qa_get_user_avatar_source($flags, $email, $blobId)
{
if (QA_FINAL_EXTERNAL_USERS)
$handleuserids=qa_get_userids_from_public(array($handle));
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
else {
require_once QA_INCLUDE_DIR.'db/users.php';
$handleuserids=qa_db_user_get_handle_userids(array($handle));
if (qa_opt('avatar_allow_gravatar') && (($flags & QA_USER_FLAGS_SHOW_GRAVATAR) > 0) && isset($email)) {
return 'gravatar';
} elseif (qa_opt('avatar_allow_upload') && (($flags & QA_USER_FLAGS_SHOW_AVATAR) > 0) && isset($blobId)) {
return 'local-user';
} elseif ((qa_opt('avatar_allow_gravatar') || qa_opt('avatar_allow_upload')) && qa_opt('avatar_default_show') && strlen(qa_opt('avatar_default_blobid') > 0)) {
return 'local-default';
} else {
return null;
}
if (count($handleuserids)==1)
return reset($handleuserids); // don't use $handleuserids[$handle] since capitalization might be different
return null;
}
function qa_user_level_for_categories($categoryids)
/*
Return the level of the logged in user for a post with $categoryids (expressing the full hierarchy to the final category)
*/
/**
* Return the avatar URL, either Gravatar or from a blob ID, constrained to $size pixels.
*
* @param int $flags The user's flags
* @param string $email The user's email. Only needed to return the Gravatar link
* @param string $blobId The blob ID. Only needed to return the locally stored avatar
* @param int $size The size to constrain the final image
* @param bool $absolute Whether the link returned should be absolute or relative
* @return null|string The URL to the user's avatar or null if none could be found (not even as a default site avatar)
*/
function qa_get_user_avatar_url($flags, $email, $blobId, $size = null, $absolute = false)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
require_once QA_INCLUDE_DIR.'app/updates.php';
$avatarSource = qa_get_user_avatar_source($flags, $email, $blobId);
$level=qa_get_logged_in_level();
switch ($avatarSource) {
case 'gravatar':
return qa_get_gravatar_url($email, $size);
case 'local-user':
return qa_get_avatar_blob_url($blobId, $size, $absolute);
case 'local-default':
return qa_get_avatar_blob_url(qa_opt('avatar_default_blobid'), $size, $absolute);
default: // NULL
return null;
}
}
if (count($categoryids)) {
$userlevels=qa_get_logged_in_levels();
$categorylevels=array(); // create a map
foreach ($userlevels as $userlevel)
if ($userlevel['entitytype']==QA_ENTITY_CATEGORY)
$categorylevels[$userlevel['entityid']]=$userlevel['level'];
/**
* Return HTML to display for the user's avatar, constrained to $size pixels, with optional $padding to that size
*
* @param int $flags The user's flags
* @param string $email The user's email. Only needed to return the Gravatar HTML
* @param string $blobId The blob ID. Only needed to return the locally stored avatar HTML
* @param string $handle The handle of the user that the avatar will link to
* @param string $blobId The blob ID. Only needed to return the locally stored avatar
* @param int $width The width to constrain the image
* @param int $height The height to constrain the image
* @param int $size The size to constrain the final image
* @param bool $padding HTML padding to add to the image
* @return string|null The HTML to the user's avatar or null if no valid source for the avatar could be found
*/
function qa_get_user_avatar_html($flags, $email, $handle, $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 . 'app/format.php';
if (strlen($handle) == 0) {
return null;
}
foreach ($categoryids as $categoryid)
$level=max($level, @$categorylevels[$categoryid]);
$avatarSource = qa_get_user_avatar_source($flags, $email, $blobId);
switch ($avatarSource) {
case 'gravatar':
$html = qa_get_gravatar_html($email, $size);
break;
case 'local-user':
$html = qa_get_avatar_blob_html($blobId, $width, $height, $size, $padding);
break;
case 'local-default':
$html = qa_get_avatar_blob_html(qa_opt('avatar_default_blobid'), qa_opt('avatar_default_width'), qa_opt('avatar_default_height'), $size, $padding);
break;
default: // NULL
return null;
}
return $level;
return sprintf('<a href="%s" class="qa-avatar-link">%s</a>', qa_path_html('user/' . $handle), $html);
}
function qa_user_level_for_post($post)
/*
Return the level of the logged in user for $post, as retrieved from the database
*/
/**
* Return email address for user $userid (if not using single sign-on integration)
* @param $userid
* @return
*/
function qa_get_user_email($userid)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if (strlen(@$post['categoryids']))
return qa_user_level_for_categories(explode(',', $post['categoryids']));
$userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true));
return null;
return $userinfo['email'];
}
function qa_user_level_maximum()
/*
Return the maximum possible level of the logged in user in any context (i.e. for any category)
*/
/**
* Called after a database write $action performed by a user $userid
* @param $userid
* @param $action
* @return mixed
*/
function qa_user_report_action($userid, $action)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$level=qa_get_logged_in_level();
require_once QA_INCLUDE_DIR . 'db/users.php';
$userlevels=qa_get_logged_in_levels();
foreach ($userlevels as $userlevel)
$level=max($level, $userlevel['level']);
return $level;
qa_db_user_written($userid, qa_remote_ip_address());
}
function qa_user_post_permit_error($permitoption, $post, $limitaction=null, $checkblocks=true)
/*
Check whether the logged in user has permission to perform $permitoption on post $post (from the database)
Other parameters and the return value are as for qa_user_permit_error(...)
*/
/**
* Return textual representation of the user $level
* @param $level
* @return mixed|string
*/
function qa_user_level_string($level)
{
return qa_user_permit_error($permitoption, $limitaction, qa_user_level_for_post($post), $checkblocks);
}
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if ($level >= QA_USER_LEVEL_SUPER)
$string = 'users/level_super';
elseif ($level >= QA_USER_LEVEL_ADMIN)
$string = 'users/level_admin';
elseif ($level >= QA_USER_LEVEL_MODERATOR)
$string = 'users/level_moderator';
elseif ($level >= QA_USER_LEVEL_EDITOR)
$string = 'users/level_editor';
elseif ($level >= QA_USER_LEVEL_EXPERT)
$string = 'users/level_expert';
elseif ($level >= QA_USER_LEVEL_APPROVED)
$string = 'users/approved_user';
else
$string = 'users/registered_user';
function qa_user_maximum_permit_error($permitoption, $limitaction=null, $checkblocks=true)
/*
Check whether the logged in user would have permittion to perform $permitoption in any context (i.e. for any category)
Other parameters and the return value are as for qa_user_permit_error(...)
*/
{
return qa_user_permit_error($permitoption, $limitaction, qa_user_level_maximum(), $checkblocks);
return qa_lang($string);
}
/**
* Check whether the logged in user has permission to perform an action.
*
* @param string $permitoption The permission to check (if null, this simply checks whether the user is blocked).
* @param string $limitaction Constant from qa-app-limits.php to check against user or IP rate limits.
* @param int $userlevel A QA_USER_LEVEL_* constant to consider the user at a different level to usual (e.g. if
* they are performing this action in a category for which they have elevated privileges).
* @param bool $checkblocks Whether to check the user's blocked status.
* @param array $userfields Cache for logged in user, containing keys 'userid', 'level' (optional), 'flags'.
*
* @return bool|string The permission error, or false if no error. Possible errors, in order of priority:
* 'login' => the user should login or register
* 'level' => a special privilege level (e.g. expert) or minimum number of points is required
* 'userblock' => the user has been blocked
* 'ipblock' => the ip address has been blocked
* 'confirm' => the user should confirm their email address
* 'approve' => the user needs to be approved by the site admins
* 'limit' => the user or IP address has reached a rate limit (if $limitaction specified)
* false => the operation can go ahead
* Return an array of links to login, register, email confirm and logout pages (if not using single sign-on integration)
* @param $rooturl
* @param $tourl
* @return array
*/
function qa_user_permit_error($permitoption=null, $limitaction=null, $userlevel=null, $checkblocks=true, $userfields=null)
function qa_get_login_links($rooturl, $tourl)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
require_once QA_INCLUDE_DIR.'app/limits.php';
return array(
'login' => qa_path('login', isset($tourl) ? array('to' => $tourl) : null, $rooturl),
'register' => qa_path('register', isset($tourl) ? array('to' => $tourl) : null, $rooturl),
'confirm' => qa_path('confirm', null, $rooturl),
'logout' => qa_path('logout', null, $rooturl),
);
}
if (!isset($userfields))
$userfields = qa_get_logged_in_user_cache();
} // end of: if (QA_FINAL_EXTERNAL_USERS) { ... } else { ... }
/**
* Return whether someone is logged in at the moment
*/
function qa_is_logged_in()
{
$userid = qa_get_logged_in_userid();
return isset($userid);
}
/**
* Return displayable handle/username of currently logged in user, or null if none
*/
function qa_get_logged_in_handle()
{
return qa_get_logged_in_user_field(QA_FINAL_EXTERNAL_USERS ? 'publicusername' : 'handle');
}
/**
* Return email of currently logged in user, or null if none
*/
function qa_get_logged_in_email()
{
return qa_get_logged_in_user_field('email');
}
/**
* Return level of currently logged in user, or null if none
*/
function qa_get_logged_in_level()
{
return qa_get_logged_in_user_field('level');
}
/**
* Return flags (see QA_USER_FLAGS_*) of currently logged in user, or null if none
*/
function qa_get_logged_in_flags()
{
if (QA_FINAL_EXTERNAL_USERS)
return qa_get_logged_in_user_field('blocked') ? QA_USER_FLAGS_USER_BLOCKED : 0;
else
return qa_get_logged_in_user_field('flags');
}
/**
* Return an array of all the specific (e.g. per category) level privileges for the logged in user, retrieving from the database if necessary
*/
function qa_get_logged_in_levels()
{
require_once QA_INCLUDE_DIR . 'db/selects.php';
return qa_db_get_pending_result('userlevels', qa_db_user_levels_selectspec(qa_get_logged_in_userid(), true));
}
/**
* Return an array mapping each userid in $userids to that user's handle (public username), or to null if not found
* @param $userids
* @return array
*/
function qa_userids_to_handles($userids)
{
if (QA_FINAL_EXTERNAL_USERS)
$rawuseridhandles = qa_get_public_from_userids($userids);
else {
require_once QA_INCLUDE_DIR . 'db/users.php';
$rawuseridhandles = qa_db_user_get_userid_handles($userids);
}
$userid = isset($userfields['userid']) ? $userfields['userid'] : null;
$gotuseridhandles = array();
foreach ($userids as $userid)
$gotuseridhandles[$userid] = @$rawuseridhandles[$userid];
return $gotuseridhandles;
}
/**
* Return an string mapping the received userid to that user's handle (public username), or to null if not found
* @param $userid
* @return mixed|null
*/
function qa_userid_to_handle($userid)
{
$handles = qa_userids_to_handles(array($userid));
return empty($handles) ? null : $handles[$userid];
}
/**
* Return an array mapping each handle in $handles the user's userid, or null if not found. If $exactonly is true then
* $handles must have the correct case and accents. Otherwise, handles are case- and accent-insensitive, and the keys
* of the returned array will match the $handles provided, not necessary those in the DB.
* @param $handles
* @param bool $exactonly
* @return array
*/
function qa_handles_to_userids($handles, $exactonly = false)
{
require_once QA_INCLUDE_DIR . 'util/string.php';
if (QA_FINAL_EXTERNAL_USERS)
$rawhandleuserids = qa_get_userids_from_public($handles);
else {
require_once QA_INCLUDE_DIR . 'db/users.php';
$rawhandleuserids = qa_db_user_get_handle_userids($handles);
}
if (!isset($userlevel))
$userlevel = isset($userfields['level']) ? $userfields['level'] : null;
$gothandleuserids = array();
$flags = isset($userfields['flags']) ? $userfields['flags'] : null;
if (!$checkblocks)
$flags &= ~QA_USER_FLAGS_USER_BLOCKED;
if ($exactonly) { // only take the exact matches
foreach ($handles as $handle)
$gothandleuserids[$handle] = @$rawhandleuserids[$handle];
$error = qa_permit_error($permitoption, $userid, $userlevel, $flags);
} else { // normalize to lowercase without accents, and then find matches
$normhandleuserids = array();
foreach ($rawhandleuserids as $handle => $userid)
$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))] = $userid;
if ($checkblocks && !$error && qa_is_ip_blocked())
$error = 'ipblock';
foreach ($handles as $handle)
$gothandleuserids[$handle] = @$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))];
}
if (!$error && isset($userid) && ($flags & QA_USER_FLAGS_MUST_CONFIRM) && qa_opt('confirm_user_emails'))
$error = 'confirm';
return $gothandleuserids;
}
if (!$error && isset($userid) && ($flags & QA_USER_FLAGS_MUST_APPROVE) && qa_opt('moderate_users'))
$error = 'approve';
if (isset($limitaction) && !$error) {
if (qa_user_limits_remaining($limitaction) <= 0)
$error = 'limit';
}
/**
* Return the userid corresponding to $handle (not case- or accent-sensitive)
* @param $handle
* @return mixed|null
*/
function qa_handle_to_userid($handle)
{
if (QA_FINAL_EXTERNAL_USERS)
$handleuserids = qa_get_userids_from_public(array($handle));
return $error;
else {
require_once QA_INCLUDE_DIR . 'db/users.php';
$handleuserids = qa_db_user_get_handle_userids(array($handle));
}
if (count($handleuserids) == 1)
return reset($handleuserids); // don't use $handleuserids[$handle] since capitalization might be different
/**
* Check whether user can perform $permitoption. Result as for qa_user_permit_error(...).
*
* @param string $permitoption Permission option name (from database) for action.
* @param int $userid ID of user (null for no user).
* @param int $userlevel Level to check against.
* @param int $userflags Flags for this user.
* @param int $userpoints User's points: if $userid is currently logged in, you can set $userpoints=null to retrieve them only if necessary.
*
* @return string|bool Reason the user is not permitted, or false if the operation can go ahead.
*/
function qa_permit_error($permitoption, $userid, $userlevel, $userflags, $userpoints=null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$permit = isset($permitoption) ? qa_opt($permitoption) : QA_PERMIT_ALL;
return null;
}
if (isset($userid) && ($permit == QA_PERMIT_POINTS || $permit == QA_PERMIT_POINTS_CONFIRMED || $permit == QA_PERMIT_APPROVED_POINTS)) {
// deal with points threshold by converting as appropriate
if (!isset($userpoints) && $userid == qa_get_logged_in_userid())
$userpoints = qa_get_logged_in_points(); // allow late retrieval of points (to avoid unnecessary DB query when using external users)
/**
* Return the level of the logged in user for a post with $categoryids (expressing the full hierarchy to the final category)
* @param $categoryids
* @return mixed|null
*/
function qa_user_level_for_categories($categoryids)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if ($userpoints >= qa_opt($permitoption . '_points')) {
$permit = $permit == QA_PERMIT_APPROVED_POINTS
? QA_PERMIT_APPROVED
: ($permit == QA_PERMIT_POINTS_CONFIRMED ? QA_PERMIT_CONFIRMED : QA_PERMIT_USERS); // convert if user has enough points
} else
$permit = QA_PERMIT_EXPERTS; // otherwise show a generic message so they're not tempted to collect points just for this
}
return qa_permit_value_error($permit, $userid, $userlevel, $userflags);
}
require_once QA_INCLUDE_DIR . 'app/updates.php';
$level = qa_get_logged_in_level();
/**
* Check whether user can reach the permission level. Result as for qa_user_permit_error(...).
*
* @param int $permit Permission constant.
* @param int $userid ID of user (null for no user).
* @param int $userlevel Level to check against.
* @param int $userflags Flags for this user.
*
* @return string|bool Reason the user is not permitted, or false if the operation can go ahead
*/
function qa_permit_value_error($permit, $userid, $userlevel, $userflags)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if (count($categoryids)) {
$userlevels = qa_get_logged_in_levels();
if (!isset($userid) && $permit < QA_PERMIT_ALL)
return 'login';
$levelError =
($permit <= QA_PERMIT_SUPERS && $userlevel < QA_USER_LEVEL_SUPER) ||
($permit <= QA_PERMIT_ADMINS && $userlevel < QA_USER_LEVEL_ADMIN) ||
($permit <= QA_PERMIT_MODERATORS && $userlevel < QA_USER_LEVEL_MODERATOR) ||
($permit <= QA_PERMIT_EDITORS && $userlevel < QA_USER_LEVEL_EDITOR) ||
($permit <= QA_PERMIT_EXPERTS && $userlevel < QA_USER_LEVEL_EXPERT);
if ($levelError)
return 'level';
if (isset($userid) && ($userflags & QA_USER_FLAGS_USER_BLOCKED))
return 'userblock';
if ($permit >= QA_PERMIT_USERS)
return false;
if ($permit >= QA_PERMIT_CONFIRMED) {
$confirmed = ($userflags & QA_USER_FLAGS_EMAIL_CONFIRMED);
if (
!QA_FINAL_EXTERNAL_USERS && // not currently supported by single sign-on integration
qa_opt('confirm_user_emails') && // if this option off, we can't ask it of the user
$userlevel < QA_USER_LEVEL_APPROVED && // if user approved or assigned to a higher level, no need
!$confirmed // actual confirmation
)
return 'confirm';
}
elseif ($permit >= QA_PERMIT_APPROVED) {
if (
qa_opt('moderate_users') && // if this option off, we can't ask it of the user
$userlevel < QA_USER_LEVEL_APPROVED // user has not been approved
)
return 'approve';
$categorylevels = array(); // create a map
foreach ($userlevels as $userlevel) {
if ($userlevel['entitytype'] == QA_ENTITY_CATEGORY)
$categorylevels[$userlevel['entityid']] = $userlevel['level'];
}
return false;
foreach ($categoryids as $categoryid) {
$level = max($level, @$categorylevels[$categoryid]);
}
}
return $level;
}
function qa_user_captcha_reason($userlevel=null)
/*
Return whether a captcha is required for posts submitted by the current user. You can pass in a QA_USER_LEVEL_*
constant in $userlevel to consider the user at a different level to usual (e.g. if they are performing this action
in a category for which they have elevated privileges).
Possible results:
'login' => captcha required because the user is not logged in
'approve' => captcha required because the user has not been approved
'confirm' => captcha required because the user has not confirmed their email address
false => captcha is not required
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$reason=false;
if (!isset($userlevel))
$userlevel=qa_get_logged_in_level();
/**
* Return the level of the logged in user for $post, as retrieved from the database
* @param $post
* @return mixed|null
*/
function qa_user_level_for_post($post)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if ($userlevel < QA_USER_LEVEL_APPROVED) { // approved users and above aren't shown captchas
$userid=qa_get_logged_in_userid();
if (strlen(@$post['categoryids']))
return qa_user_level_for_categories(explode(',', $post['categoryids']));
if (qa_opt('captcha_on_anon_post') && !isset($userid))
$reason='login';
elseif (qa_opt('moderate_users') && qa_opt('captcha_on_unapproved'))
$reason='approve';
elseif (qa_opt('confirm_user_emails') && qa_opt('captcha_on_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED) )
$reason='confirm';
}
return null;
}
return $reason;
}
/**
* Return the maximum possible level of the logged in user in any context (i.e. for any category)
*/
function qa_user_level_maximum()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
function qa_user_use_captcha($userlevel=null)
/*
Return whether a captcha should be presented to the logged in user for writing posts. You can pass in a
QA_USER_LEVEL_* constant in $userlevel to consider the user at a different level to usual.
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$level = qa_get_logged_in_level();
return qa_user_captcha_reason($userlevel)!=false;
$userlevels = qa_get_logged_in_levels();
foreach ($userlevels as $userlevel) {
$level = max($level, $userlevel['level']);
}
return $level;
}
/**
* Check whether the logged in user has permission to perform $permitoption on post $post (from the database)
* Other parameters and the return value are as for qa_user_permit_error(...)
* @param $permitoption
* @param $post
* @param $limitaction
* @param bool $checkblocks
* @return bool|string
*/
function qa_user_post_permit_error($permitoption, $post, $limitaction = null, $checkblocks = true)
{
return qa_user_permit_error($permitoption, $limitaction, qa_user_level_for_post($post), $checkblocks);
}
/**
* Check whether the logged in user would have permittion to perform $permitoption in any context (i.e. for any category)
* Other parameters and the return value are as for qa_user_permit_error(...)
* @param $permitoption
* @param $limitaction
* @param bool $checkblocks
* @return bool|string
*/
function qa_user_maximum_permit_error($permitoption, $limitaction = null, $checkblocks = true)
{
return qa_user_permit_error($permitoption, $limitaction, qa_user_level_maximum(), $checkblocks);
}
/**
* Check whether the logged in user has permission to perform an action.
*
* @param string $permitoption The permission to check (if null, this simply checks whether the user is blocked).
* @param string $limitaction Constant from qa-app-limits.php to check against user or IP rate limits.
* @param int $userlevel A QA_USER_LEVEL_* constant to consider the user at a different level to usual (e.g. if
* they are performing this action in a category for which they have elevated privileges).
* @param bool $checkblocks Whether to check the user's blocked status.
* @param array $userfields Cache for logged in user, containing keys 'userid', 'level' (optional), 'flags'.
*
* @return bool|string The permission error, or false if no error. Possible errors, in order of priority:
* 'login' => the user should login or register
* 'level' => a special privilege level (e.g. expert) or minimum number of points is required
* 'userblock' => the user has been blocked
* 'ipblock' => the ip address has been blocked
* 'confirm' => the user should confirm their email address
* 'approve' => the user needs to be approved by the site admins
* 'limit' => the user or IP address has reached a rate limit (if $limitaction specified)
* false => the operation can go ahead
*/
function qa_user_permit_error($permitoption = null, $limitaction = null, $userlevel = null, $checkblocks = true, $userfields = null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
require_once QA_INCLUDE_DIR . 'app/limits.php';
if (!isset($userfields))
$userfields = qa_get_logged_in_user_cache();
$userid = isset($userfields['userid']) ? $userfields['userid'] : null;
if (!isset($userlevel))
$userlevel = isset($userfields['level']) ? $userfields['level'] : null;
$flags = isset($userfields['flags']) ? $userfields['flags'] : null;
if (!$checkblocks)
$flags &= ~QA_USER_FLAGS_USER_BLOCKED;
$error = qa_permit_error($permitoption, $userid, $userlevel, $flags);
if ($checkblocks && !$error && qa_is_ip_blocked())
$error = 'ipblock';
if (!$error && isset($userid) && ($flags & QA_USER_FLAGS_MUST_CONFIRM) && qa_opt('confirm_user_emails'))
$error = 'confirm';
if (!$error && isset($userid) && ($flags & QA_USER_FLAGS_MUST_APPROVE) && qa_opt('moderate_users'))
$error = 'approve';
if (isset($limitaction) && !$error) {
if (qa_user_limits_remaining($limitaction) <= 0)
$error = 'limit';
}
function qa_user_moderation_reason($userlevel=null)
/*
Return whether moderation is required for posts submitted by the current user. You can pass in a QA_USER_LEVEL_*
constant in $userlevel to consider the user at a different level to usual (e.g. if they are performing this action
in a category for which they have elevated privileges).
Possible results:
'login' => moderation required because the user is not logged in
'approve' => moderation required because the user has not been approved
'confirm' => moderation required because the user has not confirmed their email address
'points' => moderation required because the user has insufficient points
false => moderation is not required
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
return $error;
}
/**
* Check whether user can perform $permitoption. Result as for qa_user_permit_error(...).
*
* @param string $permitoption Permission option name (from database) for action.
* @param int $userid ID of user (null for no user).
* @param int $userlevel Level to check against.
* @param int $userflags Flags for this user.
* @param int $userpoints User's points: if $userid is currently logged in, you can set $userpoints=null to retrieve them only if necessary.
*
* @return string|bool Reason the user is not permitted, or false if the operation can go ahead.
*/
function qa_permit_error($permitoption, $userid, $userlevel, $userflags, $userpoints = null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$permit = isset($permitoption) ? qa_opt($permitoption) : QA_PERMIT_ALL;
if (isset($userid) && ($permit == QA_PERMIT_POINTS || $permit == QA_PERMIT_POINTS_CONFIRMED || $permit == QA_PERMIT_APPROVED_POINTS)) {
// deal with points threshold by converting as appropriate
if (!isset($userpoints) && $userid == qa_get_logged_in_userid())
$userpoints = qa_get_logged_in_points(); // allow late retrieval of points (to avoid unnecessary DB query when using external users)
if ($userpoints >= qa_opt($permitoption . '_points')) {
$permit = $permit == QA_PERMIT_APPROVED_POINTS
? QA_PERMIT_APPROVED
: ($permit == QA_PERMIT_POINTS_CONFIRMED ? QA_PERMIT_CONFIRMED : QA_PERMIT_USERS); // convert if user has enough points
} else
$permit = QA_PERMIT_EXPERTS; // otherwise show a generic message so they're not tempted to collect points just for this
}
$reason=false;
if (!isset($userlevel))
$userlevel=qa_get_logged_in_level();
return qa_permit_value_error($permit, $userid, $userlevel, $userflags);
}
/**
* Check whether user can reach the permission level. Result as for qa_user_permit_error(...).
*
* @param int $permit Permission constant.
* @param int $userid ID of user (null for no user).
* @param int $userlevel Level to check against.
* @param int $userflags Flags for this user.
*
* @return string|bool Reason the user is not permitted, or false if the operation can go ahead
*/
function qa_permit_value_error($permit, $userid, $userlevel, $userflags)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if (!isset($userid) && $permit < QA_PERMIT_ALL)
return 'login';
$levelError =
($permit <= QA_PERMIT_SUPERS && $userlevel < QA_USER_LEVEL_SUPER) ||
($permit <= QA_PERMIT_ADMINS && $userlevel < QA_USER_LEVEL_ADMIN) ||
($permit <= QA_PERMIT_MODERATORS && $userlevel < QA_USER_LEVEL_MODERATOR) ||
($permit <= QA_PERMIT_EDITORS && $userlevel < QA_USER_LEVEL_EDITOR) ||
($permit <= QA_PERMIT_EXPERTS && $userlevel < QA_USER_LEVEL_EXPERT);
if ($levelError)
return 'level';
if (isset($userid) && ($userflags & QA_USER_FLAGS_USER_BLOCKED))
return 'userblock';
if ($permit >= QA_PERMIT_USERS)
return false;
if ($permit >= QA_PERMIT_CONFIRMED) {
$confirmed = ($userflags & QA_USER_FLAGS_EMAIL_CONFIRMED);
if (
($userlevel < QA_USER_LEVEL_EXPERT) && // experts and above aren't moderated
qa_user_permit_error('permit_moderate') // if the user can approve posts, no point in moderating theirs
) {
$userid=qa_get_logged_in_userid();
if (isset($userid)) {
if (qa_opt('moderate_users') && qa_opt('moderate_unapproved') && ($userlevel<QA_USER_LEVEL_APPROVED))
$reason='approve';
elseif (qa_opt('confirm_user_emails') && qa_opt('moderate_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED) )
$reason='confirm';
elseif (qa_opt('moderate_by_points') && (qa_get_logged_in_points() < qa_opt('moderate_points_limit')))
$reason='points';
} elseif (qa_opt('moderate_anon_post'))
$reason='login';
}
return $reason;
!QA_FINAL_EXTERNAL_USERS && // not currently supported by single sign-on integration
qa_opt('confirm_user_emails') && // if this option off, we can't ask it of the user
$userlevel < QA_USER_LEVEL_APPROVED && // if user approved or assigned to a higher level, no need
!$confirmed // actual confirmation
)
return 'confirm';
} elseif ($permit >= QA_PERMIT_APPROVED) {
if (
qa_opt('moderate_users') && // if this option off, we can't ask it of the user
$userlevel < QA_USER_LEVEL_APPROVED // user has not been approved
)
return 'approve';
}
return false;
}
/**
* Return whether a captcha is required for posts submitted by the current user. You can pass in a QA_USER_LEVEL_*
* constant in $userlevel to consider the user at a different level to usual (e.g. if they are performing this action
* in a category for which they have elevated privileges).
*
* Possible results:
* 'login' => captcha required because the user is not logged in
* 'approve' => captcha required because the user has not been approved
* 'confirm' => captcha required because the user has not confirmed their email address
* false => captcha is not required
* @param $userlevel
* @return bool|mixed|string
*/
function qa_user_captcha_reason($userlevel = null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$reason = false;
if (!isset($userlevel))
$userlevel = qa_get_logged_in_level();
if ($userlevel < QA_USER_LEVEL_APPROVED) { // approved users and above aren't shown captchas
$userid = qa_get_logged_in_userid();
if (qa_opt('captcha_on_anon_post') && !isset($userid))
$reason = 'login';
elseif (qa_opt('moderate_users') && qa_opt('captcha_on_unapproved'))
$reason = 'approve';
elseif (qa_opt('confirm_user_emails') && qa_opt('captcha_on_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED))
$reason = 'confirm';
}
function qa_user_userfield_label($userfield)
/*
Return the label to display for $userfield as retrieved from the database, using default if no name set
*/
{
if (isset($userfield['content']))
return $userfield['content'];
else {
$defaultlabels=array(
'name' => 'users/full_name',
'about' => 'users/about',
'location' => 'users/location',
'website' => 'users/website',
);
if (isset($defaultlabels[$userfield['title']]))
return qa_lang($defaultlabels[$userfield['title']]);
}
return $reason;
}
/**
* Return whether a captcha should be presented to the logged in user for writing posts. You can pass in a
* QA_USER_LEVEL_* constant in $userlevel to consider the user at a different level to usual.
* @param $userlevel
* @return bool|mixed
*/
function qa_user_use_captcha($userlevel = null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
return qa_user_captcha_reason($userlevel) != false;
}
/**
* Return whether moderation is required for posts submitted by the current user. You can pass in a QA_USER_LEVEL_*
* constant in $userlevel to consider the user at a different level to usual (e.g. if they are performing this action
* in a category for which they have elevated privileges).
*
* Possible results:
* 'login' => moderation required because the user is not logged in
* 'approve' => moderation required because the user has not been approved
* 'confirm' => moderation required because the user has not confirmed their email address
* 'points' => moderation required because the user has insufficient points
* false => moderation is not required
* @param $userlevel
* @return bool|string
*/
function qa_user_moderation_reason($userlevel = null)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$reason = false;
if (!isset($userlevel))
$userlevel = qa_get_logged_in_level();
if (
($userlevel < QA_USER_LEVEL_EXPERT) && // experts and above aren't moderated
qa_user_permit_error('permit_moderate') // if the user can approve posts, no point in moderating theirs
) {
$userid = qa_get_logged_in_userid();
if (isset($userid)) {
if (qa_opt('moderate_users') && qa_opt('moderate_unapproved') && ($userlevel < QA_USER_LEVEL_APPROVED))
$reason = 'approve';
elseif (qa_opt('confirm_user_emails') && qa_opt('moderate_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED))
$reason = 'confirm';
elseif (qa_opt('moderate_by_points') && (qa_get_logged_in_points() < qa_opt('moderate_points_limit')))
$reason = 'points';
} elseif (qa_opt('moderate_anon_post'))
$reason = 'login';
}
return '';
return $reason;
}
/**
* Return the label to display for $userfield as retrieved from the database, using default if no name set
* @param $userfield
* @return string
*/
function qa_user_userfield_label($userfield)
{
if (isset($userfield['content']))
return $userfield['content'];
else {
$defaultlabels = array(
'name' => 'users/full_name',
'about' => 'users/about',
'location' => 'users/location',
'website' => 'users/website',
);
if (isset($defaultlabels[$userfield['title']]))
return qa_lang($defaultlabels[$userfield['title']]);
}
return '';
}
function qa_set_form_security_key()
/*
Set or extend the cookie in browser of non logged-in users which identifies them for the purposes of form security (anti-CSRF protection)
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
global $qa_form_key_cookie_set;
/**
* Set or extend the cookie in browser of non logged-in users which identifies them for the purposes of form security (anti-CSRF protection)
*/
function qa_set_form_security_key()
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
if ( (!qa_is_logged_in()) && !@$qa_form_key_cookie_set) {
$qa_form_key_cookie_set=true;
global $qa_form_key_cookie_set;
if (strlen(@$_COOKIE['qa_key'])!=QA_FORM_KEY_LENGTH) {
require_once QA_INCLUDE_DIR.'util/string.php';
$_COOKIE['qa_key']=qa_random_alphanum(QA_FORM_KEY_LENGTH);
}
if ((!qa_is_logged_in()) && !@$qa_form_key_cookie_set) {
$qa_form_key_cookie_set = true;
setcookie('qa_key', $_COOKIE['qa_key'], time()+2*QA_FORM_EXPIRY_SECS, '/', QA_COOKIE_DOMAIN, (bool)ini_get('session.cookie_secure'), true); // extend on every page request
if (strlen(@$_COOKIE['qa_key']) != QA_FORM_KEY_LENGTH) {
require_once QA_INCLUDE_DIR . 'util/string.php';
$_COOKIE['qa_key'] = qa_random_alphanum(QA_FORM_KEY_LENGTH);
}
}
function qa_calc_form_security_hash($action, $timestamp)
/*
Return the form security (anti-CSRF protection) hash for an $action (any string), that can be performed within
QA_FORM_EXPIRY_SECS of $timestamp (in unix seconds) by the current user.
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$salt=qa_opt('form_security_salt');
if (qa_is_logged_in())
return sha1($salt.'/'.$action.'/'.$timestamp.'/'.qa_get_logged_in_userid().'/'.qa_get_logged_in_user_field('passsalt'));
else
return sha1($salt.'/'.$action.'/'.$timestamp.'/'.@$_COOKIE['qa_key']); // lower security for non logged in users - code+cookie can be transferred
setcookie('qa_key', $_COOKIE['qa_key'], time() + 2 * QA_FORM_EXPIRY_SECS, '/', QA_COOKIE_DOMAIN, (bool)ini_get('session.cookie_secure'), true); // extend on every page request
}
}
function qa_get_form_security_code($action)
/*
Return the full form security (anti-CSRF protection) code for an $action (any string) performed within
QA_FORM_EXPIRY_SECS of now by the current user.
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
/**
* Return the form security (anti-CSRF protection) hash for an $action (any string), that can be performed within
* QA_FORM_EXPIRY_SECS of $timestamp (in unix seconds) by the current user.
* @param $action
* @param $timestamp
* @return mixed|string
*/
function qa_calc_form_security_hash($action, $timestamp)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
qa_set_form_security_key();
$salt = qa_opt('form_security_salt');
$timestamp=qa_opt('db_time');
if (qa_is_logged_in())
return sha1($salt . '/' . $action . '/' . $timestamp . '/' . qa_get_logged_in_userid() . '/' . qa_get_logged_in_user_field('passsalt'));
else
return sha1($salt . '/' . $action . '/' . $timestamp . '/' . @$_COOKIE['qa_key']); // lower security for non logged in users - code+cookie can be transferred
}
return (int)qa_is_logged_in().'-'.$timestamp.'-'.qa_calc_form_security_hash($action, $timestamp);
}
/**
* Return the full form security (anti-CSRF protection) code for an $action (any string) performed within
* QA_FORM_EXPIRY_SECS of now by the current user.
* @param $action
* @return mixed|string
*/
function qa_get_form_security_code($action)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
function qa_check_form_security_code($action, $value)
/*
Return whether $value matches the expected form security (anti-CSRF protection) code for $action (any string) and
that the code has not expired (if more than QA_FORM_EXPIRY_SECS have passed). Logs causes for suspicion.
*/
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
qa_set_form_security_key();
$timestamp = qa_opt('db_time');
$reportproblems=array();
$silentproblems=array();
return (int)qa_is_logged_in() . '-' . $timestamp . '-' . qa_calc_form_security_hash($action, $timestamp);
}
if (!isset($value))
$silentproblems[]='code missing';
elseif (!strlen($value))
$silentproblems[]='code empty';
/**
* Return whether $value matches the expected form security (anti-CSRF protection) code for $action (any string) and
* that the code has not expired (if more than QA_FORM_EXPIRY_SECS have passed). Logs causes for suspicion.
* @param $action
* @param $value
* @return bool
*/
function qa_check_form_security_code($action, $value)
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
else {
$parts=explode('-', $value);
$reportproblems = array();
$silentproblems = array();
if (count($parts)==3) {
$loggedin=$parts[0];
$timestamp=$parts[1];
$hash=$parts[2];
$timenow=qa_opt('db_time');
if (!isset($value)) {
$silentproblems[] = 'code missing';
if ($timestamp>$timenow)
$reportproblems[]='time '.($timestamp-$timenow).'s in future';
elseif ($timestamp<($timenow-QA_FORM_EXPIRY_SECS))
$silentproblems[]='timeout after '.($timenow-$timestamp).'s';
} elseif (!strlen($value)) {
$silentproblems[] = 'code empty';
if (qa_is_logged_in()) {
if (!$loggedin)
$silentproblems[]='now logged in';
} else {
$parts = explode('-', $value);
if (count($parts) == 3) {
$loggedin = $parts[0];
$timestamp = $parts[1];
$hash = $parts[2];
$timenow = qa_opt('db_time');
if ($timestamp > $timenow) {
$reportproblems[] = 'time ' . ($timestamp - $timenow) . 's in future';
} elseif ($timestamp < ($timenow - QA_FORM_EXPIRY_SECS)) {
$silentproblems[] = 'timeout after ' . ($timenow - $timestamp) . 's';
}
if (qa_is_logged_in()) {
if (!$loggedin) {
$silentproblems[] = 'now logged in';
}
} else {
if ($loggedin) {
$silentproblems[] = 'now logged out';
} else {
if ($loggedin)
$silentproblems[]='now logged out';
else {
$key=@$_COOKIE['qa_key'];
if (!isset($key))
$silentproblems[]='key cookie missing';
elseif (!strlen($key))
$silentproblems[]='key cookie empty';
elseif (strlen($key)!=QA_FORM_KEY_LENGTH)
$reportproblems[]='key cookie '.$key.' invalid';
$key = @$_COOKIE['qa_key'];
if (!isset($key)) {
$silentproblems[] = 'key cookie missing';
} elseif (!strlen($key)) {
$silentproblems[] = 'key cookie empty';
} elseif (strlen($key) != QA_FORM_KEY_LENGTH) {
$reportproblems[] = 'key cookie ' . $key . ' invalid';
}
}
}
if (empty($silentproblems) && empty($reportproblems))
if (!hash_equals(strtolower(qa_calc_form_security_hash($action, $timestamp)), strtolower($hash)))
$reportproblems[]='code mismatch';
if (empty($silentproblems) && empty($reportproblems)) {
if (!hash_equals(strtolower(qa_calc_form_security_hash($action, $timestamp)), strtolower($hash))) {
$reportproblems[] = 'code mismatch';
}
}
} else
$reportproblems[]='code '.$value.' malformed';
} else {
$reportproblems[] = 'code ' . $value . ' malformed';
}
if (!empty($reportproblems) && QA_DEBUG_PERFORMANCE)
@error_log(
'PHP Question2Answer form security violation for '.$action.
' by '.(qa_is_logged_in() ? ('userid '.qa_get_logged_in_userid()) : 'anonymous').
' ('.implode(', ', array_merge($reportproblems, $silentproblems)).')'.
' on '.@$_SERVER['REQUEST_URI'].
' via '.@$_SERVER['HTTP_REFERER']
);
return (empty($silentproblems) && empty($reportproblems));
}
if (!empty($reportproblems) && QA_DEBUG_PERFORMANCE) {
@error_log(
'PHP Question2Answer form security violation for ' . $action .
' by ' . (qa_is_logged_in() ? ('userid ' . qa_get_logged_in_userid()) : 'anonymous') .
' (' . implode(', ', array_merge($reportproblems, $silentproblems)) . ')' .
' on ' . @$_SERVER['REQUEST_URI'] .
' via ' . @$_SERVER['HTTP_REFERER']
);
}
/*
Omit PHP closing tag to help avoid accidental output
*/
return (empty($silentproblems) && empty($reportproblems));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment