Commit 842abb91 by Scott

Merge branch 'bugfix' into dev

parents f6276f4b d757fa58
1.8.1
\ No newline at end of file
1.8.2
\ No newline at end of file
......@@ -19,6 +19,7 @@
More about this license: http://www.question2answer.org/license.php
*/
require_once QA_INCLUDE_DIR . 'app/posts.php';
require_once QA_INCLUDE_DIR . 'app/users.php';
require_once QA_INCLUDE_DIR . 'app/limits.php';
require_once QA_INCLUDE_DIR . 'db/selects.php';
......@@ -37,7 +38,7 @@ list($question, $childposts) = qa_db_select_with_pending(
// Check if the question exists, is not closed, and whether the user has permission to do this
if (@$question['basetype'] == 'Q' && !isset($question['closedbyid']) && !qa_user_post_permit_error('permit_post_a', $question, QA_LIMIT_ANSWERS)) {
if (@$question['basetype'] == 'Q' && !qa_post_is_closed($question) && !qa_user_post_permit_error('permit_post_a', $question, QA_LIMIT_ANSWERS)) {
require_once QA_INCLUDE_DIR . 'app/captcha.php';
require_once QA_INCLUDE_DIR . 'app/format.php';
require_once QA_INCLUDE_DIR . 'app/post-create.php';
......
......@@ -35,8 +35,8 @@ if (qa_get_logged_in_level() < QA_USER_LEVEL_ADMIN) {
}
$uri = qa_post_text('uri');
$version = qa_post_text('version');
$isCore = qa_post_text('isCore') === 'true';
$currentVersion = qa_post_text('version');
$isCore = qa_post_text('isCore') === "true";
if ($isCore) {
$contents = qa_retrieve_url($uri);
......@@ -58,7 +58,7 @@ if ($isCore) {
$metadata = $metadataUtil->fetchFromUrl($uri);
if (strlen(@$metadata['version']) > 0) {
if (strcmp($metadata['version'], $version)) {
if (version_compare($currentVersion, $metadata['version']) < 0) {
if (qa_qa_version_below(@$metadata['min_q2a'])) {
$versionResponse = strtr(qa_lang_html('admin/version_requires_q2a'), array(
'^1' => qa_html('v' . $metadata['version']),
......
......@@ -43,7 +43,7 @@ function qa_suspend_notifications($suspend = true)
/**
* Send email to person with $userid and/or $email and/or $handle (null/invalid values are ignored or retrieved from
* user database as appropriate). Email uses $subject and $body, after substituting each key in $subs with its
* corresponding value, plus applying some standard substitutions such as ^site_title, ^handle and ^email.
* corresponding value, plus applying some standard substitutions such as ^site_title, ^site_url, ^handle and ^email.
* @param $userid
* @param $email
* @param $handle
......@@ -100,6 +100,7 @@ function qa_send_notification($userid, $email, $handle, $subject, $body, $subs,
if (isset($email) && qa_email_validate($email)) {
$subs['^site_title'] = qa_opt('site_title');
$subs['^site_url'] = qa_opt('site_url');
$subs['^handle'] = $handle;
$subs['^email'] = $email;
$subs['^open'] = "\n";
......
......@@ -293,6 +293,7 @@ function qa_post_html_fields($post, $userid, $cookieid, $usershtml, $dummy, $opt
if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
require_once QA_INCLUDE_DIR . 'app/updates.php';
require_once QA_INCLUDE_DIR . 'app/posts.php';
if (isset($options['blockwordspreg']))
require_once QA_INCLUDE_DIR . 'util/string.php';
......@@ -320,15 +321,16 @@ function qa_post_html_fields($post, $userid, $cookieid, $usershtml, $dummy, $opt
$fields['tags'] = 'id="' . qa_html($elementid) . '"';
$fields['classes'] = ($isquestion && $favoritedview && @$post['userfavoriteq']) ? 'qa-q-favorited' : '';
if ($isquestion && isset($post['closedbyid']))
if ($isquestion && qa_post_is_closed($post)) {
$fields['classes'] = ltrim($fields['classes'] . ' qa-q-closed');
}
if ($microdata) {
if ($isanswer) {
$fields['tags'] .= ' itemprop="suggestedAnswer' . ($isselected ? ' acceptedAnswer' : '') . '" itemscope itemtype="http://schema.org/Answer"';
$fields['tags'] .= ' itemprop="suggestedAnswer' . ($isselected ? ' acceptedAnswer' : '') . '" itemscope itemtype="https://schema.org/Answer"';
}
if ($iscomment) {
$fields['tags'] .= ' itemscope itemtype="http://schema.org/Comment"';
$fields['tags'] .= ' itemscope itemtype="https://schema.org/Comment"';
}
}
......@@ -572,8 +574,12 @@ function qa_post_html_fields($post, $userid, $cookieid, $usershtml, $dummy, $opt
$fields['what'] = qa_lang_html($isquestion ? 'main/asked' : ($isanswer ? 'main/answered' : 'main/commented'));
if (@$options['whatlink'] && strlen(@$options['q_request'])) {
$fields['what_url'] = ($post['basetype'] == 'Q') ? qa_path_html($options['q_request'])
$fields['what_url'] = $post['basetype'] == 'Q'
? qa_path_html($options['q_request'])
: qa_path_html($options['q_request'], array('show' => $postid), null, null, qa_anchor($post['basetype'], $postid));
if ($microdata) {
$fields['what_url_tags'] = ' itemprop="url"';
}
}
}
......@@ -617,7 +623,7 @@ function qa_post_html_fields($post, $userid, $cookieid, $usershtml, $dummy, $opt
( // otherwise check if one of these conditions is fulfilled...
(!isset($post['created'])) || // ... we didn't show the created time (should never happen in practice)
($post['hidden'] && ($post['updatetype'] == QA_UPDATE_VISIBLE)) || // ... the post was hidden as the last action
(isset($post['closedbyid']) && ($post['updatetype'] == QA_UPDATE_CLOSED)) || // ... the post was closed as the last action
(qa_post_is_closed($post) && $post['updatetype'] == QA_UPDATE_CLOSED) || // ... the post was closed as the last action
(abs($post['updated'] - $post['created']) > 300) || // ... or over 5 minutes passed between create and update times
($post['lastuserid'] != $post['userid']) // ... or it was updated by a different user
)
......@@ -637,7 +643,7 @@ function qa_post_html_fields($post, $userid, $cookieid, $usershtml, $dummy, $opt
break;
case QA_UPDATE_CLOSED:
$langstring = isset($post['closedbyid']) ? 'main/closed' : 'main/reopened';
$langstring = qa_post_is_closed($post) ? 'main/closed' : 'main/reopened';
break;
case QA_UPDATE_TAGS:
......@@ -737,7 +743,7 @@ function qa_message_html_fields($message, $options = array())
* @param int $postuserid The post user's ID.
* @param array $usershtml Array of HTML representing usernames.
* @param string $ip The post user's IP.
* @param bool|string $microdata Whether to include microdata (no longer used).
* @param bool|string $microdata Whether to include microdata.
* @param string $name The author's username.
* @return array The HTML.
*/
......@@ -757,7 +763,7 @@ function qa_who_to_html($isbyuser, $postuserid, $usershtml, $ip = null, $microda
if ($microdata) {
// duplicate HTML from qa_get_one_user_html()
$whohtml = '<span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">' . $whohtml . '</span></span>';
$whohtml = '<span itemprop="author" itemscope itemtype="https://schema.org/Person"><span itemprop="name">' . $whohtml . '</span></span>';
}
if (isset($ip))
......@@ -838,10 +844,11 @@ function qa_other_to_q_html_fields($question, $userid, $cookieid, $usershtml, $d
break;
case 'Q-' . QA_UPDATE_CLOSED:
$isClosed = qa_post_is_closed($question);
if (@$question['opersonal'])
$langstring = isset($question['closedbyid']) ? 'misc/your_q_closed' : 'misc/your_q_reopened';
$langstring = $isClosed ? 'misc/your_q_closed' : 'misc/your_q_reopened';
else
$langstring = isset($question['closedbyid']) ? 'main/closed' : 'main/reopened';
$langstring = $isClosed ? 'main/closed' : 'main/reopened';
break;
case 'Q-' . QA_UPDATE_TAGS:
......@@ -2355,6 +2362,7 @@ function qa_favorite_form($entitytype, $entityid, $favorite, $title)
* Format a number using the decimal point and thousand separator specified in the language files.
* If the number is compacted it is turned into a string such as 1.3k or 2.5m.
*
* @since 1.8.0
* @param integer $number Number to be formatted
* @param integer $decimals Amount of decimals to use (ignored if number gets shortened)
* @param bool $compact Whether the number can be shown as compact or not
......
......@@ -592,7 +592,6 @@ function qa_post_html_defaults($basetype, $full = false)
'blockwordspreg' => qa_get_block_words_preg(),
'showurllinks' => qa_opt('show_url_links'),
'linksnewwindow' => qa_opt('links_in_new_window'),
'microformats' => $full,
'fulldatedays' => qa_opt('show_full_date_days'),
);
}
......
......@@ -155,7 +155,9 @@ function qa_question_set_selchildid($userid, $handle, $cookieid, $oldquestion, $
{
$oldselchildid = $oldquestion['selchildid'];
qa_db_post_set_selchildid($oldquestion['postid'], isset($selchildid) ? $selchildid : null, $userid, qa_remote_ip_address());
$lastip = qa_remote_ip_address();
qa_db_post_set_selchildid($oldquestion['postid'], isset($selchildid) ? $selchildid : null, $userid, $lastip);
qa_db_points_update_ifuser($oldquestion['userid'], 'aselects');
qa_db_unselqcount_update();
......@@ -168,6 +170,15 @@ function qa_question_set_selchildid($userid, $handle, $cookieid, $oldquestion, $
'postid' => $oldselchildid,
'answer' => $answers[$oldselchildid],
));
if (!empty($oldquestion['closed']) && empty($oldquestion['closedbyid'])) {
qa_db_post_set_closed($oldquestion['postid'], null, $userid, $lastip);
qa_report_event('q_reopen', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
));
}
}
if (isset($selchildid)) {
......@@ -179,6 +190,17 @@ function qa_question_set_selchildid($userid, $handle, $cookieid, $oldquestion, $
'postid' => $selchildid,
'answer' => $answers[$selchildid],
));
if (empty($oldquestion['closed'])) {
qa_db_post_set_closed($oldquestion['postid'], null, $userid, $lastip);
qa_report_event('q_close', $userid, $handle, $cookieid, array(
'postid' => $oldquestion['postid'],
'oldquestion' => $oldquestion,
'reason' => 'answer-selected',
'originalid' => $answers[$selchildid],
));
}
}
}
......
......@@ -207,8 +207,8 @@ function qa_post_set_selchildid($questionid, $answerid, $byuserid = null)
/**
* Closed $questionid if $closed is true, otherwise reopen it. If $closed is true, pass either the $originalpostid of
* the question that it is a duplicate of, or a $note to explain why it's closed. Pass the identify of the user in
* Close $questionid if $closed is true, otherwise reopen it. If $closed is true, pass either the $originalpostid of
* the question that it is a duplicate of, or a $note to explain why it's closed. Pass the identifier of the user in
* $byuserid (or null for an anonymous change).
* @param $questionid
* @param bool $closed
......@@ -234,6 +234,18 @@ function qa_post_set_closed($questionid, $closed = true, $originalpostid = null,
qa_question_close_clear($oldquestion, $oldclosepost, $byuserid, $byhandle, null);
}
/**
* Return whether the given question is closed. This check takes into account the do_close_on_select option which
* considers questions with a selected answer as closed.
* @since 1.8.2
* @param array $question
* @return bool
*/
function qa_post_is_closed(array $question)
{
return isset($question['closedbyid']) || (isset($question['selchildid']) && qa_opt('do_close_on_select'));
}
/**
* Hide $postid if $hidden is true, otherwise show the post. Pass the identify of the user making this change in
......
......@@ -62,6 +62,7 @@ function qa_q_list_page_content($questions, $pagesize, $start, $count, $sometitl
require_once QA_INCLUDE_DIR . 'app/format.php';
require_once QA_INCLUDE_DIR . 'app/updates.php';
require_once QA_INCLUDE_DIR . 'app/posts.php';
$userid = qa_get_logged_in_userid();
......@@ -89,7 +90,7 @@ function qa_q_list_page_content($questions, $pagesize, $start, $count, $sometitl
$qa_content['q_list']['qs'] = array();
if (count($questions)) {
if (!empty($questions)) {
$qa_content['title'] = $sometitle;
$defaults = qa_post_html_defaults('Q');
......@@ -100,7 +101,7 @@ function qa_q_list_page_content($questions, $pagesize, $start, $count, $sometitl
foreach ($questions as $question) {
$fields = qa_any_to_q_html_fields($question, $userid, qa_cookie_get(), $usershtml, null, qa_post_html_options($question, $defaults));
if (!empty($fields['raw']['closedbyid'])) {
if (qa_post_is_closed($question)) {
$fields['closed'] = array(
'state' => qa_lang_html('main/closed'),
);
......
......@@ -520,9 +520,12 @@ function qa_set_user_avatar($userid, $imagedata, $oldblobid = null)
$newblobid = qa_create_blob($imagedata, 'jpeg', null, $userid, null, qa_remote_ip_address());
if (isset($newblobid)) {
qa_db_user_set($userid, 'avatarblobid', $newblobid);
qa_db_user_set($userid, 'avatarwidth', $width);
qa_db_user_set($userid, 'avatarheight', $height);
qa_db_user_set($userid, array(
'avatarblobid' => $newblobid,
'avatarwidth' => $width,
'avatarheight' => $height,
));
qa_db_user_set_flag($userid, QA_USER_FLAGS_SHOW_AVATAR, true);
qa_db_user_set_flag($userid, QA_USER_FLAGS_SHOW_GRAVATAR, false);
......
......@@ -289,8 +289,10 @@ if (QA_FINAL_EXTERNAL_USERS) {
if (empty($userinfo['sessioncode']) || ($source !== $userinfo['sessionsource'])) {
$sessioncode = qa_db_user_rand_sessioncode();
qa_db_user_set($userid, 'sessioncode', $sessioncode);
qa_db_user_set($userid, 'sessionsource', $source);
qa_db_user_set($userid, array(
'sessioncode' => $sessioncode,
'sessionsource' => $source,
));
} else
$sessioncode = $userinfo['sessioncode'];
......@@ -518,6 +520,7 @@ if (QA_FINAL_EXTERNAL_USERS) {
* Return the URL to the $blobId with a stored size of $width and $height.
* Constrain the image to $size (width AND height)
*
* @since 1.8.0
* @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
......@@ -569,7 +572,7 @@ if (QA_FINAL_EXTERNAL_USERS) {
$userHtml = '<a href="' . $url . '" class="qa-user-link' . $favclass . '"' . $mfAttr . '>' . $userHandle . '</a>';
if ($microdata) {
$userHtml = '<span itemprop="author" itemscope itemtype="http://schema.org/Person">' . $userHtml . '</span>';
$userHtml = '<span itemprop="author" itemscope itemtype="https://schema.org/Person">' . $userHtml . '</span>';
}
return $userHtml;
......@@ -582,6 +585,7 @@ if (QA_FINAL_EXTERNAL_USERS) {
* 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
*
* @since 1.8.0
* @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.
......@@ -1401,6 +1405,7 @@ function qa_check_form_security_code($action, $value)
/**
* Return the URL for the Gravatar corresponding to $email, constrained to $size
*
* @since 1.8.0
* @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
......
......@@ -164,17 +164,30 @@ function qa_db_user_get_handle_userids($handles)
/**
* Set $field of $userid to $value in the database users table
* @param $userid
* @param $field
* @param $value
* Set $field of $userid to $value in the database users table. If the $fields parameter is an array, the $value
* parameter is ignored and each element of the array is treated as a key-value pair of user fields and values.
* @param mixed $userid
* @param string|array $fields
* @param string|null $value
*/
function qa_db_user_set($userid, $field, $value)
function qa_db_user_set($userid, $fields, $value = null)
{
qa_db_query_sub(
'UPDATE ^users SET ' . qa_db_escape_string($field) . '=$ WHERE userid=$',
$value, $userid
);
if (!is_array($fields)) {
$fields = array(
$fields => $value,
);
}
$sql = 'UPDATE ^users SET ';
foreach ($fields as $field => $fieldValue) {
$sql .= qa_db_escape_string($field) . ' = $, ';
}
$sql = substr($sql, 0, -2) . ' WHERE userid = $';
$params = array_values($fields);
$params[] = $userid;
qa_db_query_sub_params($sql, $params);
}
......
......@@ -184,7 +184,7 @@ function qa_page_q_post_rules($post, $parentpost = null, $siblingposts = null, $
$rules['closeable'] = qa_opt('allow_close_questions') && $post['type'] == 'Q' && !$rules['closed'] && $permiterror_close_open === false;
// cannot reopen a question if it's been hidden, or if it was closed by someone else and you don't have global closing permissions
$rules['reopenable'] = $rules['closed'] && isset($post['closedbyid']) && $permiterror_close_open === false && !$post['hidden']
$rules['reopenable'] = $rules['closed'] && $permiterror_close_open === false && !$post['hidden']
&& ($notclosedbyother || !qa_user_permit_error('permit_close_q', null, $userlevel, true, $userfields));
$rules['moderatable'] = $post['queued'] && !$permiterror_moderate;
......@@ -266,6 +266,8 @@ function qa_page_q_post_rules($post, $parentpost = null, $siblingposts = null, $
*/
function qa_page_q_question_view($question, $parentquestion, $closepost, $usershtml, $formrequested)
{
require_once QA_INCLUDE_DIR . 'app/posts.php';
$questionid = $question['postid'];
$userid = qa_get_logged_in_userid();
$cookieid = qa_cookie_get();
......@@ -433,7 +435,7 @@ function qa_page_q_question_view($question, $parentquestion, $closepost, $usersh
// Information about the question that this question is a duplicate of (if appropriate)
if (isset($closepost)) {
if (isset($closepost) || qa_post_is_closed($question)) {
if ($closepost['basetype'] == 'Q') {
if ($closepost['hidden']) {
// don't show link for hidden questions
......@@ -461,6 +463,12 @@ function qa_page_q_question_view($question, $parentquestion, $closepost, $usersh
'blockwordspreg' => qa_get_block_words_preg(),
)),
);
} else { // If closed by a selected answer due to the do_close_on_select setting being enabled
$q_view['closed'] = array(
'state' => qa_lang_html('main/closed'),
'label' => qa_lang_html('main/closed'),
'content' => '',
);
}
}
......@@ -848,10 +856,10 @@ function qa_page_q_comment_follow_list($question, $parent, $commentsfollows, $al
$skipfirst--;
} elseif ($commentfollow['basetype'] == 'C') {
$commentlist['cs'][$commentfollowid] = qa_page_q_comment_view($question, $parent, $commentfollow, $usershtml, $formrequested);
} elseif ($commentfollow['basetype'] == 'Q') {
$htmloptions = qa_post_html_options($commentfollow);
$htmloptions['avatarsize'] = qa_opt('avatar_q_page_c_size');
$htmloptions['voteview'] = false;
$commentlist['cs'][$commentfollowid] = qa_post_html_fields($commentfollow, $userid, $cookieid, $usershtml, null, $htmloptions);
}
......
......@@ -259,8 +259,8 @@ if ($formtype == 'q_edit') { // ...in edit mode
$microdata = qa_opt('use_microdata');
if ($microdata) {
$qa_content['head_lines'][] = '<meta itemprop="name" content="' . qa_html($qa_content['q_view']['raw']['title']) . '">';
$qa_content['html_tags'] .= ' itemscope itemtype="http://schema.org/QAPage"';
$qa_content['main_tags'] = ' itemscope itemtype="http://schema.org/Question"';
$qa_content['html_tags'] .= ' itemscope itemtype="https://schema.org/QAPage"';
$qa_content['wrapper_tags'] = ' itemprop="mainEntity" itemscope itemtype="https://schema.org/Question"';
}
......@@ -336,7 +336,7 @@ if (qa_opt('sort_answers_by') == 'votes') {
// further changes to ordering to deal with queued, hidden and selected answers
$countfortitle = $question['acount'];
$countfortitle = (int) $question['acount'];
$nextposition = 10000;
$answerposition = array();
......@@ -424,17 +424,19 @@ foreach ($answerids as $answerid) {
if ($question['basetype'] == 'Q') {
$qa_content['a_list']['title_tags'] = 'id="a_list_title"';
if ($countfortitle > 0) {
$split = $countfortitle == 1
? qa_lang_html_sub_split('question/1_answer_title', '1', '1')
: qa_lang_html_sub_split('question/x_answers_title', $countfortitle);
$split = $countfortitle == 1
? qa_lang_html_sub_split('question/1_answer_title', '1', '1')
: qa_lang_html_sub_split('question/x_answers_title', $countfortitle);
if ($microdata) {
$split['data'] = '<span itemprop="answerCount">' . $split['data'] . '</span>';
}
$qa_content['a_list']['title'] = $split['prefix'] . $split['data'] . $split['suffix'];
} else
if ($microdata) {
$split['data'] = '<span itemprop="answerCount">' . $split['data'] . '</span>';
}
$qa_content['a_list']['title'] = $split['prefix'] . $split['data'] . $split['suffix'];
if ($countfortitle == 0) {
$qa_content['a_list']['title_tags'] .= ' style="display:none;" ';
}
}
if (!$formrequested) {
......
......@@ -35,26 +35,42 @@ if (QA_FINAL_EXTERNAL_USERS)
// Check the code and unsubscribe the user if appropriate
$unsubscribed = false;
$loginuserid = qa_get_logged_in_userid();
$incode = trim(qa_get('c')); // trim to prevent passing in blank values to match uninitiated DB rows
$inhandle = qa_get('u');
if (!empty($inhandle)) { // match based on code and handle provided on URL
$userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($inhandle, false));
if (strtolower(trim(@$userinfo['emailcode'])) == strtolower($incode)) {
qa_db_user_set_flag($userinfo['userid'], QA_USER_FLAGS_NO_MAILINGS, true);
$unsubscribed = true;
// check if already unsubscribed
$unsubscribed = (bool) (qa_get_logged_in_flags() & QA_USER_FLAGS_NO_MAILINGS);
$loggedInUserId = qa_get_logged_in_userid();
$isLoggedIn = $loggedInUserId !== null;
if (qa_clicked('dounsubscribe')) {
if (!qa_check_form_security_code('unsubscribe', qa_post_text('formcode'))) {
$pageError = qa_lang_html('misc/form_security_again');
} else {
if ($isLoggedIn) {
// logged in users can unsubscribe right away
qa_db_user_set_flag($loggedInUserId, QA_USER_FLAGS_NO_MAILINGS, true);
$unsubscribed = true;
} else {
// logged out users require valid code (from email link)
$incode = trim(qa_post_text('code'));
$inhandle = qa_post_text('handle');
if (!empty($inhandle)) {
$userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($inhandle, false));
if (strtolower(trim(@$userinfo['emailcode'])) == strtolower($incode)) {
qa_db_user_set_flag($userinfo['userid'], QA_USER_FLAGS_NO_MAILINGS, true);
$unsubscribed = true;
}
}
if (!$unsubscribed) {
$pageError = qa_insert_login_links(qa_lang_html('users/unsubscribe_wrong_log_in'), 'unsubscribe');
}
}
}
}
if (!$unsubscribed && isset($loginuserid)) { // as a backup, also unsubscribe logged in user
qa_db_user_set_flag($loginuserid, QA_USER_FLAGS_NO_MAILINGS, true);
$unsubscribed = true;
}
// Prepare content for theme
......@@ -68,9 +84,60 @@ if ($unsubscribed) {
'^1' => '<a href="' . qa_path_html('account') . '">',
'^2' => '</a>',
));
} elseif (!empty($pageError)) {
$qa_content['error'] = $pageError;
} else {
$qa_content['error'] = qa_insert_login_links(qa_lang_html('users/unsubscribe_wrong_log_in'), 'unsubscribe');
}
$contentForm = array(
'tags' => 'method="post" action="' . qa_path_html('unsubscribe') . '"',
'style' => 'wide',
'fields' => array(),
'buttons' => array(
'send' => array(
'tags' => 'name="dounsubscribe"',
'label' => qa_lang_html('users/unsubscribe_title'),
),
),
'hidden' => array(
'formcode' => qa_get_form_security_code('unsubscribe'),
),
);
if ($isLoggedIn) {
// user is logged in: show button to confirm unsubscribe
$contentForm['fields']['email'] = array(
'type' => 'static',
'label' => qa_lang_html('users/email_label'),
'value' => qa_html(qa_get_logged_in_email()),
);
} else {
// user is not logged in: show form with email address
$incode = trim(qa_get('c'));
$inhandle = qa_get('u');
if (empty($incode) || empty($inhandle)) {
$qa_content['error'] = qa_insert_login_links(qa_lang_html('users/unsubscribe_wrong_log_in'), 'account');
$contentForm = null;
} else {
$contentForm['fields']['handle'] = array(
'type' => 'static',
'label' => qa_lang_html('users/handle_label'),
'value' => qa_html($inhandle),
);
$contentForm['hidden']['code'] = qa_html($incode);
$contentForm['hidden']['handle'] = qa_html($inhandle);
}
}
if ($contentForm) {
$qa_content['form'] = $contentForm;
}
}
return $qa_content;
......@@ -162,9 +162,12 @@ if (!QA_FINAL_EXTERNAL_USERS) {
if (isset($useraccount['avatarblobid'])) {
require_once QA_INCLUDE_DIR . 'app/blobs.php';
qa_db_user_set($userid, 'avatarblobid', null);
qa_db_user_set($userid, 'avatarwidth', null);
qa_db_user_set($userid, 'avatarheight', null);
qa_db_user_set($userid, array(
'avatarblobid' => null,
'avatarwidth' => null,
'avatarheight' => null,
));
qa_delete_blob($useraccount['avatarblobid']);
}
}
......
......@@ -20,8 +20,8 @@
*/
define('QA_VERSION', '1.8.1'); // also used as suffix for .js and .css requests
define('QA_BUILD_DATE', '2018-12-01');
define('QA_VERSION', '1.8.2'); // also used as suffix for .js and .css requests
define('QA_BUILD_DATE', '2018-12-20');
/**
......@@ -84,6 +84,7 @@ if (!isset($qa_autoconnect) || $qa_autoconnect !== false) {
/**
* Converts the $version string (e.g. 1.6.2.2) to a floating point that can be used for greater/lesser comparisons
* (PHP's version_compare() function is not quite suitable for our needs)
* @deprecated 1.8.2 no longer used
* @param $version
* @return float
*/
......@@ -106,30 +107,24 @@ function qa_version_to_float($version)
/**
* Returns true if the current Q2A version is lower than $version, if both are valid version strings for qa_version_to_float()
* Returns true if the current Q2A version is lower than $version
* @param $version
* @return bool
*/
function qa_qa_version_below($version)
{
$minqa = qa_version_to_float($version);
$thisqa = qa_version_to_float(QA_VERSION);
return $minqa && $thisqa && $thisqa < $minqa;
return version_compare(QA_VERSION, $version) < 0;
}
/**
* Returns true if the current PHP version is lower than $version, if both are valid version strings for qa_version_to_float()
* Returns true if the current PHP version is lower than $version
* @param $version
* @return bool
*/
function qa_php_version_below($version)
{
$minphp = qa_version_to_float($version);
$thisphp = qa_version_to_float(phpversion());
return $minphp && $thisphp && $thisphp < $minphp;
return version_compare(phpversion(), $version) < 0;
}
......
......@@ -372,14 +372,26 @@ function qa_db_apply_sub($query, $arguments)
/**
* Run $query after substituting ^, # and $ symbols, and return the result resource (or call fail handler).
* @param $query
* @param string $query
* @return mixed
*/
function qa_db_query_sub($query) // arguments for substitution retrieved using func_get_args()
{
$funcargs = func_get_args();
return qa_db_query_raw(qa_db_apply_sub($query, array_slice($funcargs, 1)));
return qa_db_query_sub_params($query, array_slice($funcargs, 1));
}
/**
* Run $query after substituting ^, # and $ symbols, and return the result resource (or call fail handler).
* Query parameters are passed as an array.
* @param string $query
* @param array $params
* @return mixed
*/
function qa_db_query_sub_params($query, $params)
{
return qa_db_query_raw(qa_db_apply_sub($query, $params));
}
......
......@@ -280,6 +280,7 @@ switch ($feedtype) {
require_once QA_INCLUDE_DIR . 'app/format.php';
require_once QA_INCLUDE_DIR . 'app/updates.php';
require_once QA_INCLUDE_DIR . 'app/posts.php';
require_once QA_INCLUDE_DIR . 'util/string.php';
if ($feedtype != 'search' && $feedtype != 'hot') // leave search results and hot questions sorted by relevance
......@@ -337,7 +338,7 @@ foreach ($questions as $question) {
break;
case 'Q-' . QA_UPDATE_CLOSED:
$langstring = isset($question['closedbyid']) ? 'misc/feed_closed_prefix' : 'misc/feed_reopened_prefix';
$langstring = qa_post_is_closed($question) ? 'misc/feed_closed_prefix' : 'misc/feed_reopened_prefix';
break;
case 'Q-' . QA_UPDATE_TAGS:
......
......@@ -435,7 +435,8 @@ class qa_html_theme_base
$this->body_prefix();
$this->notices();
$this->output('<div class="qa-body-wrapper">', '');
$extratags = isset($this->content['wrapper_tags']) ? $this->content['wrapper_tags'] : '';
$this->output('<div class="qa-body-wrapper"' . $extratags . '>', '');
$this->widgets('full', 'top');
$this->header();
......@@ -713,9 +714,8 @@ class qa_html_theme_base
{
$content = $this->content;
$hidden = !empty($content['hidden']) ? ' qa-main-hidden' : '';
$extratags = isset($this->content['main_tags']) ? $this->content['main_tags'] : '';
$this->output('<div class="qa-main' . $hidden . '"' . $extratags . '>');
$this->output('<div class="qa-main' . $hidden . '">');
$this->widgets('main', 'top');
......@@ -1964,13 +1964,16 @@ class qa_html_theme_base
{
if (isset($post['what'])) {
$classes = $class . '-what';
if (@$post['what_your'])
if (isset($post['what_your']) && $post['what_your']) {
$classes .= ' ' . $class . '-what-your';
}
if (isset($post['what_url']))
$this->output('<a href="' . $post['what_url'] . '" class="' . $classes . '">' . $post['what'] . '</a>');
else
if (isset($post['what_url'])) {
$tags = isset($post['what_url_tags']) ? $post['what_url_tags'] : '';
$this->output('<a href="' . $post['what_url'] . '" class="' . $classes . '"' . $tags . '>' . $post['what'] . '</a>');
} else {
$this->output('<span class="' . $classes . '">' . $post['what'] . '</span>');
}
}
}
......
......@@ -821,9 +821,9 @@ if (!defined('QA_PREG_BLOCK_WORD_SEPARATOR')) {
define('QA_PREG_BLOCK_WORD_SEPARATOR', '[\n\r\t\ \!\"\\\'\(\)\+\,\.\/\:\;\<\=\>\?\[\\\\\]\^\`\{\|\}\~\$\&\-\_\#\%\@]');
}
// Pattern to match Chinese/Japanese/Korean ideographic symbols in UTF-8 encoding
// Pattern to match Thai/Chinese/Japanese/Korean ideographic symbols in UTF-8 encoding
if (!defined('QA_PREG_CJK_IDEOGRAPHS_UTF8')) {
define('QA_PREG_CJK_IDEOGRAPHS_UTF8', '\xE2[\xBA-\xBF][\x80-\xBF]|\xE3[\x80\x88-\xBF][\x80-\xBF]|[\xE4-\xE9][\x80-\xBF][\x80-\xBF]|\xEF[\xA4-\xAB][\x80-\xBF]|\xF0[\xA0-\xAF][\x80-\xBF][\x80-\xBF]');
define('QA_PREG_CJK_IDEOGRAPHS_UTF8', '\xE0[\xB8-\xB9][\x80-\xBF]|\xE2[\xBA-\xBF][\x80-\xBF]|\xE3[\x80\x88-\xBF][\x80-\xBF]|[\xE4-\xE9][\x80-\xBF][\x80-\xBF]|\xEF[\xA4-\xAB][\x80-\xBF]|\xF0[\xA0-\xAF][\x80-\xBF][\x80-\xBF]');
}
qa_string_initialize();
......@@ -145,10 +145,12 @@ class qa_event_logger
public function value_to_text($value)
{
require_once QA_INCLUDE_DIR . 'util/string.php';
if (is_array($value))
$text = 'array(' . count($value) . ')';
elseif (strlen($value) > 40)
$text = substr($value, 0, 38) . '...';
elseif (qa_strlen($value) > 40)
$text = qa_substr($value, 0, 38) . '...';
else
$text = $value;
......
......@@ -25,7 +25,7 @@ class qa_wysiwyg_upload
{
public function match_request($request)
{
return ($request == 'wysiwyg-editor-upload');
return $request === 'wysiwyg-editor-upload';
}
public function process_request($request)
......@@ -34,24 +34,33 @@ class qa_wysiwyg_upload
$url = '';
if (is_array($_FILES) && count($_FILES)) {
if (!qa_opt('wysiwyg_editor_upload_images'))
$message = qa_lang_html('users/no_permission');
require_once QA_INCLUDE_DIR.'app/upload.php';
if (qa_opt('wysiwyg_editor_upload_images')) {
require_once QA_INCLUDE_DIR . 'app/upload.php';
$upload = qa_upload_file_one(
qa_opt('wysiwyg_editor_upload_max_size'),
qa_get('qa_only_image') || !qa_opt('wysiwyg_editor_upload_all'),
qa_get('qa_only_image') ? 600 : null, // max width if it's an image upload
null // no max height
);
$onlyImage = qa_get('qa_only_image');
$upload = qa_upload_file_one(
qa_opt('wysiwyg_editor_upload_max_size'),
$onlyImage || !qa_opt('wysiwyg_editor_upload_all'),
$onlyImage ? 600 : null, // max width if it's an image upload
null // no max height
);
$message = @$upload['error'];
$url = @$upload['bloburl'];
if (isset($upload['error'])) {
$message = $upload['error'];
} else {
$url = $upload['bloburl'];
}
} else {
$message = qa_lang_html('users/no_permission');
}
}
echo "<script>window.parent.CKEDITOR.tools.callFunction(".qa_js(qa_get('CKEditorFuncNum')).
", ".qa_js($url).", ".qa_js($message).");</script>";
echo sprintf(
'<script>window.parent.CKEDITOR.tools.callFunction(%s, %s, %s);</script>',
qa_js(qa_get('CKEditorFuncNum')),
qa_js($url),
qa_js($message)
);
return null;
}
......
......@@ -2,6 +2,29 @@
class BaseTest extends PHPUnit_Framework_TestCase
{
public function test__qa_version_to_float()
{
$this->assertSame(1.006, qa_version_to_float('1.6'));
$this->assertSame(1.007004, qa_version_to_float('1.7.4'));
$this->assertSame(1.008, qa_version_to_float('1.8.0-beta1'));
}
public function test__qa_qa_version_below()
{
// as we cannot change the QA_VERSION constant, we test an appended version against the set constant
$buildVersion = QA_VERSION . '.1234';
$betaVersion = QA_VERSION . '-beta1';
$this->assertSame(true, qa_qa_version_below($buildVersion));
$this->assertSame(false, qa_qa_version_below($betaVersion));
}
public function test__qa_php_version_below()
{
// as we cannot change the PHP version, we test against an unsupported PHP version and a far-future version
$this->assertSame(false, qa_php_version_below('5.1.4'));
$this->assertSame(true, qa_php_version_below('11.1.0'));
}
public function test__qa_js()
{
$this->assertSame("'test'", qa_js('test'));
......
......@@ -452,8 +452,8 @@ h2 {font-size:16px; padding-top:12px; clear:both;}
/* Favorited items */
.qa-q-favorited .qa-q-item-title a, .qa-tag-favorited, .qa-cat-favorited, .qa-user-favorited, .qa-nav-cat-favorited, .qa-browse-cat-favorited {background: url(favorite-icon-14x14.gif) no-repeat;}
.qa-cat-parent-favorited {background: url(favorite-light-icon-14x14.gif) no-repeat;}
.qa-q-favorited .qa-q-item-title a, .qa-tag-favorited, .qa-cat-favorited, .qa-user-favorited, .qa-nav-cat-favorited, .qa-browse-cat-favorited {background-image: url(favorite-icon-14x14.gif); background-repeat: no-repeat;}
.qa-cat-parent-favorited {background-image: url(favorite-light-icon-14x14.gif); background-repeat: no-repeat;}
.qa-q-favorited .qa-q-item-title a, .qa-nav-cat-favorited, .qa-browse-cat-favorited {background-position: left center; padding-left:18px;}
.qa-tag-favorited {background-position: 3px center; padding-left:20px;}
.qa-cat-favorited, .qa-cat-parent-favorited, .qa-user-favorited {background-position: left center; padding-left:17px;}
......
......@@ -66,24 +66,28 @@ class qa_html_theme extends qa_html_theme_base
if ($this->localfonts) {
// add Ubuntu font locally (inlined for speed)
$this->output_array(array(
'<style>',
'@font-face {',
' font-family: "Ubuntu"; font-style: normal; font-weight: 400;',
' src: local("Ubuntu"), url("' . $this->rooturl . 'fonts/Ubuntu-regular.woff") format("woff");',
'}',
'@font-face {',
' font-family: "Ubuntu"; font-style: normal; font-weight: 700;',
' src: local("Ubuntu Bold"), local("Ubuntu-Bold"), url("' . $this->rooturl . 'fonts/Ubuntu-700.woff") format("woff");',
'}',
'@font-face {',
' font-family: "Ubuntu"; font-style: italic; font-weight: 400;',
' src: local("Ubuntu Italic"), local("Ubuntu-Italic"), url("' . $this->rooturl . 'fonts/Ubuntu-italic.woff") format("woff");',
'}',
'@font-face {',
' font-family: "Ubuntu"; font-style: italic; font-weight: 700;',
' src: local("Ubuntu Bold Italic"), local("Ubuntu-BoldItalic"), url("' . $this->rooturl . 'fonts/Ubuntu-700italic.woff") format("woff");',
'}',
'</style>',
"<style>",
"@font-face {",
" font-family: 'Ubuntu'; font-weight: normal; font-style: normal;",
" src: local('Ubuntu'),",
" url('{$this->rooturl}fonts/ubuntu-regular.woff2') format('woff2'), url('{$this->rooturl}fonts/ubuntu-regular.woff') format('woff');",
"}",
"@font-face {",
" font-family: 'Ubuntu'; font-weight: bold; font-style: normal;",
" src: local('Ubuntu Bold'), local('Ubuntu-Bold'),",
" url('{$this->rooturl}fonts/ubuntu-bold.woff2') format('woff2'), url('{$this->rooturl}fonts/ubuntu-bold.woff') format('woff');",
"}",
"@font-face {",
" font-family: 'Ubuntu'; font-weight: normal; font-style: italic;",
" src: local('Ubuntu Italic'), local('Ubuntu-Italic'),",
" url('{$this->rooturl}fonts/ubuntu-italic.woff2') format('woff2'), url('{$this->rooturl}fonts/ubuntu-italic.woff') format('woff');",
"}",
"@font-face {",
" font-family: 'Ubuntu'; font-weight: bold; font-style: italic;",
" src: local('Ubuntu Bold Italic'), local('Ubuntu-BoldItalic'),",
" url('{$this->rooturl}fonts/ubuntu-bold-italic.woff2') format('woff2'), url('{$this->rooturl}fonts/ubuntu-bold-italic.woff') format('woff');",
"}",
"</style>",
));
}
else {
......@@ -229,7 +233,8 @@ class qa_html_theme extends qa_html_theme_base
$this->widgets('full', 'top');
$this->header();
$this->output('<div class="qa-body-wrapper">', '');
$extratags = isset($this->content['wrapper_tags']) ? $this->content['wrapper_tags'] : '';
$this->output('<div class="qa-body-wrapper"' . $extratags . '>', '');
$this->widgets('full', 'high');
$this->output('<div class="qa-main-wrapper">', '');
......
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