Commit f24542bb by Scott

Coding style (app posts/admin)

parent 5a94ef80
...@@ -20,623 +20,618 @@ ...@@ -20,623 +20,618 @@
More about this license: http://www.question2answer.org/license.php 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 if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../'); header('Location: ../');
exit; exit;
} }
function qa_admin_check_privileges(&$qa_content) /**
/* * Return true if user is logged in with admin privileges. If not, return false
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
and set up $qa_content with the appropriate title and error message */
*/ function qa_admin_check_privileges(&$qa_content)
{ {
if (!qa_is_logged_in()) { if (!qa_is_logged_in()) {
require_once QA_INCLUDE_DIR.'app/format.php'; 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['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['error']=qa_insert_login_links(qa_lang_html('admin/not_logged_in'), qa_request());
return false; return false;
} elseif (qa_get_logged_in_level()<QA_USER_LEVEL_ADMIN) {
$qa_content=qa_content_prepare();
$qa_content['title']=qa_lang_html('admin/admin_title'); } elseif (qa_get_logged_in_level()<QA_USER_LEVEL_ADMIN) {
$qa_content['error']=qa_lang_html('admin/no_privileges'); $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() $codetolanguage = array( // 2-letter language codes as per ISO 639-1
{ 'ar' => 'Arabic - العربية',
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } 'az' => 'Azerbaijani - Azərbaycanca',
'bg' => 'Bulgarian - Български',
/** 'bn' => 'Bengali - বাংলা',
* @deprecated The hardcoded language ids will be removed in favor of language metadata files. 'ca' => 'Catalan - Català',
* See qa-lang/en-GB directory for a clear example of how to use them. 'cs' => 'Czech - Čeština',
*/ 'cy' => 'Welsh - Cymraeg',
$codetolanguage = array( // 2-letter language codes as per ISO 639-1 'da' => 'Danish - Dansk',
'ar' => 'Arabic - العربية', 'de' => 'German - Deutsch',
'az' => 'Azerbaijani - Azərbaycanca', 'el' => 'Greek - Ελληνικά',
'bg' => 'Bulgarian - Български', 'en-GB' => 'English (UK)',
'bn' => 'Bengali - বাংলা', 'es' => 'Spanish - Español',
'ca' => 'Catalan - Català', 'et' => 'Estonian - Eesti',
'cs' => 'Czech - Čeština', 'fa' => 'Persian - فارسی',
'cy' => 'Welsh - Cymraeg', 'fi' => 'Finnish - Suomi',
'da' => 'Danish - Dansk', 'fr' => 'French - Français',
'de' => 'German - Deutsch', 'he' => 'Hebrew - עברית',
'el' => 'Greek - Ελληνικά', 'hr' => 'Croatian - Hrvatski',
'en-GB' => 'English (UK)', 'hu' => 'Hungarian - Magyar',
'es' => 'Spanish - Español', 'id' => 'Indonesian - Bahasa Indonesia',
'et' => 'Estonian - Eesti', 'is' => 'Icelandic - Íslenska',
'fa' => 'Persian - فارسی', 'it' => 'Italian - Italiano',
'fi' => 'Finnish - Suomi', 'ja' => 'Japanese - 日本語',
'fr' => 'French - Français', 'ka' => 'Georgian - ქართული ენა',
'he' => 'Hebrew - עברית', 'kh' => 'Khmer - ភាសាខ្មែរ',
'hr' => 'Croatian - Hrvatski', 'ko' => 'Korean - 한국어',
'hu' => 'Hungarian - Magyar', 'ku-CKB' => 'Kurdish Central - کورد',
'id' => 'Indonesian - Bahasa Indonesia', 'lt' => 'Lithuanian - Lietuvių',
'is' => 'Icelandic - Íslenska', 'lv' => 'Latvian - Latviešu',
'it' => 'Italian - Italiano', 'nl' => 'Dutch - Nederlands',
'ja' => 'Japanese - 日本語', 'no' => 'Norwegian - Norsk',
'ka' => 'Georgian - ქართული ენა', 'pl' => 'Polish - Polski',
'kh' => 'Khmer - ភាសាខ្មែរ', 'pt' => 'Portuguese - Português',
'ko' => 'Korean - 한국어', 'ro' => 'Romanian - Română',
'ku-CKB' => 'Kurdish Central - کورد', 'ru' => 'Russian - Русский',
'lt' => 'Lithuanian - Lietuvių', 'sk' => 'Slovak - Slovenčina',
'lv' => 'Latvian - Latviešu', 'sl' => 'Slovenian - Slovenščina',
'nl' => 'Dutch - Nederlands', 'sq' => 'Albanian - Shqip',
'no' => 'Norwegian - Norsk', 'sr' => 'Serbian - Српски',
'pl' => 'Polish - Polski', 'sv' => 'Swedish - Svenska',
'pt' => 'Portuguese - Português', 'th' => 'Thai - ไทย',
'ro' => 'Romanian - Română', 'tr' => 'Turkish - Türkçe',
'ru' => 'Russian - Русский', 'ug' => 'Uyghur - ئۇيغۇرچە',
'sk' => 'Slovak - Slovenčina', 'uk' => 'Ukrainian - Українська',
'sl' => 'Slovenian - Slovenščina', 'uz' => 'Uzbek - ўзбек',
'sq' => 'Albanian - Shqip', 'vi' => 'Vietnamese - Tiếng Việt',
'sr' => 'Serbian - Српски', 'zh-TW' => 'Chinese Traditional - 繁體中文',
'sv' => 'Swedish - Svenska', 'zh' => 'Chinese Simplified - 简体中文',
'th' => 'Thai - ไทย', );
'tr' => 'Turkish - Türkçe',
'ug' => 'Uyghur - ئۇيغۇرچە', $options = array('' => 'English (US)');
'uk' => 'Ukrainian - Українська',
'uz' => 'Uzbek - ўзбек', // find all language folders
'vi' => 'Vietnamese - Tiếng Việt', $metadataUtil = new Q2A_Util_Metadata();
'zh-TW' => 'Chinese Traditional - 繁體中文', foreach (glob(QA_LANG_DIR . '*', GLOB_ONLYDIR) as $directory) {
'zh' => 'Chinese Simplified - 简体中文', $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)'); asort($options, SORT_STRING);
return $options;
// 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); * Return a sorted array of available themes, [theme name] => [theme name]
if (isset($metadata['name'])) */
$options[$code] = $metadata['name']; function qa_admin_theme_options()
// otherwise use an entry from above {
elseif (isset($codetolanguage[$code])) if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$options[$code] = $codetolanguage[$code];
$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;
} }
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 $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); } * Return an array of options representing matching precision, [value] => [label]
*/
$metadataUtil = new Q2A_Util_Metadata(); function qa_admin_match_options()
foreach (glob(QA_THEME_DIR . '*', GLOB_ONLYDIR) as $directory) { {
$theme = basename($directory); return array(
$metadata = $metadataUtil->fetchFromAddonPath($directory); 5 => qa_lang_html('options/match_5'),
if (empty($metadata)) { 4 => qa_lang_html('options/match_4'),
// limit theme parsing to first 8kB 3 => qa_lang_html('options/match_3'),
$contents = file_get_contents($directory . '/qa-styles.css', false, null, -1, 8192); 2 => qa_lang_html('options/match_2'),
$metadata = qa_addon_metadata($contents, 'Theme'); 1 => qa_lang_html('options/match_1'),
} );
$options[$theme] = isset($metadata['name']) ? $metadata['name'] : $theme; }
}
asort($options, SORT_STRING); /**
return $options; * 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() if (QA_FINAL_EXTERNAL_USERS || !qa_opt('moderate_users')) {
/* unset($options[QA_PERMIT_APPROVED]);
Return an array of widget placement options, with keys matching the database value unset($options[QA_PERMIT_APPROVED_POINTS]);
*/
{
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 $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) { * Return the sub navigation structure common to admin pages
if ($rawoption>$maximum) */
break; 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() $navigation['admin/users']=array(
/* 'label' => qa_lang_html('admin/users_title'),
Return an array of options representing matching precision, [value] => [label] 'url' => qa_path_html('admin/users'),
*/ 'selected_on' => array('admin/users$', 'admin/userfields$', 'admin/usertitles$'),
{
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/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) $navigation['admin/posting']=array(
/* 'label' => qa_lang_html('admin/posting_title'),
Return an array of options representing permission restrictions, [value] => [label] 'url' => qa_path_html('admin/posting'),
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'),
); );
foreach ($options as $key => $label) $navigation['admin/viewing']=array(
if (($key<$narrowest) || ($key>$widest)) 'label' => qa_lang_html('admin/viewing_title'),
unset($options[$key]); 'url' => qa_path_html('admin/viewing'),
);
if (!$doconfirms) { $navigation['admin/lists']=array(
unset($options[QA_PERMIT_CONFIRMED]); 'label' => qa_lang_html('admin/lists_title'),
unset($options[QA_PERMIT_POINTS_CONFIRMED]); 'url' => qa_path_html('admin/lists'),
} );
if (!$dopoints) { if (qa_using_categories())
unset($options[QA_PERMIT_POINTS]); $navigation['admin/categories']=array(
unset($options[QA_PERMIT_POINTS_CONFIRMED]); 'label' => qa_lang_html('admin/categories_title'),
unset($options[QA_PERMIT_APPROVED_POINTS]); 'url' => qa_path_html('admin/categories'),
} );
if (QA_FINAL_EXTERNAL_USERS || !qa_opt('moderate_users')) { $navigation['admin/permissions']=array(
unset($options[QA_PERMIT_APPROVED]); 'label' => qa_lang_html('admin/permissions_title'),
unset($options[QA_PERMIT_APPROVED_POINTS]); '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() $navigation['admin/points']=array(
/* 'label' => qa_lang_html('admin/points_title'),
Return the sub navigation structure common to admin pages 'url' => qa_path_html('admin/points'),
*/ );
{
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$navigation=array(); $navigation['admin/spam']=array(
$level=qa_get_logged_in_level(); 'label' => qa_lang_html('admin/spam_title'),
'url' => qa_path_html('admin/spam'),
);
if ($level>=QA_USER_LEVEL_ADMIN) { if (defined('QA_CACHE_DIRECTORY')) {
$navigation['admin/general']=array( $navigation['admin/caching']=array(
'label' => qa_lang_html('admin/general_title'), 'label' => qa_lang_html('admin/caching_title'),
'url' => qa_path_html('admin/general'), 'url' => qa_path_html('admin/caching'),
); );
}
$navigation['admin/emails']=array( $navigation['admin/stats']=array(
'label' => qa_lang_html('admin/emails_title'), 'label' => qa_lang_html('admin/stats_title'),
'url' => qa_path_html('admin/emails'), 'url' => qa_path_html('admin/stats'),
); );
$navigation['admin/users']=array( if (!QA_FINAL_EXTERNAL_USERS)
'label' => qa_lang_html('admin/users_title'), $navigation['admin/mailing']=array(
'url' => qa_path_html('admin/users'), 'label' => qa_lang_html('admin/mailing_title'),
'selected_on' => array('admin/users$', 'admin/userfields$', 'admin/usertitles$'), 'url' => qa_path_html('admin/mailing'),
); );
$navigation['admin/layout']=array( $navigation['admin/plugins']=array(
'label' => qa_lang_html('admin/layout_title'), 'label' => qa_lang_html('admin/plugins_title'),
'url' => qa_path_html('admin/layout'), 'url' => qa_path_html('admin/plugins'),
); );
}
$navigation['admin/posting']=array( if (!qa_user_maximum_permit_error('permit_moderate')) {
'label' => qa_lang_html('admin/posting_title'), $count=qa_user_permit_error('permit_moderate') ? null : qa_opt('cache_queuedcount'); // if only in some categories don't show cached count
'url' => qa_path_html('admin/posting'),
);
$navigation['admin/viewing']=array( $navigation['admin/moderate']=array(
'label' => qa_lang_html('admin/viewing_title'), 'label' => qa_lang_html('admin/moderate_title').($count ? (' ('.$count.')') : ''),
'url' => qa_path_html('admin/viewing'), 'url' => qa_path_html('admin/moderate'),
); );
}
$navigation['admin/lists']=array( if (qa_opt('flagging_of_posts') && !qa_user_maximum_permit_error('permit_hide_show')) {
'label' => qa_lang_html('admin/lists_title'), $count=qa_user_permit_error('permit_hide_show') ? null : qa_opt('cache_flaggedcount'); // if only in some categories don't show cached count
'url' => qa_path_html('admin/lists'),
);
if (qa_using_categories()) $navigation['admin/flagged']=array(
$navigation['admin/categories']=array( 'label' => qa_lang_html('admin/flagged_title').($count ? (' ('.$count.')') : ''),
'label' => qa_lang_html('admin/categories_title'), 'url' => qa_path_html('admin/flagged'),
'url' => qa_path_html('admin/categories'), );
); }
$navigation['admin/permissions']=array( if ( (!qa_user_maximum_permit_error('permit_hide_show')) || (!qa_user_maximum_permit_error('permit_delete_hidden')) )
'label' => qa_lang_html('admin/permissions_title'), $navigation['admin/hidden']=array(
'url' => qa_path_html('admin/permissions'), 'label' => qa_lang_html('admin/hidden_title'),
); 'url' => qa_path_html('admin/hidden'),
);
$navigation['admin/pages']=array( if ( (!QA_FINAL_EXTERNAL_USERS) && qa_opt('moderate_users') && ($level>=QA_USER_LEVEL_MODERATOR)) {
'label' => qa_lang_html('admin/pages_title'), $count=qa_opt('cache_uapprovecount');
'url' => qa_path_html('admin/pages'),
);
$navigation['admin/feeds']=array( $navigation['admin/approve']=array(
'label' => qa_lang_html('admin/feeds_title'), 'label' => qa_lang_html('admin/approve_users_title').($count ? (' ('.$count.')') : ''),
'url' => qa_path_html('admin/feeds'), 'url' => qa_path_html('admin/approve'),
); );
}
$navigation['admin/points']=array( return $navigation;
'label' => qa_lang_html('admin/points_title'), }
'url' => qa_path_html('admin/points'),
);
$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( * Return the error that needs to displayed on all admin pages, or null if none
'label' => qa_lang_html('admin/caching_title'), */
'url' => qa_path_html('admin/caching'), 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( if (defined('QA_DB_VERSION_CURRENT') && (qa_opt('db_version')<QA_DB_VERSION_CURRENT) && (qa_get_logged_in_level()>=QA_USER_LEVEL_ADMIN))
'label' => qa_lang_html('admin/stats_title'), return strtr(
'url' => qa_path_html('admin/stats'), qa_lang_html('admin/upgrade_db'),
);
array(
'^1' => '<a href="'.qa_path_html('install').'">',
'^2' => '</a>',
)
);
if (!QA_FINAL_EXTERNAL_USERS) elseif (defined('QA_BLOBS_DIRECTORY') && !is_writable(QA_BLOBS_DIRECTORY))
$navigation['admin/mailing']=array( return qa_lang_html_sub('admin/blobs_directory_error', qa_html(QA_BLOBS_DIRECTORY));
'label' => qa_lang_html('admin/mailing_title'),
'url' => qa_path_html('admin/mailing'),
);
$navigation['admin/plugins']=array( else
'label' => qa_lang_html('admin/plugins_title'), return null;
'url' => qa_path_html('admin/plugins'), }
);
}
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.')') : ''), * Return an HTML fragment to display for a URL test which has passed
'url' => qa_path_html('admin/moderate'), */
); 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.')') : ''), * Returns whether a URL path beginning with $requestpart is reserved by the engine or a plugin page module
'url' => qa_path_html('admin/flagged'), */
); 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')) ) if (isset($routing[$requestpart]) || isset($routing[$requestpart.'/']) || is_numeric($requestpart))
$navigation['admin/hidden']=array( return true;
'label' => qa_lang_html('admin/hidden_title'),
'url' => qa_path_html('admin/hidden'),
);
if ( (!QA_FINAL_EXTERNAL_USERS) && qa_opt('moderate_users') && ($level>=QA_USER_LEVEL_MODERATOR)) { $pathmap=qa_get_request_map();
$count=qa_opt('cache_uapprovecount');
$navigation['admin/approve']=array( foreach ($pathmap as $mappedrequest)
'label' => qa_lang_html('admin/approve_users_title').($count ? (' ('.$count.')') : ''), if (trim(strtolower($mappedrequest)) == $requestpart)
'url' => qa_path_html('admin/approve'), 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 false;
/* }
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>',
)
);
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() $useraccount=qa_db_select_with_pending(qa_db_user_account_selectspec($entityid, true));
/*
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<';
}
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) case 'userblock':
/* require_once QA_INCLUDE_DIR.'app/users-edit.php';
Returns whether a URL path beginning with $requestpart is reserved by the engine or a plugin page module qa_set_user_blocked($useraccount['userid'], $useraccount['handle'], true);
*/ return true;
{ break;
$requestpart=trim(strtolower($requestpart)); }
$routing=qa_page_routing();
if (isset($routing[$requestpart]) || isset($routing[$requestpart.'/']) || is_numeric($requestpart)) } else { // something to do with a post
return true; require_once QA_INCLUDE_DIR.'app/posts.php';
$pathmap=qa_get_request_map(); $post=qa_post_get_full($entityid);
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;
}
$pagemodules=qa_load_modules_with('page', 'match_request'); if (isset($post)) {
foreach ($pagemodules as $pagemodule) $queued=(substr($post['type'], 1)=='_QUEUED');
if ($pagemodule->match_request($requestpart))
return true;
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) case 'hide':
/* if ((!$queued) && !qa_user_post_permit_error('permit_hide_show', $post)) {
Returns true if admin (hidden/flagged/approve/moderate) page $action performed on $entityid is permitted by the qa_post_set_hidden($entityid, true, $userid);
logged in user and was processed successfully return true;
*/ }
{ break;
$userid=qa_get_logged_in_userid();
if ( (!QA_FINAL_EXTERNAL_USERS) && (($action=='userapprove') || ($action=='userblock')) ) { // approve/block moderated users case 'reshow':
require_once QA_INCLUDE_DIR.'db/selects.php'; 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) ) case 'clearflags':
switch ($action) { require_once QA_INCLUDE_DIR.'app/votes.php';
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 'userblock': if (!qa_user_post_permit_error('permit_hide_show', $post)) {
require_once QA_INCLUDE_DIR.'app/users-edit.php'; qa_flags_clear_all($post, $userid, qa_get_logged_in_handle(), null);
qa_set_user_blocked($useraccount['userid'], $useraccount['handle'], true);
return true; return true;
break; }
} 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;
}
} }
} }
return false;
} }
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)
*/ /**
{ * 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) function qa_admin_check_clicks()
if (strpos($field, 'admin_')===0) { {
@list($dummy, $entityid, $action)=explode('_', $field); if (qa_is_http_post())
foreach ($_POST as $field => $value)
if (strlen($entityid) && strlen($action)) { if (strpos($field, 'admin_')===0) {
if (!qa_check_form_security_code('admin/click', qa_post_text('code'))) @list($dummy, $entityid, $action)=explode('_', $field);
return qa_lang_html('misc/form_security_again');
elseif (qa_admin_single_click($entityid, $action)) if (strlen($entityid) && strlen($action)) {
qa_redirect(qa_request()); 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. * 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. * @deprecated Deprecated from 1.7; use `qa_addon_metadata($contents, $type)` instead.
*/ */
function qa_admin_addon_metadata($contents, $fields) function qa_admin_addon_metadata($contents, $fields)
{ {
$metadata=array(); $metadata=array();
foreach ($fields as $key => $field) foreach ($fields as $key => $field)
if (preg_match('/'.str_replace(' ', '[ \t]*', preg_quote($field, '/')).':[ \t]*([^\n\f]*)[\n\f]/i', $contents, $matches)) if (preg_match('/'.str_replace(' ', '[ \t]*', preg_quote($field, '/')).':[ \t]*([^\n\f]*)[\n\f]/i', $contents, $matches))
$metadata[$key]=trim($matches[1]); $metadata[$key]=trim($matches[1]);
return $metadata; 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 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 reset($hashes);
* 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 the URL (relative to the current page) to navigate to the options panel for plugin module $name of $type * 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_module_options_path($type, $name) function qa_admin_plugin_options_path($directory)
{ {
$info = qa_get_module_info($type, $name); $hash = qa_admin_plugin_directory_hash($directory);
$dir = rtrim($info['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'], '/');
/* return qa_admin_plugin_options_path($dir);
Omit PHP closing tag to help avoid accidental output }
*/
\ No newline at end of file
...@@ -20,255 +20,314 @@ ...@@ -20,255 +20,314 @@
More about this license: http://www.question2answer.org/license.php 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 if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../'); header('Location: ../');
exit; 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'; qa_db_posts_calc_category_path($postid);
require_once QA_INCLUDE_DIR.'db/post-create.php'; qa_db_hotness_update($postid);
require_once QA_INCLUDE_DIR.'db/points.php';
require_once QA_INCLUDE_DIR.'db/hotness.php';
require_once QA_INCLUDE_DIR.'util/string.php';
if ($queued) {
qa_db_queuedcount_update();
function qa_combine_notify_email($userid, $notify, $email) } else {
/* qa_post_index($postid, 'Q', $postid, @$followanswer['postid'], $title, $content, $format, $text, $tagstring, $categoryid);
Return value to store in database combining $notify and $email values entered by user $userid (or null for anonymous) qa_update_counts_for_q($postid);
*/ qa_db_points_update_ifuser($userid, 'qposts');
{
return $notify ? (empty($email) ? (isset($userid) ? '@' : null) : $email) : null;
} }
qa_report_event($queued ? 'q_queue' : 'q_post', $userid, $handle, $cookieid, array(
function qa_question_create($followanswer, $userid, $handle, $cookieid, $title, $content, $format, $text, $tagstring, $notify, $email, 'postid' => $postid,
$categoryid=null, $extravalue=null, $queued=false, $name=null) 'parentid' => @$followanswer['postid'],
/* 'parent' => $followanswer,
Add a question (application level) - create record, update appropriate counts, index it, send notifications. 'title' => $title,
If question is follow-on from an answer, $followanswer should contain answer database record, otherwise null. 'content' => $content,
See qa-app-posts.php for a higher-level function which is easier to use. 'format' => $format,
*/ 'text' => $text,
{ 'tags' => $tagstring,
require_once QA_INCLUDE_DIR.'db/selects.php'; 'categoryid' => $categoryid,
'extra' => $extravalue,
$postid=qa_db_post_create($queued ? 'Q_QUEUED' : 'Q', @$followanswer['postid'], $userid, isset($userid) ? null : $cookieid, 'name' => $name,
qa_remote_ip_address(), $title, $content, $format, $tagstring, qa_combine_notify_email($userid, $notify, $email), 'notify' => $notify,
$categoryid, isset($userid) ? null : $name); 'email' => $email,
));
if (isset($extravalue)) {
require_once QA_INCLUDE_DIR.'db/metas.php'; return $postid;
qa_db_postmeta_set($postid, 'qa_q_extra', $extravalue); }
}
qa_db_posts_calc_category_path($postid); /**
qa_db_hotness_update($postid); * Perform various common cached count updating operations to reflect changes in the question whose id is $postid
* @param $postid
if ($queued) { */
qa_db_queuedcount_update(); function qa_update_counts_for_q($postid)
{
} else { if (isset($postid)) // post might no longer exist
qa_post_index($postid, 'Q', $postid, @$followanswer['postid'], $title, $content, $format, $text, $tagstring, $categoryid); qa_db_category_path_qcount_update(qa_db_post_get_category_path($postid));
qa_update_counts_for_q($postid);
qa_db_points_update_ifuser($userid, 'qposts'); qa_db_qcount_update();
} qa_db_unaqcount_update();
qa_db_unselqcount_update();
qa_report_event($queued ? 'q_queue' : 'q_post', $userid, $handle, $cookieid, array( qa_db_unupaqcount_update();
'postid' => $postid, }
'parentid' => @$followanswer['postid'],
'parent' => $followanswer,
'title' => $title, /**
'content' => $content, * Return an array containing the elements of $inarray whose key is in $keys
'format' => $format, * @param $inarray
'text' => $text, * @param $keys
'tags' => $tagstring, * @return array
'categoryid' => $categoryid, */
'extra' => $extravalue, function qa_array_filter_by_keys($inarray, $keys)
'name' => $name, {
'notify' => $notify, $outarray = array();
'email' => $email,
)); foreach ($keys as $key) {
if (isset($inarray[$key]))
return $postid; $outarray[$key] = $inarray[$key];
} }
return $outarray;
function qa_update_counts_for_q($postid) }
/*
Perform various common cached count updating operations to reflect changes in the question whose id is $postid
*/ /**
{ * Suspend the indexing (and unindexing) of posts via qa_post_index(...) and qa_post_unindex(...)
if (isset($postid)) // post might no longer exist * if $suspend is true, otherwise reinstate it. A counter is kept to allow multiple calls.
qa_db_category_path_qcount_update(qa_db_post_get_category_path($postid)); * @param bool $suspend
*/
qa_db_qcount_update(); function qa_suspend_post_indexing($suspend = true)
qa_db_unaqcount_update(); {
qa_db_unselqcount_update(); global $qa_post_indexing_suspended;
qa_db_unupaqcount_update();
$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');
} }
qa_report_event($queued ? 'a_queue' : 'a_post', $userid, $handle, $cookieid, array(
function qa_array_filter_by_keys($inarray, $keys) 'postid' => $postid,
/* 'parentid' => $question['postid'],
Return an array containing the elements of $inarray whose key is in $keys 'parent' => $question,
*/ 'content' => $content,
{ 'format' => $format,
$outarray=array(); 'text' => $text,
'categoryid' => $question['categoryid'],
foreach ($keys as $key) 'name' => $name,
if (isset($inarray[$key])) 'notify' => $notify,
$outarray[$key]=$inarray[$key]; 'email' => $email,
));
return $outarray;
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) foreach ($commentsfollows as $comment) {
/* if (($comment['type'] == 'C') && ($comment['parentid'] == $parent['postid'])) // find just those for this parent, fully visible
Suspend the indexing (and unindexing) of posts via qa_post_index(...) and qa_post_unindex(...) $thread[] = $comment;
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);
} }
qa_report_event($queued ? 'c_queue' : 'c_post', $userid, $handle, $cookieid, array(
function qa_post_index($postid, $type, $questionid, $parentid, $title, $content, $format, $text, $tagstring, $categoryid) 'postid' => $postid,
/* 'parentid' => $parent['postid'],
Add post $postid (which comes under $questionid) of $type (Q/A/C) to the database index, with $title, $text, 'parenttype' => $parent['basetype'],
$tagstring and $categoryid. Calls through to all installed search modules. 'parent' => $parent,
*/ 'questionid' => $question['postid'],
{ 'question' => $question,
global $qa_post_indexing_suspended; 'thread' => $thread,
'content' => $content,
if ($qa_post_indexing_suspended>0) 'format' => $format,
return; 'text' => $text,
'categoryid' => $question['categoryid'],
// Send through to any search modules for indexing 'name' => $name,
'notify' => $notify,
$searches=qa_load_modules_with('search', 'index_post'); 'email' => $email,
foreach ($searches as $search) ));
$search->index_post($postid, $type, $questionid, $parentid, $title, $content, $format, $text, $tagstring, $categoryid);
} return $postid;
}
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
...@@ -20,1069 +20,1249 @@ ...@@ -20,1069 +20,1249 @@
More about this license: http://www.question2answer.org/license.php 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 if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../'); header('Location: ../');
exit; 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'; if ($setupdated && $remoderate) {
require_once QA_INCLUDE_DIR.'app/updates.php'; require_once QA_INCLUDE_DIR . 'app/posts.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';
$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); foreach ($answers as $answer)
define('QA_POST_STATUS_HIDDEN', 1); qa_post_unindex($answer['postid']);
define('QA_POST_STATUS_QUEUED', 2);
function qa_question_set_content($oldquestion, $title, $content, $format, $text, $tagstring, $notify, $userid, $handle, $cookieid, $extravalue=null, $name=null, $remoderate=false, $silent=false) foreach ($commentsfollows as $comment) {
/* if ($comment['basetype'] == 'C')
Change the fields of a question (application level) to $title, $content, $format, $tagstring, $notify, $extravalue qa_post_unindex($comment['postid']);
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);
} }
if ($setupdated && $remoderate) { if (@$closepost['parentid'] == $oldquestion['postid'])
require_once QA_INCLUDE_DIR.'app/posts.php'; qa_post_unindex($closepost['postid']);
$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']);
qa_db_post_set_type($oldquestion['postid'], 'Q_QUEUED'); qa_db_post_set_type($oldquestion['postid'], 'Q_QUEUED');
qa_update_counts_for_q($oldquestion['postid']); qa_update_counts_for_q($oldquestion['postid']);
qa_db_queuedcount_update(); qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects')); qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects'));
if ($oldquestion['flagcount']) if ($oldquestion['flagcount'])
qa_db_flaggedcount_update(); qa_db_flaggedcount_update();
} } elseif ($oldquestion['type'] == 'Q') { // not hidden or queued
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']);
qa_post_index($oldquestion['postid'], 'Q', $oldquestion['postid'], $oldquestion['parentid'], $title, $content, $format, $text, $tagstring, $oldquestion['categoryid']); }
}
$eventparams=array( $eventparams = array(
'postid' => $oldquestion['postid'], 'postid' => $oldquestion['postid'],
'title' => $title, 'title' => $title,
'content' => $content, 'content' => $content,
'format' => $format, 'format' => $format,
'text' => $text, 'text' => $text,
'tags' => $tagstring, 'tags' => $tagstring,
'extra' => $extravalue, 'extra' => $extravalue,
'name' => $name, 'name' => $name,
'oldquestion' => $oldquestion, 'oldquestion' => $oldquestion,
); );
qa_report_event('q_edit', $userid, $handle, $cookieid, $eventparams + array( qa_report_event('q_edit', $userid, $handle, $cookieid, $eventparams + array(
'silent' => $silent, 'silent' => $silent,
'oldtitle' => $oldquestion['title'], 'oldtitle' => $oldquestion['title'],
'oldcontent' => $oldquestion['content'], 'oldcontent' => $oldquestion['content'],
'oldformat' => $oldquestion['format'], 'oldformat' => $oldquestion['format'],
'oldtags' => $oldquestion['tags'], 'oldtags' => $oldquestion['tags'],
'titlechanged' => $titlechanged, 'titlechanged' => $titlechanged,
'contentchanged' => $contentchanged, 'contentchanged' => $contentchanged,
'tagschanged' => $tagschanged, '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) qa_report_event('a_select', $userid, $handle, $cookieid, array(
/* 'parentid' => $oldquestion['postid'],
Set the selected answer (application level) of $oldquestion to $selchildid. Pass details of the user doing this 'parent' => $oldquestion,
in $userid, $handle and $cookieid, and the database records for the selected and deselected answers in $answers. 'postid' => $selchildid,
Handles user points values and notifications. 'answer' => $answers[$selchildid],
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],
));
}
} }
}
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 * 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. * $oldclosepost (to match $oldquestion['closedbyid']) if any.
See qa-app-posts.php for a higher-level function which is easier to use. * See qa-app-posts.php for a higher-level function which is easier to use.
*/ * @param $oldquestion
{ * @param $oldclosepost
if (isset($oldquestion['closedbyid'])) { * @param $userid
qa_db_post_set_closed($oldquestion['postid'], null, $userid, qa_remote_ip_address()); * @param $handle
* @param $cookieid
if (isset($oldclosepost) && ($oldclosepost['parentid']==$oldquestion['postid'])) { */
qa_post_unindex($oldclosepost['postid']); function qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid)
qa_db_post_delete($oldclosepost['postid']); {
} if (isset($oldquestion['closedbyid'])) {
qa_db_post_set_closed($oldquestion['postid'], null, $userid, qa_remote_ip_address());
qa_report_event('q_reopen', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'], if (isset($oldclosepost) && ($oldclosepost['parentid'] == $oldquestion['postid'])) {
'oldquestion' => $oldquestion, qa_post_unindex($oldclosepost['postid']);
)); qa_db_post_delete($oldclosepost['postid']);
} }
}
function qa_question_close_duplicate($oldquestion, $oldclosepost, $originalpostid, $userid, $handle, $cookieid) qa_report_event('q_reopen', $userid, $handle, $cookieid, array(
/*
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(
'postid' => $oldquestion['postid'], 'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion, '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 * Close $oldquestion as a duplicate of the question with id $originalpostid. Pass details of the user doing this in
$cookieid, and the $oldclosepost (to match $oldquestion['closedbyid']) if any. * $userid, $handle and $cookieid, and the $oldclosepost (to match $oldquestion['closedbyid']) if any. See
See qa-app-posts.php for a higher-level function which is easier to use. * qa-app-posts.php for a higher-level function which is easier to use.
*/ * @param $oldquestion
{ * @param $oldclosepost
qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid); * @param $originalpostid
* @param $userid
$postid=qa_db_post_create('NOTE', $oldquestion['postid'], $userid, isset($userid) ? null : $cookieid, * @param $handle
qa_remote_ip_address(), null, $note, '', null, null, $oldquestion['categoryid']); * @param $cookieid
*/
qa_db_posts_calc_category_path($postid); function qa_question_close_duplicate($oldquestion, $oldclosepost, $originalpostid, $userid, $handle, $cookieid)
{
if ($oldquestion['type']=='Q') qa_question_close_clear($oldquestion, $oldclosepost, $userid, $handle, $cookieid);
qa_post_index($postid, 'NOTE', $oldquestion['postid'], $oldquestion['postid'], null, $note, '', $note, null, $oldquestion['categoryid']);
qa_db_post_set_closed($oldquestion['postid'], $originalpostid, $userid, qa_remote_ip_address());
qa_db_post_set_closed($oldquestion['postid'], $postid, $userid, qa_remote_ip_address());
qa_report_event('q_close', $userid, $handle, $cookieid, array(
qa_report_event('q_close', $userid, $handle, $cookieid, array( 'postid' => $oldquestion['postid'],
'postid' => $oldquestion['postid'], 'oldquestion' => $oldquestion,
'oldquestion' => $oldquestion, 'reason' => 'duplicate',
'reason' => 'other', 'originalid' => $originalpostid,
'note' => $note, ));
)); }
/**
* 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']);
} }
foreach ($commentsfollows as $comment) {
function qa_question_set_hidden($oldquestion, $hidden, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost=null) if ($comment['basetype'] == 'C')
/* qa_post_unindex($comment['postid']);
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);
} }
if (@$closepost['parentid'] == $oldquestion['postid'])
qa_post_unindex($closepost['postid']);
function qa_question_set_status($oldquestion, $status, $userid, $handle, $cookieid, $answers, $commentsfollows, $closepost=null) $setupdated = false;
/* $event = 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';
$washidden=($oldquestion['type']=='Q_HIDDEN'); if ($status == QA_POST_STATUS_QUEUED) {
$wasqueued=($oldquestion['type']=='Q_QUEUED'); $newtype = 'Q_QUEUED';
$wasrequeued=$wasqueued && isset($oldquestion['updated']); if (!$wasqueued)
$event = 'q_requeue'; // same event whether it was hidden or shown before
qa_post_unindex($oldquestion['postid']); } elseif ($status == QA_POST_STATUS_HIDDEN) {
$newtype = 'Q_HIDDEN';
foreach ($answers as $answer) if (!$washidden) {
qa_post_unindex($answer['postid']); $event = $wasqueued ? 'q_reject' : 'q_hide';
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';
if (!$wasqueued) if (!$wasqueued)
$event='q_requeue'; // same event whether it was hidden or shown before $setupdated = true;
}
} elseif ($status==QA_POST_STATUS_HIDDEN) {
$newtype='Q_HIDDEN';
if (!$washidden) {
$event=$wasqueued ? 'q_reject' : 'q_hide';
if (!$wasqueued)
$setupdated=true;
}
} elseif ($status==QA_POST_STATUS_NORMAL) { } elseif ($status == QA_POST_STATUS_NORMAL) {
$newtype='Q'; $newtype = 'Q';
if ($wasqueued) if ($wasqueued)
$event='q_approve'; $event = 'q_approve';
elseif ($washidden) { elseif ($washidden) {
$event='q_reshow'; $event = 'q_reshow';
$setupdated=true; $setupdated = true;
} }
} else } else
qa_fatal_error('Unknown status in qa_question_set_status(): '.$status); 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 ($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... 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); qa_db_post_set_updated($oldquestion['postid'], null);
else { // ... otherwise we're approving original created post else { // ... otherwise we're approving original created post
qa_db_post_set_created($oldquestion['postid'], null); qa_db_post_set_created($oldquestion['postid'], null);
qa_db_hotness_update($oldquestion['postid']); qa_db_hotness_update($oldquestion['postid']);
}
} }
}
qa_update_counts_for_q($oldquestion['postid']); qa_update_counts_for_q($oldquestion['postid']);
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects')); qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects'));
if ($wasqueued || ($status==QA_POST_STATUS_QUEUED)) if ($wasqueued || ($status == QA_POST_STATUS_QUEUED))
qa_db_queuedcount_update(); qa_db_queuedcount_update();
if ($oldquestion['flagcount']) if ($oldquestion['flagcount'])
qa_db_flaggedcount_update(); qa_db_flaggedcount_update();
if ($status==QA_POST_STATUS_NORMAL) { if ($status == QA_POST_STATUS_NORMAL) {
qa_post_index($oldquestion['postid'], 'Q', $oldquestion['postid'], $oldquestion['parentid'], $oldquestion['title'], $oldquestion['content'], 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']); $oldquestion['format'], qa_viewer_text($oldquestion['content'], $oldquestion['format']), $oldquestion['tags'], $oldquestion['categoryid']);
foreach ($answers as $answer) foreach ($answers as $answer) {
if ($answer['type']=='A') // even if question visible, don't index hidden or queued answers 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, 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']); $answer['content'], $answer['format'], qa_viewer_text($answer['content'], $answer['format']), null, $answer['categoryid']);
}
}
foreach ($commentsfollows as $comment) foreach ($commentsfollows as $comment) {
if ($comment['type']=='C') { if ($comment['type'] == 'C') {
$answer=@$answers[$comment['parentid']]; $answer = @$answers[$comment['parentid']];
if ( (!isset($answer)) || ($answer['type']=='A') ) // don't index comment if it or its parent is hidden 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, 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']); $comment['content'], $comment['format'], qa_viewer_text($comment['content'], $comment['format']), null, $comment['categoryid']);
} }
}
}
if ($closepost['parentid']==$oldquestion['postid']) if ($closepost['parentid'] == $oldquestion['postid']) {
qa_post_index($closepost['postid'], $closepost['type'], $oldquestion['postid'], $closepost['parentid'], null, 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']); $closepost['content'], $closepost['format'], qa_viewer_text($closepost['content'], $closepost['format']), null, $closepost['categoryid']);
} }
}
$eventparams=array( $eventparams = array(
'postid' => $oldquestion['postid'], 'postid' => $oldquestion['postid'],
'parentid' => $oldquestion['parentid'], 'parentid' => $oldquestion['parentid'],
'parent' => isset($oldquestion['parentid']) ? qa_db_single_select(qa_db_full_post_selectspec(null, $oldquestion['parentid'])) : null, 'parent' => isset($oldquestion['parentid']) ? qa_db_single_select(qa_db_full_post_selectspec(null, $oldquestion['parentid'])) : null,
'title' => $oldquestion['title'], 'title' => $oldquestion['title'],
'content' => $oldquestion['content'], 'content' => $oldquestion['content'],
'format' => $oldquestion['format'], 'format' => $oldquestion['format'],
'text' => qa_viewer_text($oldquestion['content'], $oldquestion['format']), 'text' => qa_viewer_text($oldquestion['content'], $oldquestion['format']),
'tags' => $oldquestion['tags'], 'tags' => $oldquestion['tags'],
'categoryid' => $oldquestion['categoryid'], 'categoryid' => $oldquestion['categoryid'],
'name' => $oldquestion['name'], 'name' => $oldquestion['name'],
); );
if (isset($event)) if (isset($event)) {
qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array( qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array(
'oldquestion' => $oldquestion, '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) qa_report_event('q_post', $oldquestion['userid'], $oldquestion['handle'], $oldquestion['cookieid'], $eventparams + array(
/* 'notify' => isset($oldquestion['notify']),
Sets the category (application level) of $oldquestion to $categoryid. Pass details of the user doing this in 'email' => qa_email_validate($oldquestion['notify']) ? $oldquestion['notify'] : null,
$userid, $handle and $cookieid, the database records for all answers to the question in $answers, the database 'delayed' => $oldquestion['created'],
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'],
)); ));
} }
}
/**
* 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) if (@$closepost['parentid'] == $oldquestion['postid'])
/* $otherpostids[] = $closepost['postid'];
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,
);
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'])) { $searchmodules = qa_load_modules_with('search', 'move_post');
qa_db_post_set_closed($oldquestion['postid'], null); // for foreign key constraint foreach ($searchmodules as $searchmodule) {
qa_post_unindex($oldclosepost['postid']); $searchmodule->move_post($oldquestion['postid'], $categoryid);
qa_db_post_delete($oldclosepost['postid']); 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);
} }
qa_report_event('q_move', $userid, $handle, $cookieid, array(
function qa_question_set_userid($oldquestion, $userid, $handle, $cookieid) 'postid' => $oldquestion['postid'],
/* 'oldquestion' => $oldquestion,
Set the author (application level) of $oldquestion to $userid and also pass $handle and $cookieid 'categoryid' => $categoryid,
of user. Updates points and reports events as appropriate. 'oldcategoryid' => $oldquestion['categoryid'],
*/ ));
{ }
require_once QA_INCLUDE_DIR.'db/votes.php';
$postid = $oldquestion['postid']; /**
* Permanently delete a question (application level) from the database. The question must not have any answers or
qa_db_post_set_userid($postid, $userid); * comments on it. Pass details of the user doing this in $userid, $handle and $cookieid, and $closepost to match
qa_db_uservote_remove_own($postid); * $oldquestion['closedbyid'] (if any). Handles unindexing, votes, points, cached counts and event reports.
qa_db_post_recount_votes($postid); * See qa-app-posts.php for a higher-level function which is easier to use.
* @param $oldquestion
qa_db_points_update_ifuser($oldquestion['userid'], array('qposts', 'aselects', 'qvoteds', 'upvoteds', 'downvoteds')); * @param $userid
qa_db_points_update_ifuser($userid, array('qposts', 'aselects', 'qvoteds', 'qupvotes', 'qdownvotes', 'upvoteds', 'downvoteds')); * @param $handle
* @param $cookieid
qa_report_event('q_claim', $userid, $handle, $cookieid, array( * @param $oldclosepost
'postid' => $postid, */
'oldquestion' => $oldquestion, 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) qa_post_unindex($oldquestion['postid']);
/* qa_db_post_delete($oldquestion['postid']); // also deletes any related voteds due to foreign key cascading
Remove post $postid from our index and update appropriate word counts. Calls through to all search modules. 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'));
global $qa_post_indexing_suspended;
if ($qa_post_indexing_suspended>0)
return;
// Send through to any search modules for unindexing 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
$searchmodules=qa_load_modules_with('search', 'unindex_post'); qa_db_points_update_ifuser($voteruserid, ($vote > 0) ? 'qupvotes' : 'qdownvotes');
foreach ($searchmodules as $searchmodule)
$searchmodule->unindex_post($postid);
} }
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); * Set the author (application level) of $oldquestion to $userid and also pass $handle and $cookieid
$setupdated=$contentchanged && (!$wasqueued) && !$silent; * 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, $postid = $oldquestion['postid'];
$setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_CONTENT, $name);
if ($setupdated && $remoderate) { qa_db_post_set_userid($postid, $userid);
require_once QA_INCLUDE_DIR.'app/posts.php'; 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) qa_report_event('q_claim', $userid, $handle, $cookieid, array(
if ( ($comment['basetype']=='C') && ($comment['parentid']==$oldanswer['postid']) ) 'postid' => $postid,
qa_post_unindex($comment['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 if ($qa_post_indexing_suspended > 0)
qa_post_index($oldanswer['postid'], 'A', $question['postid'], $oldanswer['parentid'], null, $content, $format, $text, null, $oldanswer['categoryid']); return;
}
$eventparams=array( // Send through to any search modules for unindexing
'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) $searchmodules = qa_load_modules_with('search', 'unindex_post');
qa_report_event('a_requeue', $userid, $handle, $cookieid, $eventparams); 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(...) * Change the fields of an answer (application level) to $content, $format, $notify and $name, then reindex based on
This function is included mainly for backwards compatibility. * $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
qa_answer_set_status($oldanswer, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $userid, $handle, $cookieid, $question, $commentsfollows); * $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
function qa_answer_set_status($oldanswer, $status, $userid, $handle, $cookieid, $question, $commentsfollows) * @param $format
/* * @param $text
Set the status (application level) of $oldanswer to $status, one of the QA_POST_STATUS_* constants above. Pass * @param $notify
details of the user doing this in $userid, $handle and $cookieid, the database record for the question in $question, * @param $userid
and the database records for all comments on the answer in $commentsfollows ($commentsfollows can also contain other * @param $handle
records which are ignored). Handles indexing, user points, cached counts and event reports. See qa-app-posts.php for * @param $cookieid
a higher-level function which is easier to use. * @param $question
*/ * @param $name
{ * @param bool $remoderate
require_once QA_INCLUDE_DIR.'app/format.php'; * @param bool $silent
*/
$washidden=($oldanswer['type']=='A_HIDDEN'); function qa_answer_set_content($oldanswer, $content, $format, $text, $notify, $userid, $handle, $cookieid, $question, $name = null, $remoderate = false, $silent = false)
$wasqueued=($oldanswer['type']=='A_QUEUED'); {
$wasrequeued=$wasqueued && isset($oldanswer['updated']); qa_post_unindex($oldanswer['postid']);
qa_post_unindex($oldanswer['postid']); $wasqueued = ($oldanswer['type'] == 'A_QUEUED');
$contentchanged = strcmp($oldanswer['content'], $content) || strcmp($oldanswer['format'], $format);
foreach ($commentsfollows as $comment) $setupdated = $contentchanged && (!$wasqueued) && !$silent;
if ( ($comment['basetype']=='C') && ($comment['parentid']==$oldanswer['postid']) )
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']); 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_update_q_counts_for_a($question['postid']);
qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds')); qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds'));
if ($wasqueued || ($status==QA_POST_STATUS_QUEUED))
qa_db_queuedcount_update();
if ($oldanswer['flagcount']) if ($oldanswer['flagcount'])
qa_db_flaggedcount_update(); 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 } 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, $oldanswer['content'], qa_post_index($oldanswer['postid'], 'A', $question['postid'], $oldanswer['parentid'], null, $content, $format, $text, null, $oldanswer['categoryid']);
$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'],
));
}
} }
$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) $setupdated = false;
/* $event = null;
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']);
$params = array( if ($status == QA_POST_STATUS_QUEUED) {
'postid' => $oldanswer['postid'], $newtype = 'A_QUEUED';
'parentid' => $oldanswer['parentid'], if (!$wasqueued)
'oldanswer' => $oldanswer, $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']); if ($question['selchildid'] == $oldanswer['postid']) { // remove selected answer
qa_db_post_delete($oldanswer['postid']); // also deletes any related voteds due to cascading qa_question_set_selchildid(null, null, null, $question, null, array($oldanswer['postid'] => $oldanswer));
}
if ($question['selchildid']==$oldanswer['postid']) { } elseif ($status == QA_POST_STATUS_NORMAL) {
qa_db_post_set_selchildid($question['postid'], null); $newtype = 'A';
qa_db_points_update_ifuser($question['userid'], 'aselects'); if ($wasqueued)
qa_db_unselqcount_update(); $event = 'a_approve';
elseif ($washidden) {
$event = 'a_reshow';
$setupdated = true;
} }
qa_update_q_counts_for_a($question['postid']); } else
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'avoteds', 'upvoteds', 'downvoteds')); qa_fatal_error('Unknown status in qa_answer_set_status(): ' . $status);
foreach ($useridvotes as $voteruserid => $vote) qa_db_post_set_type($oldanswer['postid'], $newtype, $setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_VISIBLE);
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_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) if ($wasqueued || $status == QA_POST_STATUS_QUEUED)
/* qa_db_queuedcount_update();
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'];
qa_db_post_set_userid($postid, $userid); if ($oldanswer['flagcount'])
qa_db_uservote_remove_own($postid); qa_db_flaggedcount_update();
qa_db_post_recount_votes($postid);
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'avoteds', '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_db_points_update_ifuser($userid, array('aposts', 'aselecteds', 'avoteds', 'aupvotes', 'adownvotes', 'upvoteds', 'downvoteds')); 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( foreach ($commentsfollows as $comment) {
'postid' => $postid, if (($comment['type'] == 'C') && ($comment['parentid'] == $oldanswer['postid'])) { // and don't index hidden/queued comments
'parentid' => $oldanswer['parentid'], qa_post_index($comment['postid'], $comment['type'], $question['postid'], $comment['parentid'], null, $comment['content'],
'oldanswer' => $oldanswer, $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) if ($wasqueued && ($status == QA_POST_STATUS_NORMAL) && !$wasrequeued) {
/* require_once QA_INCLUDE_DIR . 'util/string.php';
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,
);
qa_report_event('c_edit', $userid, $handle, $cookieid, $eventparams + array( qa_report_event('a_post', $oldanswer['userid'], $oldanswer['handle'], $oldanswer['cookieid'], $eventparams + array(
'silent' => $silent, 'notify' => isset($oldanswer['notify']),
'oldcontent' => $oldcomment['content'], 'email' => qa_email_validate($oldanswer['notify']) ? $oldanswer['notify'] : null,
'oldformat' => $oldcomment['format'], 'delayed' => $oldanswer['created'],
'contentchanged' => $contentchanged,
)); ));
}
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) 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
Convert an answer to a comment (application level) and set its fields to $content, $format, $notify and $name. For qa_db_points_update_ifuser($voteruserid, ($vote > 0) ? 'aupvotes' : 'adownvotes');
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);
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_ccount_update();
qa_db_points_update_ifuser($oldanswer['userid'], array('aposts', 'aselecteds', 'cposts', 'avoteds')); qa_db_queuedcount_update();
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
$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));
}
$eventparams=array( if ($oldcomment['flagcount'])
'postid' => $oldanswer['postid'], qa_db_flaggedcount_update();
'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) } elseif ($oldcomment['type'] == 'C' && $question['type'] == 'Q' && ($parent['type'] == 'Q' || $parent['type'] == 'A')) { // all must be visible
qa_report_event('c_requeue', $userid, $handle, $cookieid, $eventparams); qa_post_index($oldcomment['postid'], 'C', $question['postid'], $oldcomment['parentid'], null, $content, $format, $text, null, $oldcomment['categoryid']);
// a-to-c conversion can be detected by presence of $event['oldanswer'] instead of $event['oldcomment']
} }
$eventparams = array(
function qa_comment_set_hidden($oldcomment, $hidden, $userid, $handle, $cookieid, $question, $parent) 'postid' => $oldcomment['postid'],
/* 'parentid' => $oldcomment['parentid'],
Set $oldcomment to hidden if $hidden is true, visible/normal if otherwise. All other parameters are as for qa_comment_set_status(...) 'parenttype' => $parent['basetype'],
This function is included mainly for backwards compatibility. 'parent' => $parent,
*/ 'questionid' => $question['postid'],
{ 'question' => $question,
qa_comment_set_status($oldcomment, $hidden ? QA_POST_STATUS_HIDDEN : QA_POST_STATUS_NORMAL, $userid, $handle, $cookieid, $question, $parent); '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) $useridvotes = qa_db_uservote_post_get($oldanswer['postid']);
/* foreach ($useridvotes as $voteruserid => $vote) {
Set the status (application level) of $oldcomment to $status, one of the QA_POST_STATUS_* constants above. Pass the // could do this in one query like in qa_db_users_recalc_points() but this will do for now - unlikely to be many votes
antecedent question's record in $question, details of the user doing this in $userid, $handle and $cookieid, and the qa_db_points_update_ifuser($voteruserid, ($vote > 0) ? 'aupvotes' : 'adownvotes');
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';
if (!isset($parent)) if ($setupdated && $remoderate) {
$parent=$question; // for backwards compatibility with old answer parameter qa_db_queuedcount_update();
$washidden=($oldcomment['type']=='C_HIDDEN'); if ($oldanswer['flagcount'])
$wasqueued=($oldcomment['type']=='C_QUEUED'); qa_db_flaggedcount_update();
$wasrequeued=$wasqueued && isset($oldcomment['updated']);
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; if ($question['selchildid'] == $oldanswer['postid']) { // remove selected answer
$event=null; qa_question_set_selchildid(null, null, null, $question, null, array($oldanswer['postid'] => $oldanswer));
}
if ($status==QA_POST_STATUS_QUEUED) { $eventparams = array(
$newtype='C_QUEUED'; '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) if (!$wasqueued)
$event='c_requeue'; // same event whether it was hidden or shown before $setupdated = true;
} 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);
} }
qa_db_ccount_update(); } elseif ($status == QA_POST_STATUS_NORMAL) {
qa_db_points_update_ifuser($oldcomment['userid'], array('cposts')); $newtype = 'C';
if ($wasqueued)
$event = 'c_approve';
elseif ($washidden) {
$event = 'c_reshow';
$setupdated = true;
}
if ($wasqueued || ($status==QA_POST_STATUS_QUEUED)) } else
qa_db_queuedcount_update(); qa_fatal_error('Unknown status in qa_comment_set_status(): ' . $status);
if ($oldcomment['flagcount']) qa_db_post_set_type($oldcomment['postid'], $newtype, $setupdated ? $userid : null, $setupdated ? qa_remote_ip_address() : null, QA_UPDATE_VISIBLE);
qa_db_flaggedcount_update();
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 if ($wasqueued && ($status == QA_POST_STATUS_NORMAL) && qa_opt('moderate_update_time')) { // ... for approval of a post, can set time to now instead
qa_post_index($oldcomment['postid'], 'C', $question['postid'], $oldcomment['parentid'], null, $oldcomment['content'], if ($wasrequeued)
$oldcomment['format'], qa_viewer_text($oldcomment['content'], $oldcomment['format']), null, $oldcomment['categoryid']); qa_db_post_set_updated($oldcomment['postid'], null);
else
$eventparams=array( qa_db_post_set_created($oldcomment['postid'], null);
'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) && !$wasrequeued) { qa_db_ccount_update();
require_once QA_INCLUDE_DIR.'db/selects.php'; qa_db_points_update_ifuser($oldcomment['userid'], array('cposts'));
require_once QA_INCLUDE_DIR.'util/string.php';
$commentsfollows=qa_db_single_select(qa_db_full_child_posts_selectspec(null, $oldcomment['parentid'])); if ($wasqueued || $status == QA_POST_STATUS_QUEUED)
$thread=array(); qa_db_queuedcount_update();
foreach ($commentsfollows as $comment) if ($oldcomment['flagcount'])
if (($comment['type']=='C') && ($comment['parentid']==$parent['postid'])) qa_db_flaggedcount_update();
$thread[]=$comment;
qa_report_event('c_post', $oldcomment['userid'], $oldcomment['handle'], $oldcomment['cookieid'], $eventparams + array( if ($question['type'] == 'Q' && ($parent['type'] == 'Q' || $parent['type'] == 'A') && $status == QA_POST_STATUS_NORMAL) {
'thread' => $thread, // only index if none of the things it depends on are hidden or queued
'notify' => isset($oldcomment['notify']), qa_post_index($oldcomment['postid'], 'C', $question['postid'], $oldcomment['parentid'], null, $oldcomment['content'],
'email' => qa_email_validate($oldcomment['notify']) ? $oldcomment['notify'] : null, $oldcomment['format'], qa_viewer_text($oldcomment['content'], $oldcomment['format']), null, $oldcomment['categoryid']);
'delayed' => $oldcomment['created'],
));
}
} }
$eventparams = array(
function qa_comment_delete($oldcomment, $question, $parent, $userid, $handle, $cookieid) 'postid' => $oldcomment['postid'],
/* 'parentid' => $oldcomment['parentid'],
Permanently delete a comment in $oldcomment (application level) from the database. Pass the database question in $question 'parenttype' => $parent['basetype'],
and the answer's database record in $answer if this is a comment on an answer, otherwise null. Pass details of the user 'parent' => $parent,
doing this in $userid, $handle and $cookieid. Handles unindexing, points, cached counts and event reports. 'questionid' => $question['postid'],
See qa-app-posts.php for a higher-level function which is easier to use. 'question' => $question,
*/ 'content' => $oldcomment['content'],
{ 'format' => $oldcomment['format'],
if (!isset($parent)) 'text' => qa_viewer_text($oldcomment['content'], $oldcomment['format']),
$parent=$question; // for backwards compatibility with old answer parameter 'categoryid' => $oldcomment['categoryid'],
'name' => $oldcomment['name'],
if ($oldcomment['type']!='C_HIDDEN') );
qa_fatal_error('Tried to delete a non-hidden comment');
if (isset($event)) {
$params = array( qa_report_event($event, $userid, $handle, $cookieid, $eventparams + array(
'postid' => $oldcomment['postid'],
'parentid' => $oldcomment['parentid'],
'oldcomment' => $oldcomment, '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) $commentsfollows = qa_db_single_select(qa_db_full_child_posts_selectspec(null, $oldcomment['parentid']));
/* $thread = array();
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'];
qa_db_post_set_userid($postid, $userid); foreach ($commentsfollows as $comment) {
qa_db_uservote_remove_own($postid); if ($comment['type'] == 'C' && $comment['parentid'] == $parent['postid'])
qa_db_post_recount_votes($postid); $thread[] = $comment;
}
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( qa_report_event('c_post', $oldcomment['userid'], $oldcomment['handle'], $oldcomment['cookieid'], $eventparams + array(
'postid' => $postid, 'thread' => $thread,
'parentid' => $oldcomment['parentid'], 'notify' => isset($oldcomment['notify']),
'oldcomment' => $oldcomment, '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 @@ ...@@ -56,698 +56,693 @@
[but these are not entirely redundant since they can contain historical information no longer in ^posts] [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 if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../'); header('Location: ../');
exit; exit;
} }
require_once QA_INCLUDE_DIR.'db/recalc.php'; require_once QA_INCLUDE_DIR.'db/recalc.php';
require_once QA_INCLUDE_DIR.'db/post-create.php'; require_once QA_INCLUDE_DIR.'db/post-create.php';
require_once QA_INCLUDE_DIR.'db/points.php'; require_once QA_INCLUDE_DIR.'db/points.php';
require_once QA_INCLUDE_DIR.'db/selects.php'; require_once QA_INCLUDE_DIR.'db/selects.php';
require_once QA_INCLUDE_DIR.'db/admin.php'; require_once QA_INCLUDE_DIR.'db/admin.php';
require_once QA_INCLUDE_DIR.'db/users.php'; require_once QA_INCLUDE_DIR.'db/users.php';
require_once QA_INCLUDE_DIR.'app/options.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-create.php';
require_once QA_INCLUDE_DIR.'app/post-update.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.
Advance the recalculation operation represented by $state by a single step. * $state can also be the name of a recalculation operation on its own.
$state can also be the name of a recalculation operation on its own. */
*/ function qa_recalc_perform_step(&$state)
{ {
$continue=false; $continue=false;
@list($operation, $length, $next, $done)=explode("\t", $state); @list($operation, $length, $next, $done)=explode("\t", $state);
switch ($operation) { switch ($operation) {
case 'doreindexcontent': case 'doreindexcontent':
qa_recalc_transition($state, 'doreindexcontent_pagereindex'); qa_recalc_transition($state, 'doreindexcontent_pagereindex');
break; break;
case 'doreindexcontent_pagereindex': case 'doreindexcontent_pagereindex':
$pages=qa_db_pages_get_for_reindexing($next, 10); $pages=qa_db_pages_get_for_reindexing($next, 10);
if (count($pages)) { if (count($pages)) {
require_once QA_INCLUDE_DIR.'app/format.php'; require_once QA_INCLUDE_DIR.'app/format.php';
$lastpageid=max(array_keys($pages)); $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) foreach ($searchmodules as $searchmodule)
$searchmodule->unindex_page($pageid); $searchmodule->index_page($pageid, $page['tags'], $page['heading'], $page['content'], 'html', $indextext);
$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);
}
} }
}
$next=1+$lastpageid; $next=1+$lastpageid;
$done+=count($pages); $done+=count($pages);
$continue=true; $continue=true;
} else
qa_recalc_transition($state, 'doreindexcontent_postcount');
break;
case 'doreindexcontent_postcount': } else
qa_db_qcount_update(); qa_recalc_transition($state, 'doreindexcontent_postcount');
qa_db_acount_update(); break;
qa_db_ccount_update();
qa_recalc_transition($state, 'doreindexcontent_postreindex'); case 'doreindexcontent_postcount':
break; qa_db_qcount_update();
qa_db_acount_update();
qa_db_ccount_update();
case 'doreindexcontent_postreindex': qa_recalc_transition($state, 'doreindexcontent_postreindex');
$posts=qa_db_posts_get_for_reindexing($next, 10); break;
if (count($posts)) { case 'doreindexcontent_postreindex':
require_once QA_INCLUDE_DIR.'app/format.php'; $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); $lastpostid=max(array_keys($posts));
qa_suspend_update_counts();
foreach ($posts as $postid => $post) { qa_db_prepare_for_reindexing($next, $lastpostid);
qa_post_unindex($postid); qa_suspend_update_counts();
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']);
}
$next=1+$lastpostid; foreach ($posts as $postid => $post) {
$done+=count($posts); qa_post_unindex($postid);
$continue=true; 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']);
} else {
qa_db_truncate_indexes($next);
qa_recalc_transition($state, 'doreindexposts_wordcount');
} }
break;
case 'doreindexposts_wordcount':
$wordids=qa_db_words_prepare_for_recounting($next, 1000);
if (count($wordids)) { $next=1+$lastpostid;
$lastwordid=max($wordids); $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; case 'doreindexposts_wordcount':
$done+=count($wordids); $wordids=qa_db_words_prepare_for_recounting($next, 1000);
$continue=true;
} else { if (count($wordids)) {
qa_db_tagcount_update(); // this is quick so just do it here $lastwordid=max($wordids);
qa_recalc_transition($state, 'doreindexposts_complete');
}
break;
case 'dorecountposts': qa_db_words_recount($next, $lastwordid);
qa_recalc_transition($state, 'dorecountposts_postcount');
break;
case 'dorecountposts_postcount': $next=1+$lastwordid;
qa_db_qcount_update(); $done+=count($wordids);
qa_db_acount_update(); $continue=true;
qa_db_ccount_update();
qa_db_unaqcount_update();
qa_db_unselqcount_update();
qa_recalc_transition($state, 'dorecountposts_votecount'); } else {
break; qa_db_tagcount_update(); // this is quick so just do it here
qa_recalc_transition($state, 'doreindexposts_complete');
}
break;
case 'dorecountposts_votecount': case 'dorecountposts':
$postids=qa_db_posts_get_for_recounting($next, 1000); qa_recalc_transition($state, 'dorecountposts_postcount');
break;
if (count($postids)) { case 'dorecountposts_postcount':
$lastpostid=max($postids); 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; case 'dorecountposts_votecount':
$done+=count($postids); $postids=qa_db_posts_get_for_recounting($next, 1000);
$continue=true;
} else if (count($postids)) {
qa_recalc_transition($state, 'dorecountposts_acount'); $lastpostid=max($postids);
break;
case 'dorecountposts_acount': qa_db_posts_votes_recount($next, $lastpostid);
$postids=qa_db_posts_get_for_recounting($next, 1000);
if (count($postids)) { $next=1+$lastpostid;
$lastpostid=max($postids); $done+=count($postids);
$continue=true;
qa_db_posts_answers_recount($next, $lastpostid); } else
qa_recalc_transition($state, 'dorecountposts_acount');
break;
$next=1+$lastpostid; case 'dorecountposts_acount':
$done+=count($postids); $postids=qa_db_posts_get_for_recounting($next, 1000);
$continue=true;
} else { if (count($postids)) {
qa_db_unupaqcount_update(); $lastpostid=max($postids);
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;
case 'dorefillevents': qa_db_posts_answers_recount($next, $lastpostid);
qa_recalc_transition($state, 'dorefillevents_qcount');
break;
case 'dorefillevents_qcount': $next=1+$lastpostid;
qa_db_qcount_update(); $done+=count($postids);
qa_recalc_transition($state, 'dorefillevents_refill'); $continue=true;
break;
case 'dorefillevents_refill': } else {
$questionids=qa_db_qs_get_for_event_refilling($next, 1); qa_db_unupaqcount_update();
qa_recalc_transition($state, 'dorecountposts_complete');
}
break;
if (count($questionids)) { case 'dorecalcpoints':
require_once QA_INCLUDE_DIR.'app/events.php'; qa_recalc_transition($state, 'dorecalcpoints_usercount');
require_once QA_INCLUDE_DIR.'app/updates.php'; break;
require_once QA_INCLUDE_DIR.'util/sort.php';
$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( } else
qa_db_full_post_selectspec(null, $questionid), $lastuserid=$next; // for truncation
qa_db_full_child_posts_selectspec(null, $questionid),
qa_db_full_a_child_posts_selectspec(null, $questionid)
);
// 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) case 'dorefillevents':
$posts[$postid]=$post; qa_recalc_transition($state, 'dorefillevents_qcount');
break;
foreach ($achildposts as $postid => $post) case 'dorefillevents_qcount':
$posts[$postid]=$post; 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) { if (count($questionids)) {
$followonq=($post['basetype']=='Q') && ($postid!=$questionid); 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) $lastquestionid=max($questionids);
$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;
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) // Retrieve all posts relating to this question
qa_create_event_for_q_user($questionid, $postid, $post['updatetype'], $post['lastuserid'], $post['userid'], $post['updated']);
}
// 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']); // Merge all posts while preserving keys as postids
qa_create_event_for_category($question['categoryid'], $questionid, null, $question['userid'], $question['created']);
// Collect comment threads $posts=array($questionid => $question);
$parentidcomments=array(); foreach ($childposts as $postid => $post)
$posts[$postid]=$post;
foreach ($posts as $postid => $post) foreach ($achildposts as $postid => $post)
if ($post['basetype']=='C') $posts[$postid]=$post;
$parentidcomments[$post['parentid']][$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) { foreach ($posts as $postid => $post) {
$keyuserids=array(); $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) { qa_create_event_for_q_user($questionid, $postid, $updatetype, $post['userid'], @$posts[$post['parentid']]['userid'], $post['created']);
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']);
if (isset($comment['userid'])) if (isset($post['updated']) && !$followonq)
$keyuserids[$comment['userid']]=true; qa_create_event_for_q_user($questionid, $postid, $post['updatetype'], $post['lastuserid'], $post['userid'], $post['updated']);
}
}
} }
$next=1+$lastquestionid; // Tags and categories of question
$done+=count($questionids);
$continue=true;
} else qa_create_event_for_tags($question['tags'], $questionid, null, $question['userid'], $question['created']);
qa_recalc_transition($state, 'dorefillevents_complete'); qa_create_event_for_category($question['categoryid'], $questionid, null, $question['userid'], $question['created']);
break;
case 'dorecalccategories': // Collect comment threads
qa_recalc_transition($state, 'dorecalccategories_postcount');
break;
case 'dorecalccategories_postcount': $parentidcomments=array();
qa_db_acount_update();
qa_db_ccount_update();
qa_recalc_transition($state, 'dorecalccategories_postupdate'); foreach ($posts as $postid => $post)
break; if ($post['basetype']=='C')
$parentidcomments[$post['parentid']][$postid]=$post;
case 'dorecalccategories_postupdate': // For each comment thread, notify all previous comment authors of each comment in the thread (could get slow)
$postids=qa_db_posts_get_for_recategorizing($next, 100);
if (count($postids)) { foreach ($parentidcomments as $parentid => $comments) {
$lastpostid=max($postids); $keyuserids=array();
qa_db_posts_recalc_categoryid($next, $lastpostid); qa_sort_by($comments, 'created');
qa_db_posts_calc_category_path($next, $lastpostid);
$next=1+$lastpostid; foreach ($comments as $comment) {
$done+=count($postids); foreach ($keyuserids as $keyuserid => $dummy)
$continue=true; 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 { if (isset($comment['userid']))
qa_recalc_transition($state, 'dorecalccategories_recount'); $keyuserids[$comment['userid']]=true;
}
}
} }
break;
case 'dorecalccategories_recount': $next=1+$lastquestionid;
$categoryids=qa_db_categories_get_for_recalcs($next, 10); $done+=count($questionids);
$continue=true;
if (count($categoryids)) { } else
$lastcategoryid=max($categoryids); qa_recalc_transition($state, 'dorefillevents_complete');
break;
foreach ($categoryids as $categoryid) case 'dorecalccategories':
qa_db_ifcategory_qcount_update($categoryid); qa_recalc_transition($state, 'dorecalccategories_postcount');
break;
$next=1+$lastcategoryid; case 'dorecalccategories_postcount':
$done+=count($categoryids); qa_db_acount_update();
$continue=true; qa_db_ccount_update();
} else { qa_recalc_transition($state, 'dorecalccategories_postupdate');
qa_recalc_transition($state, 'dorecalccategories_backpaths'); break;
}
break;
case 'dorecalccategories_backpaths': case 'dorecalccategories_postupdate':
$categoryids=qa_db_categories_get_for_recalcs($next, 10); $postids=qa_db_posts_get_for_recategorizing($next, 100);
if (count($categoryids)) { if (count($postids)) {
$lastcategoryid=max($categoryids); $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; $next=1+$lastpostid;
$done+=count($categoryids); $done+=count($postids);
$continue=true; $continue=true;
} else { } else {
qa_recalc_transition($state, 'dorecalccategories_complete'); qa_recalc_transition($state, 'dorecalccategories_recount');
} }
break; break;
case 'dodeletehidden': case 'dorecalccategories_recount':
qa_recalc_transition($state, 'dodeletehidden_comments'); $categoryids=qa_db_categories_get_for_recalcs($next, 10);
break;
case 'dodeletehidden_comments': if (count($categoryids)) {
$posts=qa_db_posts_get_for_deleting('C', $next, 1); $lastcategoryid=max($categoryids);
if (count($posts)) { foreach ($categoryids as $categoryid)
require_once QA_INCLUDE_DIR.'app/posts.php'; 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; case 'dorecalccategories_backpaths':
$done++; $categoryids=qa_db_categories_get_for_recalcs($next, 10);
$continue=true;
} else if (count($categoryids)) {
qa_recalc_transition($state, 'dodeletehidden_answers'); $lastcategoryid=max($categoryids);
break;
case 'dodeletehidden_answers': qa_db_categories_recalc_backpaths($next, $lastcategoryid);
$posts=qa_db_posts_get_for_deleting('A', $next, 1);
if (count($posts)) { $next=1+$lastcategoryid;
require_once QA_INCLUDE_DIR.'app/posts.php'; $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; case 'dodeletehidden_comments':
$done++; $posts=qa_db_posts_get_for_deleting('C', $next, 1);
$continue=true;
} else if (count($posts)) {
qa_recalc_transition($state, 'dodeletehidden_questions'); require_once QA_INCLUDE_DIR.'app/posts.php';
break;
case 'dodeletehidden_questions': $postid=$posts[0];
$posts=qa_db_posts_get_for_deleting('Q', $next, 1);
if (count($posts)) { qa_post_delete($postid);
require_once QA_INCLUDE_DIR.'app/posts.php';
$postid=$posts[0]; $next=1+$postid;
$done++;
$continue=true;
qa_post_delete($postid); } else
qa_recalc_transition($state, 'dodeletehidden_answers');
break;
$next=1+$postid; case 'dodeletehidden_answers':
$done++; $posts=qa_db_posts_get_for_deleting('A', $next, 1);
$continue=true;
} else if (count($posts)) {
qa_recalc_transition($state, 'dodeletehidden_complete'); require_once QA_INCLUDE_DIR.'app/posts.php';
break;
case 'doblobstodisk': $postid=$posts[0];
qa_recalc_transition($state, 'doblobstodisk_move');
break;
case 'doblobstodisk_move': qa_post_delete($postid);
$blob=qa_db_get_next_blob_in_db($next);
if (isset($blob)) { $next=1+$postid;
require_once QA_INCLUDE_DIR.'app/blobs.php'; $done++;
require_once QA_INCLUDE_DIR.'db/blobs.php'; $continue=true;
if (qa_write_blob_file($blob['blobid'], $blob['content'], $blob['format'])) } else
qa_db_blob_set_content($blob['blobid'], null); qa_recalc_transition($state, 'dodeletehidden_questions');
break;
$next=1+$blob['blobid']; case 'dodeletehidden_questions':
$done++; $posts=qa_db_posts_get_for_deleting('Q', $next, 1);
$continue=true;
} else if (count($posts)) {
qa_recalc_transition($state, 'doblobstodisk_complete'); require_once QA_INCLUDE_DIR.'app/posts.php';
break;
case 'doblobstodb': $postid=$posts[0];
qa_recalc_transition($state, 'doblobstodb_move');
break;
case 'doblobstodb_move': qa_post_delete($postid);
$blob=qa_db_get_next_blob_on_disk($next);
if (isset($blob)) { $next=1+$postid;
require_once QA_INCLUDE_DIR.'app/blobs.php'; $done++;
require_once QA_INCLUDE_DIR.'db/blobs.php'; $continue=true;
$content=qa_read_blob_file($blob['blobid'], $blob['format']); } else
qa_db_blob_set_content($blob['blobid'], $content); qa_recalc_transition($state, 'dodeletehidden_complete');
qa_delete_blob_file($blob['blobid'], $blob['format']); break;
$next=1+$blob['blobid']; case 'doblobstodisk':
$done++; qa_recalc_transition($state, 'doblobstodisk_move');
$continue=true; break;
} else case 'doblobstodisk_move':
qa_recalc_transition($state, 'doblobstodb_complete'); $blob=qa_db_get_next_blob_in_db($next);
break;
default: if (isset($blob)) {
$state=''; require_once QA_INCLUDE_DIR.'app/blobs.php';
break; require_once QA_INCLUDE_DIR.'db/blobs.php';
}
if ($continue) if (qa_write_blob_file($blob['blobid'], $blob['content'], $blob['format']))
$state=$operation."\t".$length."\t".$next."\t".$done; 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) case 'doblobstodb':
/* qa_recalc_transition($state, 'doblobstodb_move');
Change the $state to represent the beginning of a new $operation break;
*/
{
$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; 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) $content=qa_read_blob_file($blob['blobid'], $blob['format']);
/* qa_db_blob_set_content($blob['blobid'], $content);
Return how many steps there will be in recalculation $operation qa_delete_blob_file($blob['blobid'], $blob['format']);
*/
{
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;
}
$next=1+$blob['blobid'];
$done++;
$continue=true;
/** } else
* Return the translated language ID string replacing the progress and total in it. qa_recalc_transition($state, 'doblobstodb_complete');
* @access private break;
* @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)
));
}
default:
$state='';
break;
}
function qa_recalc_get_message($state) if ($continue)
/* $state=$operation."\t".$length."\t".$next."\t".$done;
Return a string which gives a user-viewable version of $state
*/
{
require_once QA_INCLUDE_DIR . 'app/format.php';
@list($operation, $length, $next, $done) = explode("\t", $state); return $continue && ($done<$length);
}
$done = (int) $done;
$length = (int) $length;
/**
switch ($operation) { * Change the $state to represent the beginning of a new $operation
case 'doreindexcontent_postcount': */
case 'dorecountposts_postcount': function qa_recalc_transition(&$state, $operation)
case 'dorecalccategories_postcount': {
case 'dorefillevents_qcount': $length=qa_recalc_stage_length($operation);
$message = qa_lang('admin/recalc_posts_count'); $next=(QA_FINAL_EXTERNAL_USERS && ($operation=='dorecalcpoints_recalc')) ? '' : 0;
break; $done=0;
case 'doreindexcontent_pagereindex': $state=$operation."\t".$length."\t".$next."\t".$done;
$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); * Return how many steps there will be in recalculation $operation
break; */
function qa_recalc_stage_length($operation)
case 'doreindexposts_complete': {
$message = qa_lang('admin/reindex_posts_complete'); switch ($operation) {
break; case 'doreindexcontent_pagereindex':
$length=qa_db_count_pages();
case 'doreindexposts_wordcount': break;
$message = qa_recalc_progress_lang('admin/reindex_posts_wordcounted', $done, $length);
break; case 'doreindexcontent_postreindex':
$length=qa_opt('cache_qcount')+qa_opt('cache_acount')+qa_opt('cache_ccount');
case 'dorecountposts_votecount': break;
$message = qa_recalc_progress_lang('admin/recount_posts_votes_recounted', $done, $length);
break; case 'doreindexposts_wordcount':
$length=qa_db_count_words();
case 'dorecountposts_acount': break;
$message = qa_recalc_progress_lang('admin/recount_posts_as_recounted', $done, $length);
break; case 'dorecalcpoints_recalc':
$length=qa_opt('cache_userpointscount');
case 'dorecountposts_complete': break;
$message = qa_lang('admin/recount_posts_complete');
break; case 'dorecountposts_votecount':
case 'dorecountposts_acount':
case 'dorecalcpoints_usercount': case 'dorecalccategories_postupdate':
$message = qa_lang('admin/recalc_points_usercount'); $length=qa_db_count_posts();
break; break;
case 'dorecalcpoints_recalc': case 'dorefillevents_refill':
$message = qa_recalc_progress_lang('admin/recalc_points_recalced', $done, $length); $length=qa_opt('cache_qcount')+qa_db_count_posts('Q_HIDDEN');
break; break;
case 'dorecalcpoints_complete': case 'dorecalccategories_recount':
$message = qa_lang('admin/recalc_points_complete'); case 'dorecalccategories_backpaths':
break; $length=qa_db_count_categories();
break;
case 'dorefillevents_refill':
$message = qa_recalc_progress_lang('admin/refill_events_refilled', $done, $length); case 'dodeletehidden_comments':
break; $length=count(qa_db_posts_get_for_deleting('C'));
break;
case 'dorefillevents_complete':
$message = qa_lang('admin/refill_events_complete'); case 'dodeletehidden_answers':
break; $length=count(qa_db_posts_get_for_deleting('A'));
break;
case 'dorecalccategories_postupdate':
$message = qa_recalc_progress_lang('admin/recalc_categories_updated', $done, $length); case 'dodeletehidden_questions':
break; $length=count(qa_db_posts_get_for_deleting('Q'));
break;
case 'dorecalccategories_recount':
$message = qa_recalc_progress_lang('admin/recalc_categories_recounting', $done, $length); case 'doblobstodisk_move':
break; $length=qa_db_count_blobs_in_db();
break;
case 'dorecalccategories_backpaths':
$message = qa_recalc_progress_lang('admin/recalc_categories_backpaths', $done, $length); case 'doblobstodb_move':
break; $length=qa_db_count_blobs_on_disk();
break;
case 'dorecalccategories_complete':
$message = qa_lang('admin/recalc_categories_complete'); default:
break; $length=0;
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 $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;
}
/* return $message;
Omit PHP closing tag to help avoid accidental output }
*/
\ No newline at end of file
...@@ -20,1321 +20,1394 @@ ...@@ -20,1321 +20,1394 @@
More about this license: http://www.question2answer.org/license.php 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 if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
header('Location: ../'); header('Location: ../');
exit; 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); // Access functions for user information
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); * 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()
if (QA_FINAL_EXTERNAL_USERS) { {
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')) { if (isset($user)) {
require_once QA_INCLUDE_DIR.'util/external-users-wp.php'; $user['flags'] = isset($user['blocked']) ? QA_USER_FLAGS_USER_BLOCKED : 0;
} $qa_cached_logged_in_user = $user;
elseif (defined('QA_FINAL_JOOMLA_INTEGRATE_PATH')) { } else
require_once QA_INCLUDE_DIR.'util/external-users-joomla.php'; $qa_cached_logged_in_user = false;
}
else {
require_once QA_EXTERNAL_DIR.'qa-external-users.php';
} }
// 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)) { return isset($user[$field]) ? $user[$field] : null;
$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 @$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() $qa_cached_logged_in_points = qa_db_select_with_pending(qa_db_user_points_selectspec(qa_get_logged_in_userid(), true));
/*
Return the userid of the currently logged in user, or null if none
*/
{
return qa_get_logged_in_user_field('userid');
} }
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) @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
Return HTML to display for the avatar of $userid, constrained to $size pixels, with optional $padding to that size @ini_set('session.cookie_domain', QA_COOKIE_DOMAIN);
*/
{
if (function_exists('qa_avatar_html_from_userid'))
return qa_avatar_html_from_userid($userid, $size, $padding);
else
return null;
}
if (!isset($_SESSION))
session_start();
}
} else {
function qa_start_session() /**
/* * Returns a suffix to be used for names of session variables to prevent them being shared between multiple Q2A sites on the same server
Open a PHP session if one isn't opened already */
*/ function qa_session_var_suffix()
{ {
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } 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 global $qa_session_suffix;
@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)) if (!$qa_session_suffix) {
session_start(); $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;
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;
}
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
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
{ */
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } 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).
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).
Pass true if user checked 'Remember me' (either now or previously, as learned from cookie). * @param $handle
*/ * @param $sessioncode
{ * @param $remember
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } * @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 // 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); 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
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); } 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
Set the session variables to indicate that $userid is logged in from $source * @param $userid
*/ * @param $source
{ * @return mixed
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } */
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_userid_' . $suffix] = $userid;
$_SESSION['qa_session_source_'.$suffix]=$source; $_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
// 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
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); } 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_userid_' . $suffix]);
unset($_SESSION['qa_session_source_'.$suffix]); unset($_SESSION['qa_session_source_' . $suffix]);
unset($_SESSION['qa_session_verify_'.$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.
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.
$remember states if 'Remember me' was checked in the login form. * @param $userid
*/ * @param string $handle
{ * @param bool $remember
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } * @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)) { if (isset($userid)) {
qa_set_session_user($userid, $source); qa_set_session_user($userid, $source);
// PHP sessions time out too quickly on the server side, so we also set a cookie as backup. // 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 // 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. // 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 // 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 // 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'])) { if (empty($userinfo['sessioncode']) || ($source !== $userinfo['sessionsource'])) {
$sessioncode=qa_db_user_rand_sessioncode(); $sessioncode = qa_db_user_rand_sessioncode();
qa_db_user_set($userid, 'sessioncode', $sessioncode); qa_db_user_set($userid, 'sessioncode', $sessioncode);
qa_db_user_set($userid, 'sessionsource', $source); qa_db_user_set($userid, 'sessionsource', $source);
} else } else
$sessioncode=$userinfo['sessioncode']; $sessioncode = $userinfo['sessioncode'];
qa_db_user_logged_in($userid, qa_remote_ip_address()); qa_db_user_logged_in($userid, qa_remote_ip_address());
qa_set_session_cookie($handle, $sessioncode, $remember); 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 { } else {
$olduserid=qa_get_logged_in_userid(); $olduserid = qa_get_logged_in_userid();
$oldhandle=qa_get_logged_in_handle(); $oldhandle = qa_get_logged_in_handle();
qa_clear_session_cookie(); qa_clear_session_cookie();
qa_clear_session_user(); 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
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
A new user is created based on $fields if it's a new combination of $source and $identifier * @param $source
*/ * @param $identifier
{ * @param $fields
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } * @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); $users = qa_db_user_login_find($source, $identifier);
$countusers=count($users); $countusers = count($users);
if ($countusers>1) if ($countusers > 1)
qa_fatal_error('External login mapped to more than one user'); // should never happen qa_fatal_error('External login mapped to more than one user'); // should never happen
if ($countusers) // user exists so log them in if ($countusers) // user exists so log them in
qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source); qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
else { // create and log in user else { // create and log in user
require_once QA_INCLUDE_DIR.'app/users-edit.php'; 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) { if (count($users) == 1) {
qa_db_user_login_sync(false); qa_db_user_login_sync(false);
qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source); qa_set_logged_in_user($users[0]['userid'], $users[0]['handle'], false, $source);
} else { } else {
$handle=qa_handle_make_valid(@$fields['handle']); $handle = qa_handle_make_valid(@$fields['handle']);
if (strlen(@$fields['email'])) { // remove email address if it will cause a duplicate if (strlen(@$fields['email'])) { // remove email address if it will cause a duplicate
$emailusers=qa_db_user_find_by_email($fields['email']); $emailusers = qa_db_user_find_by_email($fields['email']);
if (count($emailusers)) { if (count($emailusers)) {
qa_redirect('login', array('e' => $fields['email'], 'ee' => '1')); qa_redirect('login', array('e' => $fields['email'], 'ee' => '1'));
unset($fields['email']); unset($fields['email']);
unset($fields['confirmed']); 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 qa_db_user_login_add($userid, $source, $identifier);
if (!hash_equals(qa_session_verify_code($sessionuserid), @$_SESSION['qa_session_verify_'.$suffix])) qa_db_user_login_sync(false);
qa_clear_session_user();
if (!empty($_COOKIE['qa_session'])) { $profilefields = array('name', 'location', 'website', 'about');
@list($handle, $sessioncode, $remember)=explode('/', $_COOKIE['qa_session']);
if ($remember) foreach ($profilefields as $fieldname) {
qa_set_session_cookie($handle, $sessioncode, $remember); // extend 'remember me' cookies each time if (strlen(@$fields[$fieldname]))
qa_db_user_profile_set($userid, $fieldname, $fields[$fieldname]);
$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
}
} }
$qa_logged_in_userid_checked=true; if (strlen(@$fields['avatar']))
} qa_set_user_avatar($userid, $fields['avatar']);
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 (!isset($qa_cached_logged_in_user)) { qa_set_logged_in_user($userid, $handle, false, $source);
$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;
}
}
} }
return $qa_cached_logged_in_user;
} }
}
/** /**
* Return $field of the currently logged in user * Return the userid of the currently logged in user, or null if none logged in
*/ */
function qa_get_logged_in_user_field($field) function qa_get_logged_in_userid()
{ {
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$usercache = qa_get_logged_in_user_cache();
return isset($usercache[$field]) ? $usercache[$field] : null; global $qa_logged_in_userid_checked;
}
$suffix = qa_session_var_suffix();
function qa_get_logged_in_points() 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 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); }
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() if (!empty($_COOKIE['qa_session'])) {
/* @list($handle, $sessioncode, $remember) = explode('/', $_COOKIE['qa_session']);
Return column type to use for users (if not using single sign-on integration)
*/
{
return 'INT UNSIGNED';
}
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
* 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); }
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)) { $userinfo = qa_db_single_select(qa_db_user_account_selectspec($handle, false)); // don't get any pending
return null;
}
$params = array('qa_blobid' => $blobId); if (strtolower(trim($userinfo['sessioncode'])) == strtolower($sessioncode))
if (isset($size)) { qa_set_session_user($userinfo['userid'], $userinfo['sessionsource']);
$params['qa_size'] = $size; else
qa_clear_session_cookie(); // if cookie not valid, remove it to save future checks
}
} }
$rootUrl = $absolute ? qa_opt('site_url') : null; $qa_logged_in_userid_checked = true;
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;
} }
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();
/** if (isset($userid))
* Return where the avatar will be fetched from for the given user flags. The possible return values are return @$_SESSION['qa_session_source_' . $suffix];
* '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;
}
}
/** /**
* Return the avatar URL, either Gravatar or from a blob ID, constrained to $size pixels. * Return array of information about the currently logged in user, cache to ensure only one call to external code
* */
* @param int $flags The user's flags function qa_get_logged_in_user_cache()
* @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 global $qa_cached_logged_in_user;
* @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;
}
}
if (!isset($qa_cached_logged_in_user)) {
$userid = qa_get_logged_in_userid();
/** if (isset($userid)) {
* Return HTML to display for the user's avatar, constrained to $size pixels, with optional $padding to that size 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));
* @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;
}
$avatarSource = qa_get_user_avatar_source($flags, $email, $blobId); if (!isset($qa_cached_logged_in_user)) {
// the user can no longer be found (should only apply to deleted users)
switch ($avatarSource) { qa_clear_session_user();
case 'gravatar': qa_redirect(''); // implicit exit;
$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 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'];
} }
return $qa_cached_logged_in_user;
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);
} }
function qa_get_logged_in_handle() /**
/* * Return $field of the currently logged in user
Return displayable handle/username of currently logged in user, or null if none * @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 isset($usercache[$field]) ? $usercache[$field] : null;
/*
Return email of currently logged in user, or null if none
*/
{
return qa_get_logged_in_user_field('email');
} }
function qa_get_logged_in_level() /**
/* * Return the number of points of the currently logged in user, or null if none is logged in
Return level of currently logged in user, or null if none */
*/ 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 column type to use for users (if not using single sign-on integration)
Return flags (see QA_USER_FLAGS_*) of currently logged in user, or null if none */
*/ function qa_get_mysql_user_column_type()
{ {
if (QA_FINAL_EXTERNAL_USERS) return 'INT UNSIGNED';
return qa_get_logged_in_user_field('blocked') ? QA_USER_FLAGS_USER_BLOCKED : 0;
else
return qa_get_logged_in_user_field('flags');
} }
function qa_get_logged_in_levels() /**
/* * Return the URL to the $blobId with a stored size of $width and $height.
Return an array of all the specific (e.g. per category) level privileges for the logged in user, retrieving from the database if necessary * 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'; if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
return qa_db_get_pending_result('userlevels', qa_db_user_levels_selectspec(qa_get_logged_in_userid(), true));
}
require_once QA_INCLUDE_DIR . 'util/image.php';
function qa_userids_to_handles($userids) if (strlen($blobId) == 0 || (isset($size) && (int)$size <= 0)) {
/* return null;
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);
else { $params = array('qa_blobid' => $blobId);
require_once QA_INCLUDE_DIR.'db/users.php'; if (isset($size)) {
$rawuseridhandles=qa_db_user_get_userid_handles($userids); $params['qa_size'] = $size;
} }
$gotuseridhandles=array(); $rootUrl = $absolute ? qa_opt('site_url') : null;
foreach ($userids as $userid)
$gotuseridhandles[$userid]=@$rawuseridhandles[$userid];
return $gotuseridhandles; return qa_path('image', $params, $rootUrl, QA_URL_FORMAT_PARAMS);
} }
function qa_userid_to_handle($userid) /**
/* * Get HTML to display a username, linked to their user page.
Return an string mapping the received userid to that user's handle (public username), or to null if not found *
*/ * @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)); if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
return empty($handles) ? null : $handles[$userid];
}
if (!strlen($handle))
return '';
function qa_handles_to_userids($handles, $exactonly=false) $url = qa_path_html('user/' . $handle);
/* $favclass = $favorited ? ' qa-user-favorited' : '';
Return an array mapping each handle in $handles the user's userid, or null if not found. If $exactonly is true then $mfAttr = $microdata ? ' itemprop="name"' : '';
$handles must have the correct case and accents. Otherwise, handles are case- and accent-insensitive, and the keys $mfPrefix = $microdata ? '<span itemprop="author" itemscope itemtype="http://schema.org/Person">' : '';
of the returned array will match the $handles provided, not necessary those in the DB. $mfSuffix = $microdata ? '</span>' : '';
*/
{
require_once QA_INCLUDE_DIR.'util/string.php';
if (QA_FINAL_EXTERNAL_USERS) return $mfPrefix . '<a href="' . $url . '" class="qa-user-link' . $favclass . '"' . $mfAttr . '>' . qa_html($handle) . '</a>' . $mfSuffix;
$rawhandleuserids=qa_get_userids_from_public($handles); }
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 $link = 'https://www.gravatar.com/avatar/%s';
foreach ($handles as $handle)
$gothandleuserids[$handle]=@$rawhandleuserids[$handle];
} else { // normalize to lowercase without accents, and then find matches $params = array(md5(strtolower(trim($email))));
$normhandleuserids=array();
foreach ($rawhandleuserids as $handle => $userid)
$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))]=$userid;
foreach ($handles as $handle) $size = (int)$size;
$gothandleuserids[$handle]=@$normhandleuserids[qa_string_remove_accents(qa_strtolower($handle))]; if ($size > 0) {
$link .= '?s=%d';
$params[] = $size;
} }
return $gothandleuserids; return vsprintf($link, $params);
} }
function qa_handle_to_userid($handle) /**
/* * Return where the avatar will be fetched from for the given user flags. The possible return values are
Return the userid corresponding to $handle (not case- or accent-sensitive) * '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) if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
$handleuserids=qa_get_userids_from_public(array($handle));
else { if (qa_opt('avatar_allow_gravatar') && (($flags & QA_USER_FLAGS_SHOW_GRAVATAR) > 0) && isset($email)) {
require_once QA_INCLUDE_DIR.'db/users.php'; return 'gravatar';
$handleuserids=qa_db_user_get_handle_userids(array($handle)); } 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 avatar URL, either Gravatar or from a blob ID, constrained to $size pixels.
Return the level of the logged in user for a post with $categoryids (expressing the full hierarchy to the final category) *
*/ * @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); } 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) * Return HTML to display for the user's avatar, constrained to $size pixels, with optional $padding to that size
if ($userlevel['entitytype']==QA_ENTITY_CATEGORY) *
$categorylevels[$userlevel['entityid']]=$userlevel['level']; * @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) $avatarSource = qa_get_user_avatar_source($flags, $email, $blobId);
$level=max($level, @$categorylevels[$categoryid]);
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 email address for user $userid (if not using single sign-on integration)
Return the level of the logged in user for $post, as retrieved from the database * @param $userid
*/ * @return
*/
function qa_get_user_email($userid)
{ {
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } $userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true));
if (strlen(@$post['categoryids']))
return qa_user_level_for_categories(explode(',', $post['categoryids']));
return null; return $userinfo['email'];
} }
function qa_user_level_maximum() /**
/* * Called after a database write $action performed by a user $userid
Return the maximum possible level of the logged in user in any context (i.e. for any category) * @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); } 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(); qa_db_user_written($userid, qa_remote_ip_address());
foreach ($userlevels as $userlevel)
$level=max($level, $userlevel['level']);
return $level;
} }
function qa_user_post_permit_error($permitoption, $post, $limitaction=null, $checkblocks=true) /**
/* * Return textual representation of the user $level
Check whether the logged in user has permission to perform $permitoption on post $post (from the database) * @param $level
Other parameters and the return value are as for qa_user_permit_error(...) * @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) return qa_lang($string);
/*
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);
} }
/** /**
* Check whether the logged in user has permission to perform an action. * Return an array of links to login, register, email confirm and logout pages (if not using single sign-on integration)
* * @param $rooturl
* @param string $permitoption The permission to check (if null, this simply checks whether the user is blocked). * @param $tourl
* @param string $limitaction Constant from qa-app-limits.php to check against user or IP rate limits. * @return array
* @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) function qa_get_login_links($rooturl, $tourl)
{ {
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } return array(
'login' => qa_path('login', isset($tourl) ? array('to' => $tourl) : null, $rooturl),
require_once QA_INCLUDE_DIR.'app/limits.php'; '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)) } // end of: if (QA_FINAL_EXTERNAL_USERS) { ... } else { ... }
$userfields = qa_get_logged_in_user_cache();
/**
* 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)) $gothandleuserids = array();
$userlevel = isset($userfields['level']) ? $userfields['level'] : null;
$flags = isset($userfields['flags']) ? $userfields['flags'] : null; if ($exactonly) { // only take the exact matches
if (!$checkblocks) foreach ($handles as $handle)
$flags &= ~QA_USER_FLAGS_USER_BLOCKED; $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()) foreach ($handles as $handle)
$error = 'ipblock'; $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')) return $gothandleuserids;
$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) * Return the userid corresponding to $handle (not case- or accent-sensitive)
$error = 'limit'; * @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
/** return null;
* 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) * 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')) { require_once QA_INCLUDE_DIR . 'app/updates.php';
$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);
}
$level = qa_get_logged_in_level();
/** if (count($categoryids)) {
* Check whether user can reach the permission level. Result as for qa_user_permit_error(...). $userlevels = qa_get_logged_in_levels();
*
* @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) $categorylevels = array(); // create a map
return 'login'; foreach ($userlevels as $userlevel) {
if ($userlevel['entitytype'] == QA_ENTITY_CATEGORY)
$levelError = $categorylevels[$userlevel['entityid']] = $userlevel['level'];
($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';
} }
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)) * Return the level of the logged in user for $post, as retrieved from the database
$userlevel=qa_get_logged_in_level(); * @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 if (strlen(@$post['categoryids']))
$userid=qa_get_logged_in_userid(); return qa_user_level_for_categories(explode(',', $post['categoryids']));
if (qa_opt('captcha_on_anon_post') && !isset($userid)) return null;
$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 $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) $level = qa_get_logged_in_level();
/*
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); }
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 $error;
/* }
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). /**
* Check whether user can perform $permitoption. Result as for qa_user_permit_error(...).
Possible results: *
'login' => moderation required because the user is not logged in * @param string $permitoption Permission option name (from database) for action.
'approve' => moderation required because the user has not been approved * @param int $userid ID of user (null for no user).
'confirm' => moderation required because the user has not confirmed their email address * @param int $userlevel Level to check against.
'points' => moderation required because the user has insufficient points * @param int $userflags Flags for this user.
false => moderation is not required * @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.
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } */
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; return qa_permit_value_error($permit, $userid, $userlevel, $userflags);
if (!isset($userlevel)) }
$userlevel=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 (!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 ( if (
($userlevel < QA_USER_LEVEL_EXPERT) && // experts and above aren't moderated !QA_FINAL_EXTERNAL_USERS && // not currently supported by single sign-on integration
qa_user_permit_error('permit_moderate') // if the user can approve posts, no point in moderating theirs 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
$userid=qa_get_logged_in_userid(); !$confirmed // actual confirmation
)
if (isset($userid)) { return 'confirm';
if (qa_opt('moderate_users') && qa_opt('moderate_unapproved') && ($userlevel<QA_USER_LEVEL_APPROVED)) } elseif ($permit >= QA_PERMIT_APPROVED) {
$reason='approve'; if (
elseif (qa_opt('confirm_user_emails') && qa_opt('moderate_unconfirmed') && !(qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED) ) qa_opt('moderate_users') && // if this option off, we can't ask it of the user
$reason='confirm'; $userlevel < QA_USER_LEVEL_APPROVED // user has not been approved
elseif (qa_opt('moderate_by_points') && (qa_get_logged_in_points() < qa_opt('moderate_points_limit'))) )
$reason='points'; return 'approve';
} elseif (qa_opt('moderate_anon_post'))
$reason='login';
}
return $reason;
} }
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 $reason;
/* }
Return the label to display for $userfield as retrieved from the database, using default if no name set
*/
{ /**
if (isset($userfield['content'])) * Return whether a captcha should be presented to the logged in user for writing posts. You can pass in a
return $userfield['content']; * QA_USER_LEVEL_* constant in $userlevel to consider the user at a different level to usual.
* @param $userlevel
else { * @return bool|mixed
$defaultlabels=array( */
'name' => 'users/full_name', function qa_user_use_captcha($userlevel = null)
'about' => 'users/about', {
'location' => 'users/location', if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
'website' => 'users/website',
); return qa_user_captcha_reason($userlevel) != false;
}
if (isset($defaultlabels[$userfield['title']]))
return qa_lang($defaultlabels[$userfield['title']]);
} /**
* 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) { global $qa_form_key_cookie_set;
$qa_form_key_cookie_set=true;
if (strlen(@$_COOKIE['qa_key'])!=QA_FORM_KEY_LENGTH) { if ((!qa_is_logged_in()) && !@$qa_form_key_cookie_set) {
require_once QA_INCLUDE_DIR.'util/string.php'; $qa_form_key_cookie_set = true;
$_COOKIE['qa_key']=qa_random_alphanum(QA_FORM_KEY_LENGTH);
}
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()) 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
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
} }
}
function qa_get_form_security_code($action) /**
/* * Return the form security (anti-CSRF protection) hash for an $action (any string), that can be performed within
Return the full form security (anti-CSRF protection) code for an $action (any string) performed within * QA_FORM_EXPIRY_SECS of $timestamp (in unix seconds) by the current user.
QA_FORM_EXPIRY_SECS of now by the current user. * @param $action
*/ * @param $timestamp
{ * @return mixed|string
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } */
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) qa_set_form_security_key();
/*
Return whether $value matches the expected form security (anti-CSRF protection) code for $action (any string) and $timestamp = qa_opt('db_time');
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); }
$reportproblems=array(); return (int)qa_is_logged_in() . '-' . $timestamp . '-' . qa_calc_form_security_hash($action, $timestamp);
$silentproblems=array(); }
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 { $reportproblems = array();
$parts=explode('-', $value); $silentproblems = array();
if (count($parts)==3) { if (!isset($value)) {
$loggedin=$parts[0]; $silentproblems[] = 'code missing';
$timestamp=$parts[1];
$hash=$parts[2];
$timenow=qa_opt('db_time');
if ($timestamp>$timenow) } elseif (!strlen($value)) {
$reportproblems[]='time '.($timestamp-$timenow).'s in future'; $silentproblems[] = 'code empty';
elseif ($timestamp<($timenow-QA_FORM_EXPIRY_SECS))
$silentproblems[]='timeout after '.($timenow-$timestamp).'s';
if (qa_is_logged_in()) { } else {
if (!$loggedin) $parts = explode('-', $value);
$silentproblems[]='now logged in';
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 { } else {
if ($loggedin) $key = @$_COOKIE['qa_key'];
$silentproblems[]='now logged out';
if (!isset($key)) {
else { $silentproblems[] = 'key cookie missing';
$key=@$_COOKIE['qa_key']; } elseif (!strlen($key)) {
$silentproblems[] = 'key cookie empty';
if (!isset($key)) } elseif (strlen($key) != QA_FORM_KEY_LENGTH) {
$silentproblems[]='key cookie missing'; $reportproblems[] = 'key cookie ' . $key . ' invalid';
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 (empty($silentproblems) && empty($reportproblems)) {
if (!hash_equals(strtolower(qa_calc_form_security_hash($action, $timestamp)), strtolower($hash))) if (!hash_equals(strtolower(qa_calc_form_security_hash($action, $timestamp)), strtolower($hash))) {
$reportproblems[]='code mismatch'; $reportproblems[] = 'code mismatch';
}
}
} else } else {
$reportproblems[]='code '.$value.' malformed'; $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']
);
}
/* return (empty($silentproblems) && empty($reportproblems));
Omit PHP closing tag to help avoid accidental output }
*/
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