PageRenderTime 69ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/qa-include/qa-app-format.php

http://github.com/q2a/question2answer
PHP | 1698 lines | 1046 code | 402 blank | 250 comment | 203 complexity | 3c0b2cb389b1ad325419436820cfce1e MD5 | raw file
Possible License(s): LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. Question2Answer (c) Gideon Greenspan
  4. http://www.question2answer.org/
  5. File: qa-include/qa-app-format.php
  6. Version: See define()s at top of qa-include/qa-base.php
  7. Description: Common functions for creating theme-ready structures from data
  8. This program is free software; you can redistribute it and/or
  9. modify it under the terms of the GNU General Public License
  10. as published by the Free Software Foundation; either version 2
  11. of the License, or (at your option) any later version.
  12. This program is distributed in the hope that it will be useful,
  13. but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. GNU General Public License for more details.
  16. More about this license: http://www.question2answer.org/license.php
  17. */
  18. if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
  19. header('Location: ../');
  20. exit;
  21. }
  22. define('QA_PAGE_FLAGS_EXTERNAL', 1);
  23. define('QA_PAGE_FLAGS_NEW_WINDOW', 2);
  24. function qa_time_to_string($seconds)
  25. /*
  26. Return textual representation of $seconds
  27. */
  28. {
  29. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  30. $seconds=max($seconds, 1);
  31. $scales=array(
  32. 31557600 => array( 'main/1_year' , 'main/x_years' ),
  33. 2629800 => array( 'main/1_month' , 'main/x_months' ),
  34. 604800 => array( 'main/1_week' , 'main/x_weeks' ),
  35. 86400 => array( 'main/1_day' , 'main/x_days' ),
  36. 3600 => array( 'main/1_hour' , 'main/x_hours' ),
  37. 60 => array( 'main/1_minute' , 'main/x_minutes' ),
  38. 1 => array( 'main/1_second' , 'main/x_seconds' ),
  39. );
  40. foreach ($scales as $scale => $phrases)
  41. if ($seconds>=$scale) {
  42. $count=floor($seconds/$scale);
  43. if ($count==1)
  44. $string=qa_lang($phrases[0]);
  45. else
  46. $string=qa_lang_sub($phrases[1], $count);
  47. break;
  48. }
  49. return $string;
  50. }
  51. function qa_post_is_by_user($post, $userid, $cookieid)
  52. /*
  53. Check if $post is by user $userid, or if post is anonymous and $userid not specified, then
  54. check if $post is by the anonymous user identified by $cookieid
  55. */
  56. {
  57. // In theory we should only test against NULL here, i.e. use isset($post['userid'])
  58. // but the risk of doing so is so high (if a bug creeps in that allows userid=0)
  59. // that I'm doing a tougher test. This will break under a zero user or cookie id.
  60. if (@$post['userid'] || $userid)
  61. return @$post['userid']==$userid;
  62. elseif (@$post['cookieid'])
  63. return strcmp($post['cookieid'], $cookieid)==0;
  64. return false;
  65. }
  66. function qa_userids_handles_html($useridhandles, $microformats=false)
  67. /*
  68. Return array which maps the ['userid'] and/or ['lastuserid'] in each element of
  69. $useridhandles to its HTML representation. For internal user management, corresponding
  70. ['handle'] and/or ['lasthandle'] are required in each element.
  71. */
  72. {
  73. require_once QA_INCLUDE_DIR.'qa-app-users.php';
  74. if (QA_FINAL_EXTERNAL_USERS) {
  75. $keyuserids=array();
  76. foreach ($useridhandles as $useridhandle) {
  77. if (isset($useridhandle['userid']))
  78. $keyuserids[$useridhandle['userid']]=true;
  79. if (isset($useridhandle['lastuserid']))
  80. $keyuserids[$useridhandle['lastuserid']]=true;
  81. }
  82. if (count($keyuserids))
  83. return qa_get_users_html(array_keys($keyuserids), true, qa_path_to_root(), $microformats);
  84. else
  85. return array();
  86. } else {
  87. $usershtml=array();
  88. foreach ($useridhandles as $useridhandle) {
  89. if (isset($useridhandle['userid']) && $useridhandle['handle'])
  90. $usershtml[$useridhandle['userid']]=qa_get_one_user_html($useridhandle['handle'], $microformats);
  91. if (isset($useridhandle['lastuserid']) && $useridhandle['lasthandle'])
  92. $usershtml[$useridhandle['lastuserid']]=qa_get_one_user_html($useridhandle['lasthandle'], $microformats);
  93. }
  94. return $usershtml;
  95. }
  96. }
  97. function qa_tag_html($tag, $microformats=false)
  98. /*
  99. Convert textual $tag to HTML representation
  100. */
  101. {
  102. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  103. return '<A HREF="'.qa_path_html('tag/'.$tag).'"'.($microformats ? ' rel="tag"' : '').' CLASS="qa-tag-link">'.qa_html($tag).'</A>';
  104. }
  105. function qa_category_path($navcategories, $categoryid)
  106. /*
  107. Given $navcategories retrieved for $categoryid from the database (using qa_db_category_nav_selectspec(...)),
  108. return an array of elements from $navcategories for the hierarchy down to $categoryid.
  109. */
  110. {
  111. $upcategories=array();
  112. for ($upcategory=@$navcategories[$categoryid]; isset($upcategory); $upcategory=@$navcategories[$upcategory['parentid']])
  113. $upcategories[$upcategory['categoryid']]=$upcategory;
  114. return array_reverse($upcategories, true);
  115. }
  116. function qa_category_path_html($navcategories, $categoryid)
  117. /*
  118. Given $navcategories retrieved for $categoryid from the database (using qa_db_category_nav_selectspec(...)),
  119. return some HTML that shows the category hierarchy down to $categoryid.
  120. */
  121. {
  122. $categories=qa_category_path($navcategories, $categoryid);
  123. $html='';
  124. foreach ($categories as $category)
  125. $html.=(strlen($html) ? ' / ' : '').qa_html($category['title']);
  126. return $html;
  127. }
  128. function qa_category_path_request($navcategories, $categoryid)
  129. /*
  130. Given $navcategories retrieved for $categoryid from the database (using qa_db_category_nav_selectspec(...)),
  131. return a Q2A request string that represents the category hierarchy down to $categoryid.
  132. */
  133. {
  134. $categories=qa_category_path($navcategories, $categoryid);
  135. $request='';
  136. foreach ($categories as $category)
  137. $request.=(strlen($request) ? '/' : '').$category['tags'];
  138. return $request;
  139. }
  140. function qa_ip_anchor_html($ip, $anchorhtml=null)
  141. /*
  142. Return HTML to use for $ip address, which links to appropriate page with $anchorhtml
  143. */
  144. {
  145. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  146. if (!strlen($anchorhtml))
  147. $anchorhtml=qa_html($ip);
  148. return '<A HREF="'.qa_path_html('ip/'.$ip).'" TITLE="'.qa_lang_html_sub('main/ip_address_x', qa_html($ip)).'" CLASS="qa-ip-link">'.$anchorhtml.'</A>';
  149. }
  150. function qa_post_html_fields($post, $userid, $cookieid, $usershtml, $dummy, $options=array())
  151. /*
  152. Given $post retrieved from database, return array of mostly HTML to be passed to theme layer.
  153. $userid and $cookieid refer to the user *viewing* the page.
  154. $usershtml is an array of [user id] => [HTML representation of user] built ahead of time.
  155. $dummy is a placeholder (used to be $categories parameter but that's no longer needed)
  156. $options is an array which sets what is displayed (see qa_post_html_defaults() in qa-app-options.php)
  157. If something is missing from $post (e.g. ['content']), correponding HTML also omitted.
  158. */
  159. {
  160. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  161. require_once QA_INCLUDE_DIR.'qa-app-updates.php';
  162. if (isset($options['blockwordspreg']))
  163. require_once QA_INCLUDE_DIR.'qa-util-string.php';
  164. $fields=array();
  165. $fields['raw']=$post;
  166. // Useful stuff used throughout function
  167. $postid=$post['postid'];
  168. $isquestion=($post['basetype']=='Q');
  169. $isanswer=($post['basetype']=='A');
  170. $isbyuser=qa_post_is_by_user($post, $userid, $cookieid);
  171. $anchor=urlencode(qa_anchor($post['basetype'], $postid));
  172. $elementid=isset($options['elementid']) ? $options['elementid'] : $anchor;
  173. $microformats=@$options['microformats'];
  174. $isselected=@$options['isselected'];
  175. // High level information
  176. $fields['hidden']=@$post['hidden'];
  177. $fields['tags']='ID="'.qa_html($elementid).'"';
  178. if ($microformats)
  179. $fields['classes']='hentry '.($isquestion ? 'question' : ($isanswer ? ($isselected ? 'answer answer-selected' : 'answer') : 'comment'));
  180. // Question-specific stuff (title, URL, tags, answer count, category)
  181. if ($isquestion) {
  182. if (isset($post['title'])) {
  183. $fields['url']=qa_q_path_html($postid, $post['title']);
  184. if (isset($options['blockwordspreg']))
  185. $post['title']=qa_block_words_replace($post['title'], $options['blockwordspreg']);
  186. $fields['title']=qa_html($post['title']);
  187. if ($microformats)
  188. $fields['title']='<SPAN CLASS="entry-title">'.$fields['title'].'</SPAN>';
  189. /*if (isset($post['score'])) // useful for setting match thresholds
  190. $fields['title'].=' <SMALL>('.$post['score'].')</SMALL>';*/
  191. }
  192. if (@$options['tagsview'] && isset($post['tags'])) {
  193. $fields['q_tags']=array();
  194. $tags=qa_tagstring_to_tags($post['tags']);
  195. foreach ($tags as $tag) {
  196. if (isset($options['blockwordspreg']) && count(qa_block_words_match_all($tag, $options['blockwordspreg']))) // skip censored tags
  197. continue;
  198. $fields['q_tags'][]=qa_tag_html($tag, $microformats);
  199. }
  200. }
  201. if (@$options['answersview'] && isset($post['acount'])) {
  202. $fields['answers_raw']=$post['acount'];
  203. $fields['answers']=($post['acount']==1) ? qa_lang_html_sub_split('main/1_answer', '1', '1')
  204. : qa_lang_html_sub_split('main/x_answers', number_format($post['acount']));
  205. $fields['answer_selected']=isset($post['selchildid']);
  206. }
  207. if (@$options['viewsview'] && isset($post['views'])) {
  208. $fields['views_raw']=$post['views'];
  209. $fields['views']=($post['views']==1) ? qa_lang_html_sub_split('main/1_view', '1', '1') :
  210. qa_lang_html_sub_split('main/x_views', number_format($post['views']));
  211. }
  212. if (@$options['categoryview'] && isset($post['categoryname']) && isset($post['categorybackpath']))
  213. $fields['where']=qa_lang_html_sub_split('main/in_category_x',
  214. '<A HREF="'.qa_path_html(@$options['categorypathprefix'].implode('/', array_reverse(explode('/', $post['categorybackpath'])))).
  215. '" CLASS="qa-category-link">'.qa_html($post['categoryname']).'</A>');
  216. }
  217. // Answer-specific stuff (selection)
  218. if ($isanswer) {
  219. $fields['selected']=$isselected;
  220. if ($isselected)
  221. $fields['select_text']=qa_lang_html('question/select_text');
  222. }
  223. // Post content
  224. if (@$options['contentview'] && !empty($post['content'])) {
  225. $viewer=qa_load_viewer($post['content'], $post['format']);
  226. $fields['content']=$viewer->get_html($post['content'], $post['format'], array(
  227. 'blockwordspreg' => @$options['blockwordspreg'],
  228. 'showurllinks' => @$options['showurllinks'],
  229. 'linksnewwindow' => @$options['linksnewwindow'],
  230. ));
  231. if ($microformats)
  232. $fields['content']='<SPAN CLASS="entry-content">'.$fields['content'].'</SPAN>';
  233. $fields['content']='<A NAME="'.qa_html($postid).'"></A>'.$fields['content'];
  234. // this is for backwards compatibility with any existing links using the old style of anchor
  235. // that contained the post id only (changed to be valid under W3C specifications)
  236. }
  237. // Voting stuff
  238. if (@$options['voteview']) {
  239. $voteview=$options['voteview'];
  240. // Calculate raw values and pass through
  241. $upvotes=(int)@$post['upvotes'];
  242. $downvotes=(int)@$post['downvotes'];
  243. $netvotes=(int)($upvotes-$downvotes);
  244. $fields['upvotes_raw']=$upvotes;
  245. $fields['downvotes_raw']=$downvotes;
  246. $fields['netvotes_raw']=$netvotes;
  247. // Create HTML versions...
  248. $upvoteshtml=qa_html($upvotes);
  249. $downvoteshtml=qa_html($downvotes);
  250. if ($netvotes>=1)
  251. $netvoteshtml='+'.qa_html($netvotes);
  252. elseif ($netvotes<=-1)
  253. $netvoteshtml='&ndash;'.qa_html(-$netvotes);
  254. else
  255. $netvoteshtml='0';
  256. // ...with microformats if appropriate
  257. if ($microformats) {
  258. $netvoteshtml.='<SPAN CLASS="votes-up"><SPAN CLASS="value-title" TITLE="'.$upvoteshtml.'"></SPAN></SPAN>'.
  259. '<SPAN CLASS="votes-down"><SPAN CLASS="value-title" TITLE="'.$downvoteshtml.'"></SPAN></SPAN>';
  260. $upvoteshtml='<SPAN CLASS="votes-up">'.$upvoteshtml.'</SPAN>';
  261. $downvoteshtml='<SPAN CLASS="votes-down">'.$downvoteshtml.'</SPAN>';
  262. }
  263. // Pass information on vote viewing
  264. // $voteview will be one of:
  265. // updown, updown-disabled-level, updown-disabled-page, updown-uponly-level
  266. // net, net-disabled-level, net-disabled-page, net-uponly-level
  267. $fields['vote_view']=(substr($voteview, 0, 6)=='updown') ? 'updown' : 'net';
  268. $fields['upvotes_view']=($upvotes==1) ? qa_lang_html_sub_split('main/1_liked', $upvoteshtml, '1')
  269. : qa_lang_html_sub_split('main/x_liked', $upvoteshtml);
  270. $fields['downvotes_view']=($downvotes==1) ? qa_lang_html_sub_split('main/1_disliked', $downvoteshtml, '1')
  271. : qa_lang_html_sub_split('main/x_disliked', $downvoteshtml);
  272. $fields['netvotes_view']=(abs($netvotes)==1) ? qa_lang_html_sub_split('main/1_vote', $netvoteshtml, '1')
  273. : qa_lang_html_sub_split('main/x_votes', $netvoteshtml);
  274. // Voting buttons
  275. $fields['vote_tags']='ID="voting_'.qa_html($postid).'"';
  276. $onclick='onClick="return qa_vote_click(this);"';
  277. if ($fields['hidden']) {
  278. $fields['vote_state']='disabled';
  279. $fields['vote_up_tags']='TITLE="'.qa_lang_html($isanswer ? 'main/vote_disabled_hidden_a' : 'main/vote_disabled_hidden_q').'"';
  280. $fields['vote_down_tags']=$fields['vote_up_tags'];
  281. } elseif ($isbyuser) {
  282. $fields['vote_state']='disabled';
  283. $fields['vote_up_tags']='TITLE="'.qa_lang_html($isanswer ? 'main/vote_disabled_my_a' : 'main/vote_disabled_my_q').'"';
  284. $fields['vote_down_tags']=$fields['vote_up_tags'];
  285. } elseif (strpos($voteview, '-disabled-')) {
  286. $fields['vote_state']=(@$post['uservote']>0) ? 'voted_up_disabled' : ((@$post['uservote']<0) ? 'voted_down_disabled' : 'disabled');
  287. if (strpos($voteview, '-disabled-page'))
  288. $fields['vote_up_tags']='TITLE="'.qa_lang_html('main/vote_disabled_q_page_only').'"';
  289. else
  290. $fields['vote_up_tags']='TITLE="'.qa_lang_html('main/vote_disabled_level').'"';
  291. $fields['vote_down_tags']=$fields['vote_up_tags'];
  292. } elseif (@$post['uservote']>0) {
  293. $fields['vote_state']='voted_up';
  294. $fields['vote_up_tags']='TITLE="'.qa_lang_html('main/voted_up_popup').'" NAME="'.qa_html('vote_'.$postid.'_0_'.$elementid).'" '.$onclick;
  295. $fields['vote_down_tags']=' ';
  296. } elseif (@$post['uservote']<0) {
  297. $fields['vote_state']='voted_down';
  298. $fields['vote_up_tags']=' ';
  299. $fields['vote_down_tags']='TITLE="'.qa_lang_html('main/voted_down_popup').'" NAME="'.qa_html('vote_'.$postid.'_0_'.$elementid).'" '.$onclick;
  300. } else {
  301. $fields['vote_up_tags']='TITLE="'.qa_lang_html('main/vote_up_popup').'" NAME="'.qa_html('vote_'.$postid.'_1_'.$elementid).'" '.$onclick;
  302. if (strpos($voteview, '-uponly-level')) {
  303. $fields['vote_state']='up_only';
  304. $fields['vote_down_tags']='TITLE="'.qa_lang_html('main/vote_disabled_down').'"';
  305. } else {
  306. $fields['vote_state']='enabled';
  307. $fields['vote_down_tags']='TITLE="'.qa_lang_html('main/vote_down_popup').'" NAME="'.qa_html('vote_'.$postid.'_-1_'.$elementid).'" '.$onclick;
  308. }
  309. }
  310. }
  311. // Flag count
  312. if (@$options['flagsview'] && @$post['flagcount'])
  313. $fields['flags']=($post['flagcount']==1) ? qa_lang_html_sub_split('main/1_flag', '1', '1')
  314. : qa_lang_html_sub_split('main/x_flags', $post['flagcount']);
  315. // Created when and by whom
  316. $fields['meta_order']=qa_lang_html('main/meta_order'); // sets ordering of meta elements which can be language-specific
  317. if (@$options['whatview'] ) {
  318. $fields['what']=qa_lang_html($isquestion ? 'main/asked' : ($isanswer ? 'main/answered' : 'main/commented'));
  319. if (@$options['whatlink'] && !$isquestion)
  320. $fields['what_url']=qa_path_html(qa_request(), array('show' => $postid), null, null, qa_anchor($post['basetype'], $postid));
  321. }
  322. if (isset($post['created']) && @$options['whenview']) {
  323. $fields['when']=qa_when_to_html($post['created'], @$options['fulldatedays']);
  324. if ($microformats)
  325. $fields['when']['data']='<SPAN CLASS="published"><SPAN CLASS="value-title" TITLE="'.gmdate('Y-m-d\TH:i:sO', $post['created']).'"></SPAN>'.$fields['when']['data'].'</SPAN>';
  326. }
  327. if (@$options['whoview']) {
  328. $fields['who']=qa_who_to_html($isbyuser, @$post['userid'], $usershtml, @$options['ipview'] ? @$post['createip'] : null, $microformats);
  329. if (isset($post['points'])) {
  330. if (@$options['pointsview'])
  331. $fields['who']['points']=($post['points']==1) ? qa_lang_html_sub_split('main/1_point', '1', '1')
  332. : qa_lang_html_sub_split('main/x_points', qa_html(number_format($post['points'])));
  333. if (isset($options['pointstitle']))
  334. $fields['who']['title']=qa_get_points_title_html($post['points'], $options['pointstitle']);
  335. }
  336. if (isset($post['level']))
  337. $fields['who']['level']=qa_html(qa_user_level_string($post['level']));
  338. }
  339. if ((!QA_FINAL_EXTERNAL_USERS) && (@$options['avatarsize']>0))
  340. $fields['avatar']=qa_get_user_avatar_html(@$post['flags'], @$post['email'], @$post['handle'],
  341. @$post['avatarblobid'], @$post['avatarwidth'], @$post['avatarheight'], $options['avatarsize']);
  342. // Updated when and by whom
  343. if (
  344. @$options['updateview'] && isset($post['updated']) &&
  345. (($post['updatetype']!=QA_UPDATE_SELECTED) || $isselected) && // only show selected change if it's still selected
  346. ( // otherwise check if one of these conditions is fulfilled...
  347. (!isset($post['created'])) || // ... we didn't show the created time (should never happen in practice)
  348. ($post['hidden'] && ($post['updatetype']==QA_UPDATE_VISIBLE)) || // ... the post was hidden as the last action
  349. (isset($post['closedbyid']) && ($post['updatetype']==QA_UPDATE_CLOSED)) || // ... the post was closed as the last action
  350. (abs($post['updated']-$post['created'])>300) || // ... or over 5 minutes passed between create and update times
  351. ($post['lastuserid']!=$post['userid']) // ... or it was updated by a different user
  352. )
  353. ) {
  354. switch ($post['updatetype']) {
  355. case QA_UPDATE_TYPE:
  356. case QA_UPDATE_PARENT:
  357. $langstring='main/moved';
  358. break;
  359. case QA_UPDATE_CATEGORY:
  360. $langstring='main/recategorized';
  361. break;
  362. case QA_UPDATE_VISIBLE:
  363. $langstring=$post['hidden'] ? 'main/hidden' : 'main/reshown';
  364. break;
  365. case QA_UPDATE_CLOSED:
  366. $langstring=isset($post['closedbyid']) ? 'main/closed' : 'main/reopened';
  367. break;
  368. case QA_UPDATE_TAGS:
  369. $langstring='main/retagged';
  370. break;
  371. case QA_UPDATE_SELECTED:
  372. $langstring='main/selected';
  373. break;
  374. default:
  375. $langstring='main/edited';
  376. break;
  377. }
  378. $fields['what_2']=qa_lang_html($langstring);
  379. if (@$options['whenview']) {
  380. $fields['when_2']=qa_when_to_html($post['updated'], @$options['fulldatedays']);
  381. if ($microformats)
  382. $fields['when_2']['data']='<SPAN CLASS="updated"><SPAN CLASS="value-title" TITLE="'.gmdate('Y-m-d\TH:i:sO', $post['updated']).'"></SPAN>'.$fields['when_2']['data'].'</SPAN>';
  383. }
  384. if (isset($post['lastuserid']) && @$options['whoview'])
  385. $fields['who_2']=qa_who_to_html(isset($userid) && ($post['lastuserid']==$userid), $post['lastuserid'], $usershtml, @$options['ipview'] ? $post['lastip'] : null, false);
  386. }
  387. // That's it!
  388. return $fields;
  389. }
  390. function qa_who_to_html($isbyuser, $postuserid, $usershtml, $ip=null, $microformats=false)
  391. /*
  392. Return array of split HTML (prefix, data, suffix) to represent author of post
  393. */
  394. {
  395. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  396. if (isset($postuserid) && isset($usershtml[$postuserid])) {
  397. $whohtml=$usershtml[$postuserid];
  398. if ($microformats)
  399. $whohtml='<SPAN CLASS="vcard author">'.$whohtml.'</SPAN>';
  400. } elseif ($isbyuser)
  401. $whohtml=qa_lang_html('main/me');
  402. else {
  403. $whohtml=qa_lang_html('main/anonymous');
  404. if (isset($ip))
  405. $whohtml=qa_ip_anchor_html($ip, $whohtml);
  406. }
  407. return qa_lang_html_sub_split('main/by_x', $whohtml);
  408. }
  409. function qa_when_to_html($timestamp, $fulldatedays)
  410. /*
  411. Return array of split HTML (prefix, data, suffix) to represent unix $timestamp, with the full date shown if it's
  412. more than $fulldatedays ago
  413. */
  414. {
  415. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  416. $interval=qa_opt('db_time')-$timestamp;
  417. if ( ($interval<0) || (isset($fulldatedays) && ($interval>(86400*$fulldatedays))) ) { // full style date
  418. $stampyear=date('Y', $timestamp);
  419. $thisyear=date('Y', qa_opt('db_time'));
  420. return array(
  421. 'data' => qa_html(strtr(qa_lang(($stampyear==$thisyear) ? 'main/date_format_this_year' : 'main/date_format_other_years'), array(
  422. '^day' => date((qa_lang('main/date_day_min_digits')==2) ? 'd' : 'j', $timestamp),
  423. '^month' => qa_lang('main/date_month_'.date('n', $timestamp)),
  424. '^year' => date((qa_lang('main/date_year_digits')==2) ? 'y' : 'Y', $timestamp),
  425. ))),
  426. );
  427. } else // ago-style date
  428. return qa_lang_html_sub_split('main/x_ago', qa_html(qa_time_to_string($interval)));
  429. }
  430. function qa_other_to_q_html_fields($question, $userid, $cookieid, $usershtml, $dummy, $options)
  431. /*
  432. Return array of mostly HTML to be passed to theme layer, to *link* to an answer, comment or edit on
  433. $question, as retrieved from database, with fields prefixed 'o' for the answer, comment or edit.
  434. $userid, $cookieid, $usershtml, $options are passed through to qa_post_html_fields().
  435. */
  436. {
  437. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  438. require_once QA_INCLUDE_DIR.'qa-app-updates.php';
  439. $fields=qa_post_html_fields($question, $userid, $cookieid, $usershtml, null, $options);
  440. switch ($question['obasetype'].'-'.@$question['oupdatetype']) {
  441. case 'Q-':
  442. $langstring='main/asked';
  443. break;
  444. case 'Q-'.QA_UPDATE_VISIBLE:
  445. $langstring=$question['hidden'] ? 'main/hidden' : 'main/reshown';
  446. break;
  447. case 'Q-'.QA_UPDATE_CLOSED:
  448. $langstring=isset($question['closedbyid']) ? 'main/closed' : 'main/reopened';
  449. break;
  450. case 'Q-'.QA_UPDATE_TAGS:
  451. $langstring='main/retagged';
  452. break;
  453. case 'Q-'.QA_UPDATE_CATEGORY:
  454. $langstring='main/recategorized';
  455. break;
  456. case 'A-':
  457. $langstring='main/answered';
  458. break;
  459. case 'A-'.QA_UPDATE_SELECTED:
  460. $langstring='main/answer_selected';
  461. break;
  462. case 'A-'.QA_UPDATE_VISIBLE:
  463. $langstring=$question['ohidden'] ? 'main/hidden' : 'main/answer_reshown';
  464. break;
  465. case 'A-'.QA_UPDATE_CONTENT:
  466. $langstring='main/answer_edited';
  467. break;
  468. case 'Q-'.QA_UPDATE_FOLLOWS:
  469. $langstring='main/asked_related_q';
  470. break;
  471. case 'C-':
  472. $langstring='main/commented';
  473. break;
  474. case 'C-'.QA_UPDATE_TYPE:
  475. $langstring='main/comment_moved';
  476. break;
  477. case 'C-'.QA_UPDATE_VISIBLE:
  478. $langstring=$question['ohidden'] ? 'main/hidden' : 'main/comment_reshown';
  479. break;
  480. case 'C-'.QA_UPDATE_CONTENT:
  481. $langstring='main/comment_edited';
  482. break;
  483. case 'Q-'.QA_UPDATE_CONTENT:
  484. default:
  485. $langstring='main/edited';
  486. break;
  487. }
  488. $fields['what']=qa_lang_html($langstring);
  489. if ( ($question['obasetype']!='Q') || (@$question['oupdatetype']==QA_UPDATE_FOLLOWS) )
  490. $fields['what_url']=qa_q_path_html($question['postid'], $question['title'], false, $question['obasetype'], $question['opostid']);
  491. if (@$options['contentview'] && !empty($question['ocontent'])) {
  492. $viewer=qa_load_viewer($question['ocontent'], $question['oformat']);
  493. $fields['content']=$viewer->get_html($question['ocontent'], $question['oformat'], array(
  494. 'blockwordspreg' => @$options['blockwordspreg'],
  495. 'showurllinks' => @$options['showurllinks'],
  496. 'linksnewwindow' => @$options['linksnewwindow'],
  497. ));
  498. }
  499. if (@$options['whenview'])
  500. $fields['when']=qa_when_to_html($question['otime'], @$options['fulldatedays']);
  501. if (@$options['whoview']) {
  502. $isbyuser=qa_post_is_by_user(array('userid' => $question['ouserid'], 'cookieid' => @$question['ocookieid']), $userid, $cookieid);
  503. $fields['who']=qa_who_to_html($isbyuser, $question['ouserid'], $usershtml, @$options['ipview'] ? @$question['oip'] : null, false);
  504. if (isset($question['opoints'])) {
  505. if (@$options['pointsview'])
  506. $fields['who']['points']=($question['opoints']==1) ? qa_lang_html_sub_split('main/1_point', '1', '1')
  507. : qa_lang_html_sub_split('main/x_points', qa_html(number_format($question['opoints'])));
  508. if (isset($options['pointstitle']))
  509. $fields['who']['title']=qa_get_points_title_html($question['opoints'], $options['pointstitle']);
  510. }
  511. if (isset($question['olevel']))
  512. $fields['who']['level']=qa_html(qa_user_level_string($question['olevel']));
  513. }
  514. unset($fields['flags']);
  515. if (@$options['flagsview'] && @$question['oflagcount'])
  516. $fields['flags']=($question['oflagcount']==1) ? qa_lang_html_sub_split('main/1_flag', '1', '1')
  517. : qa_lang_html_sub_split('main/x_flags', $question['oflagcount']);
  518. unset($fields['avatar']);
  519. if ((!QA_FINAL_EXTERNAL_USERS) && (@$options['avatarsize']>0))
  520. $fields['avatar']=qa_get_user_avatar_html($question['oflags'], $question['oemail'], $question['ohandle'],
  521. $question['oavatarblobid'], $question['oavatarwidth'], $question['oavatarheight'], $options['avatarsize']);
  522. return $fields;
  523. }
  524. function qa_any_to_q_html_fields($question, $userid, $cookieid, $usershtml, $dummy, $options)
  525. /*
  526. Based on the elements in $question, return HTML to be passed to theme layer to link
  527. to the question, or to an associated answer, comment or edit.
  528. */
  529. {
  530. if (isset($question['opostid']))
  531. $fields=qa_other_to_q_html_fields($question, $userid, $cookieid, $usershtml, null, $options);
  532. else
  533. $fields=qa_post_html_fields($question, $userid, $cookieid, $usershtml, null, $options);
  534. return $fields;
  535. }
  536. function qa_any_sort_by_date($questions)
  537. /*
  538. Each element in $questions represents a question and optional associated answer, comment or edit, as retrieved from database.
  539. Return it sorted by the date appropriate for each element, without removing duplicate references to the same question.
  540. */
  541. {
  542. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  543. require_once QA_INCLUDE_DIR.'qa-util-sort.php';
  544. foreach ($questions as $key => $question) // collect information about action referenced by each $question
  545. $questions[$key]['sort']=-(isset($question['opostid']) ? $question['otime'] : $question['created']);
  546. qa_sort_by($questions, 'sort');
  547. return $questions;
  548. }
  549. function qa_any_sort_and_dedupe($questions)
  550. /*
  551. Each element in $questions represents a question and optional associated answer, comment or edit, as retrieved from database.
  552. Return it sorted by the date appropriate for each element, and keep only the first item related to each question.
  553. */
  554. {
  555. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  556. require_once QA_INCLUDE_DIR.'qa-util-sort.php';
  557. foreach ($questions as $key => $question) { // collect information about action referenced by each $question
  558. if (isset($question['opostid'])) {
  559. $questions[$key]['_time']=$question['otime'];
  560. $questions[$key]['_type']=$question['obasetype'];
  561. $questions[$key]['_userid']=@$question['ouserid'];
  562. } else {
  563. $questions[$key]['_time']=$question['created'];
  564. $questions[$key]['_type']='Q';
  565. $questions[$key]['_userid']=$question['userid'];
  566. }
  567. $questions[$key]['sort']=-$questions[$key]['_time'];
  568. }
  569. qa_sort_by($questions, 'sort');
  570. $keepquestions=array(); // now remove duplicate references to same question
  571. foreach ($questions as $question) { // going in order from most recent to oldest
  572. $laterquestion=@$keepquestions[$question['postid']];
  573. if ((!isset($laterquestion)) || // keep this reference if there is no more recent one, or...
  574. (
  575. (@$laterquestion['oupdatetype']) && // the more recent reference was an edit
  576. (!@$question['oupdatetype']) && // this is not an edit
  577. ($laterquestion['_type']==$question['_type']) && // the same part (Q/A/C) is referenced here
  578. ($laterquestion['_userid']==$question['_userid']) && // the same user made the later edit
  579. (abs($laterquestion['_time']-$question['_time'])<300) // the edit was within 5 minutes of creation
  580. )
  581. )
  582. $keepquestions[$question['postid']]=$question;
  583. }
  584. return $keepquestions;
  585. }
  586. function qa_any_get_userids_handles($questions)
  587. /*
  588. Each element in $questions represents a question and optional associated answer, comment or edit, as retrieved from database.
  589. Return an array of elements (userid,handle) for the appropriate user for each element.
  590. */
  591. {
  592. $userids_handles=array();
  593. foreach ($questions as $question)
  594. if (isset($question['opostid']))
  595. $userids_handles[]=array(
  596. 'userid' => @$question['ouserid'],
  597. 'handle' => @$question['ohandle'],
  598. );
  599. else
  600. $userids_handles[]=array(
  601. 'userid' => @$question['userid'],
  602. 'handle' => @$question['handle'],
  603. );
  604. return $userids_handles;
  605. }
  606. function qa_html_convert_urls($html, $newwindow=false)
  607. /*
  608. Return $html with any URLs converted into links (with nofollow and in a new window if $newwindow)
  609. URL regular expressions can get crazy: http://internet.ls-la.net/folklore/url-regexpr.html
  610. So this is something quick and dirty that should do the trick in most cases
  611. */
  612. {
  613. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  614. return trim(preg_replace('/([^A-Za-z0-9])((http|https|ftp):\/\/([^\s&<>"\'\.])+\.([^\s&<>"\']|&amp;)+)/i', '\1<A HREF="\2" rel="nofollow"'.($newwindow ? ' target="_blank"' : '').'>\2</A>', ' '.$html.' '));
  615. }
  616. function qa_url_to_html_link($url, $newwindow=false)
  617. /*
  618. Return HTML representation of $url (if it appears to be an URL), linked with nofollow and in a new window if $newwindow
  619. */
  620. {
  621. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  622. if (is_numeric(strpos($url, '.'))) {
  623. $linkurl=$url;
  624. if (!is_numeric(strpos($linkurl, ':/')))
  625. $linkurl='http://'.$linkurl;
  626. return '<A HREF="'.qa_html($linkurl).'" rel="nofollow"'.($newwindow ? ' target="_blank"' : '').'>'.qa_html($url).'</A>';
  627. } else
  628. return qa_html($url);
  629. }
  630. function qa_insert_login_links($htmlmessage, $topage=null, $params=null)
  631. /*
  632. Return $htmlmessage with ^1...^6 substituted for links to log in or register or confirm email and come back to $topage with $params
  633. */
  634. {
  635. require_once QA_INCLUDE_DIR.'qa-app-users.php';
  636. $userlinks=qa_get_login_links(qa_path_to_root(), isset($topage) ? qa_path($topage, $params, '') : null);
  637. return strtr(
  638. $htmlmessage,
  639. array(
  640. '^1' => empty($userlinks['login']) ? '' : '<A HREF="'.qa_html($userlinks['login']).'">',
  641. '^2' => empty($userlinks['login']) ? '' : '</A>',
  642. '^3' => empty($userlinks['register']) ? '' : '<A HREF="'.qa_html($userlinks['register']).'">',
  643. '^4' => empty($userlinks['register']) ? '' : '</A>',
  644. '^5' => empty($userlinks['confirm']) ? '' : '<A HREF="'.qa_html($userlinks['confirm']).'">',
  645. '^6' => empty($userlinks['confirm']) ? '' : '</A>',
  646. )
  647. );
  648. }
  649. function qa_html_page_links($request, $start, $pagesize, $count, $prevnext, $params=array(), $hasmore=false, $anchor=null)
  650. /*
  651. Return structure to pass through to theme layer to show linked page numbers for $request.
  652. Q2A uses offset-based paging, i.e. pages are referenced in the URL by a 'start' parameter.
  653. $start is current offset, there are $pagesize items per page and $count items in total
  654. (unless $hasmore is true in which case there are at least $count items).
  655. Show links to $prevnext pages before and after this one and include $params in the URLs.
  656. */
  657. {
  658. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  659. $thispage=1+floor($start/$pagesize);
  660. $lastpage=ceil(min($count, 1+QA_MAX_LIMIT_START)/$pagesize);
  661. if (($thispage>1) || ($lastpage>$thispage)) {
  662. $links=array('label' => qa_lang_html('main/page_label'), 'items' => array());
  663. $keypages[1]=true;
  664. for ($page=max(2, min($thispage, $lastpage)-$prevnext); $page<=min($thispage+$prevnext, $lastpage); $page++)
  665. $keypages[$page]=true;
  666. $keypages[$lastpage]=true;
  667. if ($thispage>1)
  668. $links['items'][]=array(
  669. 'type' => 'prev',
  670. 'label' => qa_lang_html('main/page_prev'),
  671. 'page' => $thispage-1,
  672. 'ellipsis' => false,
  673. );
  674. foreach (array_keys($keypages) as $page)
  675. $links['items'][]=array(
  676. 'type' => ($page==$thispage) ? 'this' : 'jump',
  677. 'label' => $page,
  678. 'page' => $page,
  679. 'ellipsis' => (($page<$lastpage) || $hasmore) && (!isset($keypages[$page+1])),
  680. );
  681. if ($thispage<$lastpage)
  682. $links['items'][]=array(
  683. 'type' => 'next',
  684. 'label' => qa_lang_html('main/page_next'),
  685. 'page' => $thispage+1,
  686. 'ellipsis' => false,
  687. );
  688. foreach ($links['items'] as $key => $link)
  689. if ($link['page']!=$thispage) {
  690. $params['start']=$pagesize*($link['page']-1);
  691. $links['items'][$key]['url']=qa_path_html($request, $params, null, null, $anchor);
  692. }
  693. } else
  694. $links=null;
  695. return $links;
  696. }
  697. function qa_html_suggest_qs_tags($usingtags=false, $categoryrequest=null)
  698. /*
  699. Return HTML that suggests browsing all questions (in the category specified by $categoryrequest, if
  700. it's not null) and also popular tags if $usingtags is true
  701. */
  702. {
  703. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  704. $hascategory=strlen($categoryrequest);
  705. $htmlmessage=$hascategory ? qa_lang_html('main/suggest_category_qs') :
  706. ($usingtags ? qa_lang_html('main/suggest_qs_tags') : qa_lang_html('main/suggest_qs'));
  707. return strtr(
  708. $htmlmessage,
  709. array(
  710. '^1' => '<A HREF="'.qa_path_html('questions'.($hascategory ? ('/'.$categoryrequest) : '')).'">',
  711. '^2' => '</A>',
  712. '^3' => '<A HREF="'.qa_path_html('tags').'">',
  713. '^4' => '</A>',
  714. )
  715. );
  716. }
  717. function qa_html_suggest_ask($categoryid=null)
  718. /*
  719. Return HTML that suggest getting things started by asking a question, in $categoryid if not null
  720. */
  721. {
  722. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  723. $htmlmessage=qa_lang_html('main/suggest_ask');
  724. return strtr(
  725. $htmlmessage,
  726. array(
  727. '^1' => '<A HREF="'.qa_path_html('ask', strlen($categoryid) ? array('cat' => $categoryid) : null).'">',
  728. '^2' => '</A>',
  729. )
  730. );
  731. }
  732. function qa_category_navigation($categories, $selectedid=null, $pathprefix='', $showqcount=true, $pathparams=null)
  733. /*
  734. Return the navigation structure for the category hierarchical menu, with $selectedid selected,
  735. and links beginning with $pathprefix, and showing question counts if $showqcount
  736. */
  737. {
  738. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  739. $parentcategories=array();
  740. foreach ($categories as $category)
  741. $parentcategories[$category['parentid']][]=$category;
  742. $selecteds=qa_category_path($categories, $selectedid);
  743. return qa_category_navigation_sub($parentcategories, null, $selecteds, $pathprefix, $showqcount, $pathparams);
  744. }
  745. function qa_category_navigation_sub($parentcategories, $parentid, $selecteds, $pathprefix, $showqcount, $pathparams)
  746. /*
  747. Recursion function used by qa_category_navigation(...) to build hierarchical category menu.
  748. */
  749. {
  750. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  751. $navigation=array();
  752. if (!isset($parentid))
  753. $navigation['all']=array(
  754. 'url' => qa_path_html($pathprefix, $pathparams),
  755. 'label' => qa_lang_html('main/all_categories'),
  756. 'selected' => !count($selecteds),
  757. 'categoryid' => null,
  758. );
  759. if (isset($parentcategories[$parentid]))
  760. foreach ($parentcategories[$parentid] as $category)
  761. $navigation[qa_html($category['tags'])]=array(
  762. 'url' => qa_path_html($pathprefix.$category['tags'], $pathparams),
  763. 'label' => qa_html($category['title']),
  764. 'popup' => qa_html(@$category['content']),
  765. 'selected' => isset($selecteds[$category['categoryid']]),
  766. 'note' => $showqcount ? ('('.qa_html(number_format($category['qcount'])).')') : null,
  767. 'subnav' => qa_category_navigation_sub($parentcategories, $category['categoryid'], $selecteds, $pathprefix.$category['tags'].'/', $showqcount, $pathparams),
  768. 'categoryid' => $category['categoryid'],
  769. );
  770. return $navigation;
  771. }
  772. function qa_users_sub_navigation()
  773. /*
  774. Return the sub navigation structure for user listing pages
  775. */
  776. {
  777. if ((!QA_FINAL_EXTERNAL_USERS) && (qa_get_logged_in_level()>=QA_USER_LEVEL_MODERATOR)) {
  778. return array(
  779. 'users$' => array(
  780. 'url' => qa_path_html('users'),
  781. 'label' => qa_lang_html('main/highest_users'),
  782. ),
  783. 'users/special' => array(
  784. 'label' => qa_lang('users/special_users'),
  785. 'url' => qa_path_html('users/special'),
  786. ),
  787. 'users/blocked' => array(
  788. 'label' => qa_lang('users/blocked_users'),
  789. 'url' => qa_path_html('users/blocked'),
  790. ),
  791. );
  792. } else
  793. return null;
  794. }
  795. function qa_account_sub_navigation()
  796. /*
  797. Return the sub navigation structure for user account pages
  798. */
  799. {
  800. return array(
  801. 'account' => array(
  802. 'label' => qa_lang_html('misc/nav_my_details'),
  803. 'url' => qa_path_html('account'),
  804. ),
  805. 'favorites' => array(
  806. 'label' => qa_lang_html('misc/nav_my_favorites'),
  807. 'url' => qa_path_html('favorites'),
  808. ),
  809. );
  810. }
  811. function qa_custom_page_url($page)
  812. /*
  813. Return the url for $page retrieved from the database
  814. */
  815. {
  816. return ($page['flags'] & QA_PAGE_FLAGS_EXTERNAL)
  817. ? (is_numeric(strpos($page['tags'], '://')) ? $page['tags'] : qa_path_to_root().$page['tags'])
  818. : qa_path($page['tags']);
  819. }
  820. function qa_navigation_add_page(&$navigation, $page)
  821. /*
  822. Add an element to the $navigation array corresponding to $page retrieved from the database
  823. */
  824. {
  825. if (
  826. (!qa_permit_value_error($page['permit'], qa_get_logged_in_userid(), qa_get_logged_in_level(), qa_get_logged_in_flags())) || !isset($page['permit'])
  827. ) {
  828. $url=qa_custom_page_url($page);
  829. $navigation[($page['flags'] & QA_PAGE_FLAGS_EXTERNAL) ? ('custom-'.$page['pageid']) : $page['tags']]=array(
  830. 'url' => qa_html($url),
  831. 'label' => qa_html($page['title']),
  832. 'opposite' => ($page['nav']=='O'),
  833. 'target' => ($page['flags'] & QA_PAGE_FLAGS_NEW_WINDOW) ? '_blank' : null,
  834. 'selected' => ($page['flags'] & QA_PAGE_FLAGS_EXTERNAL) && ( ($url==qa_path(qa_request())) || ($url==qa_self_html()) ),
  835. );
  836. }
  837. }
  838. function qa_match_to_min_score($match)
  839. /*
  840. Convert an admin option for matching into a threshold for the score given by database search
  841. */
  842. {
  843. return 10-2*$match;
  844. }
  845. function qa_set_display_rules(&$qa_content, $effects)
  846. /*
  847. For each [target] => [source] in $effects, set up $qa_content so that the visibility of the DOM element ID
  848. target is equal to the checked state or boolean-casted value of the DOM element ID source. Each source can
  849. also combine multiple DOM IDs using JavaScript(=PHP) operators. This is twisted but rather convenient.
  850. */
  851. {
  852. $function='qa_display_rule_'.count(@$qa_content['script_lines']);
  853. $keysourceids=array();
  854. foreach ($effects as $target => $sources)
  855. if (preg_match_all('/[A-Za-z_][A-Za-z0-9_]*/', $sources, $matches)) // element names must be legal JS variable names
  856. foreach ($matches[0] as $element)
  857. $keysourceids[$element]=true;
  858. $funcscript=array("function ".$function."(first) {"); // build the Javascripts
  859. $loadscript=array();
  860. foreach ($keysourceids as $key => $dummy) {
  861. $funcscript[]="\tvar e=document.getElementById(".qa_js($key).");";
  862. $funcscript[]="\tvar ".$key."=e && (e.checked || (e.options && e.options[e.selectedIndex].value));";
  863. $loadscript[]="var e=document.getElementById(".qa_js($key).");";
  864. $loadscript[]="if (e) {";
  865. $loadscript[]="\t".$key."_oldonclick=e.onclick;";
  866. $loadscript[]="\te.onclick=function() {";
  867. $loadscript[]="\t\t".$function."(false);";
  868. $loadscript[]="\t\tif (typeof ".$key."_oldonclick=='function')";
  869. $loadscript[]="\t\t\t".$key."_oldonclick();";
  870. $loadscript[]="\t}";
  871. $loadscript[]="}";
  872. }
  873. foreach ($effects as $target => $sources) {
  874. $funcscript[]="\tvar e=document.getElementById(".qa_js($target).");";
  875. $funcscript[]="\tif (e) { var d=(".$sources."); if (first || (e.nodeName=='SPAN')) { e.style.display=d ? '' : 'none'; } else { if (d) { $(e).fadeIn(); } else { $(e).fadeOut(); } } }";
  876. }
  877. $funcscript[]="}";
  878. $loadscript[]=$function."(true);";
  879. $qa_content['script_lines'][]=$funcscript;
  880. $qa_content['script_onloads'][]=$loadscript;
  881. }
  882. function qa_set_up_tag_field(&$qa_content, &$field, $fieldname, $tags, $exampletags, $completetags, $maxtags)
  883. /*
  884. Set up $qa_content and $field (with HTML name $fieldname) for tag auto-completion, where
  885. $exampletags are suggestions and $completetags are simply the most popular ones. Show up to $maxtags.
  886. */
  887. {
  888. $template='<A HREF="#" CLASS="qa-tag-link" onClick="return qa_tag_click(this);">^</A>';
  889. $qa_content['script_rel'][]='qa-content/qa-ask.js?'.QA_VERSION;
  890. $qa_content['script_var']['qa_tag_template']=$template;
  891. $qa_content['script_var']['qa_tag_onlycomma']=(int)qa_opt('tag_separator_comma');
  892. $qa_content['script_var']['qa_tags_examples']=qa_html(implode(',', $exampletags));
  893. $qa_content['script_var']['qa_tags_complete']=qa_html(implode(',', $completetags));
  894. $qa_content['script_var']['qa_tags_max']=(int)$maxtags;
  895. $separatorcomma=qa_opt('tag_separator_comma');
  896. $field['label']=qa_lang_html($separatorcomma ? 'question/q_tags_comma_label' : 'question/q_tags_label');
  897. $field['value']=qa_html(implode($separatorcomma ? ', ' : ' ', $tags));
  898. $field['tags']='NAME="'.$fieldname.'" ID="tags" AUTOCOMPLETE="off" onKeyUp="qa_tag_hints();" onMouseUp="qa_tag_hints();"';
  899. $sdn=' STYLE="display:none;"';
  900. $field['note']=
  901. '<SPAN ID="tag_examples_title"'.(count($exampletags) ? '' : $sdn).'>'.qa_lang_html('question/example_tags').'</SPAN>'.
  902. '<SPAN ID="tag_complete_title"'.$sdn.'>'.qa_lang_html('question/matching_tags').'</SPAN><SPAN ID="tag_hints">';
  903. foreach ($exampletags as $tag)
  904. $field['note'].=str_replace('^', qa_html($tag), $template).' ';
  905. $field['note'].='</SPAN>';
  906. $field['note_force']=true;
  907. }
  908. function qa_get_tags_field_value($fieldname)
  909. /*
  910. Get a list of user-entered tags submitted from a field that was created with qa_set_up_tag_field(...)
  911. */
  912. {
  913. require_once QA_INCLUDE_DIR.'qa-util-string.php';
  914. $text=qa_post_text($fieldname);
  915. if (qa_opt('tag_separator_comma'))
  916. return array_unique(preg_split('/\s*,\s*/', trim(qa_strtolower(strtr($text, '/', ' '))), -1, PREG_SPLIT_NO_EMPTY));
  917. else
  918. return array_unique(qa_string_to_words($text, true, false, false, false));
  919. }
  920. function qa_set_up_category_field(&$qa_content, &$field, $fieldname, $navcategories, $categoryid, $allownone, $allownosub, $maxdepth=null, $excludecategoryid=null)
  921. /*
  922. Set up $qa_content and $field (with HTML name $fieldname) for hierarchical category navigation, with the initial value
  923. set to $categoryid (and $navcategories retrieved for $categoryid using qa_db_category_nav_selectspec(...)).
  924. If $allownone is true, it will allow selection of no category. If $allownosub is true, it will allow a category to be
  925. selected without selecting a subcategory within. Set $maxdepth to the maximum depth of category that can be selected
  926. (or null for no maximum) and $excludecategoryid to a category that should not be included.
  927. */
  928. {
  929. $pathcategories=qa_category_path($navcategories, $categoryid);
  930. $startpath='';
  931. foreach ($pathcategories as $category)
  932. $startpath.='/'.$category['categoryid'];
  933. if (!isset($maxdepth))
  934. $maxdepth=QA_CATEGORY_DEPTH;
  935. $maxdepth=min(QA_CATEGORY_DEPTH, $maxdepth);
  936. $qa_content['script_rel'][]='qa-content/qa-ask.js?'.QA_VERSION;
  937. $qa_content['script_onloads'][]='qa_category_select('.qa_js($fieldname).', '.qa_js($startpath).');';
  938. $qa_content['script_var']['qa_cat_exclude']=$excludecategoryid;
  939. $qa_content['script_var']['qa_cat_allownone']=(int)$allownone;
  940. $qa_content['script_var']['qa_cat_allownosub']=(int)$allownosub;
  941. $qa_content['script_var']['qa_cat_maxdepth']=$maxdepth;
  942. $field['type']='select';
  943. $field['tags']='NAME="'.$fieldname.'_0" ID="'.$fieldname.'_0" onChange="qa_category_select('.qa_js($fieldname).');"';
  944. $field['options']=array();
  945. // create the menu that will be shown if Javascript is disabled
  946. if ($allownone)
  947. $field['options']['']=qa_lang_html('main/no_category'); // this is also copied to first menu created by Javascript
  948. $keycategoryids=array();
  949. if ($allownosub) {
  950. $category=@$navcategories[$categoryid];
  951. $upcategory=$category;
  952. while (true) { // first get supercategories
  953. $upcategory=@$navcategories[$upcategory['parentid']];
  954. if (!isset($upcategory))
  955. break;
  956. $keycategoryids[$upcategory['categoryid']]=true;
  957. }
  958. $keycategoryids=array_reverse($keycategoryids, true);
  959. $depth=count($keycategoryids); // number of levels above
  960. if (isset($category)) {
  961. $depth++; // to count category itself
  962. foreach ($navcategories as $navcategory) // now get siblings and self
  963. if (!strcmp($navcategory['parentid'], $category['parentid']))
  964. $keycategoryids[$navcategory['categoryid']]=true;
  965. }
  966. if ($depth<$maxdepth)
  967. foreach ($navcategories as $navcategory) // now get children, if not too deep
  968. if (!strcmp($navcategory['parentid'], $categoryid))
  969. $keycategoryids[$navcategory['categoryid']]=true;
  970. } else {
  971. $haschildren=false;
  972. foreach ($navcategories as $navcategory) // check if it has any children
  973. if (!strcmp($navcategory['parentid'], $categoryid))
  974. $haschildren=true;
  975. if (!$haschildren)
  976. $keycategoryids[$categoryid]=true; // show this category if it has no children
  977. }
  978. foreach ($keycategoryids as $keycategoryid => $dummy)
  979. if (strcmp($keycategoryid, $excludecategoryid))
  980. $field['options'][$keycategoryid]=qa_category_path_html($navcategories, $keycategoryid);
  981. $field['value']=@$field['options'][$categoryid];
  982. $field['note']='<DIV ID="'.$fieldname.'_note"><NOSCRIPT STYLE="color:red;">'.qa_lang_html('question/category_js_note').'</NOSCRIPT></DIV>';
  983. }
  984. function qa_get_category_field_value($fieldname)
  985. /*
  986. Get the user-entered category id submitted from a field that was created with qa_set_up_category_field(...)
  987. */
  988. {
  989. for ($level=QA_CATEGORY_DEPTH; $level>=1; $level--) {
  990. $levelid=qa_post_text($fieldname.'_'.$level);
  991. if (strlen($levelid))
  992. return $levelid;
  993. }
  994. if (!isset($levelid)) { // no Javascript-generated menu was present so take original menu
  995. $levelid=qa_post_text($fieldname.'_0');
  996. if (strlen($levelid))
  997. return $levelid;
  998. }
  999. return null;
  1000. }
  1001. function qa_set_up_notify_fields(&$qa_content, &$fields, $basetype, $login_email, $innotify, $inemail, $errors_email, $fieldprefix='')
  1002. /*
  1003. Set up $qa_content and add to $fields to allow user to set if they want to be notified regarding their post.
  1004. $basetype is 'Q', 'A' or 'C' for question, answer or comment. $login_email is the email of logged in user,
  1005. or null if this is an anonymous post. $innotify, $inemail and $errors_email are from previous submission/validation.
  1006. */
  1007. {
  1008. $fields['notify']=array(
  1009. 'tags' => 'NAME="'.$fieldprefix.'notify"',
  1010. 'type' => 'checkbox',
  1011. 'value' => qa_html($innotify),
  1012. );
  1013. switch ($basetype) {
  1014. case 'Q':
  1015. $labelaskemail=qa_lang_html('question/q_notify_email');
  1016. $labelonly=qa_lang_html('question/q_notify_label');
  1017. $labelgotemail=qa_lang_html('question/q_notify_x_label');
  1018. break;
  1019. case 'A':
  1020. $labelaskemail=qa_lang_html('question/a_notify_email');
  1021. $labelonly=qa_lang_html('question/a_notify_label');
  1022. $labelgotemail=qa_lang_html('question/a_notify_x_label');
  1023. break;
  1024. case 'C':
  1025. $labelaskemail=qa_lang_html('question/c_notify_email');
  1026. $labelonly=qa_lang_html('question/c_notify_label');
  1027. $labelgotemail=qa_lang_html('question/c_notify_x_label');
  1028. break;
  1029. }
  1030. if (empty($login_email)) {
  1031. $fields['notify']['label']=
  1032. '<SPAN ID="'.$fieldprefix.'email_shown">'.$labelaskemail.'</SPAN>'.
  1033. '<SPAN ID="'.$fieldprefix.'email_hidden" STYLE="display:none;">'.$labelonly.'</SPAN>';
  1034. $fields['notify']['tags'].=' ID="'.$fieldpre

Large files files are truncated, but you can click here to view the full file