qa-theme-base.php 60.8 KB
Newer Older
Scott committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<?php
/*
	Question2Answer by Gideon Greenspan and contributors
	http://www.question2answer.org/

	File: qa-include/qa-theme-base.php
	Description: Default theme class, broken into lots of little functions for easy overriding


	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	More about this license: http://www.question2answer.org/license.php
*/

23 24 25 26
if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
	header('Location: ../');
	exit;
}
Scott committed
27 28 29 30 31 32 33 34 35 36 37 38 39


/*
	How do I make a theme which goes beyond CSS to actually modify the HTML output?

	Create a file named qa-theme.php in your new theme directory which defines a class qa_html_theme
	that extends this base class qa_html_theme_base. You can then override any of the methods below,
	referring back to the default method using double colon (qa_html_theme_base::) notation.

	Plugins can also do something similar by using a layer. For more information and to see some example
	code, please consult the online Q2A documentation.
*/

40 41
class qa_html_theme_base
{
42 43 44 45 46 47
	public $template;
	public $content;
	public $rooturl;
	public $request;
	public $isRTL; // (boolean) whether text direction is Right-To-Left

48
	protected $minifyHtml; // (boolean) whether to indent the HTML
49 50 51
	protected $indent = 0;
	protected $lines = 0;
	protected $context = array();
Scott committed
52

53 54
	// whether to use new block layout in rankings (true) or fall back to tables (false)
	protected $ranking_block_layout = false;
55 56
	// theme 'slug' to use as CSS class
	protected $theme;
Scott committed
57 58


Scott committed
59 60 61
	/**
	 * Initialize the object and assign local variables.
	 */
62 63 64 65 66 67 68
	public function __construct($template, $content, $rooturl, $request)
	{
		$this->template = $template;
		$this->content = $content;
		$this->rooturl = $rooturl;
		$this->request = $request;
		$this->isRTL = isset($content['direction']) && $content['direction'] === 'rtl';
69
		$this->minifyHtml = !empty($content['options']['minify_html']);
70
	}
71

72 73 74 75 76 77 78 79
	/**
	 * @deprecated PHP4-style constructor deprecated from 1.7; please use proper `__construct`
	 * function instead.
	 */
	public function qa_html_theme_base($template, $content, $rooturl, $request)
	{
		self::__construct($template, $content, $rooturl, $request);
	}
Scott committed
80

81

Scott committed
82 83 84 85 86
	/**
	 * Output each element in $elements on a separate line, with automatic HTML indenting.
	 * This should be passed markup which uses the <tag/> form for unpaired tags, to help keep
	 * track of indenting, although its actual output converts these to <tag> for W3C validation.
	 */
87 88 89
	public function output_array($elements)
	{
		foreach ($elements as $element) {
90
			$line = str_replace('/>', '>', $element);
91

92 93 94
			if ($this->minifyHtml) {
				if (strlen($line))
					echo $line."\n";
Scott committed
95
			}
96 97
			else {
				$delta = substr_count($element, '<') - substr_count($element, '<!') - 2*substr_count($element, '</') - substr_count($element, '/>');
98

99 100 101
				if ($delta < 0) {
					$this->indent += $delta;
				}
102

103 104 105 106 107
				echo str_repeat("\t", max(0, $this->indent)).$line."\n";

				if ($delta > 0) {
					$this->indent += $delta;
				}
Scott committed
108
			}
109 110

			$this->lines++;
Scott committed
111
		}
112
	}
Scott committed
113 114


Scott committed
115 116 117
	/**
	 * Output each passed parameter on a separate line - see output_array() comments.
	 */
118 119 120 121 122
	public function output() // other parameters picked up via func_get_args()
	{
		$args = func_get_args();
		$this->output_array($args);
	}
Scott committed
123 124


Scott committed
125 126 127 128
	/**
	 * Output $html at the current indent level, but don't change indent level based on the markup within.
	 * Useful for user-entered HTML which is unlikely to follow the rules we need to track indenting.
	 */
129 130 131 132 133
	public function output_raw($html)
	{
		if (strlen($html))
			echo str_repeat("\t", max(0, $this->indent)).$html."\n";
	}
Scott committed
134 135


Scott committed
136 137 138 139
	/**
	 * Output the three elements ['prefix'], ['data'] and ['suffix'] of $parts (if they're defined),
	 * with appropriate CSS classes based on $class, using $outertag and $innertag in the markup.
	 */
140 141 142 143 144 145 146 147 148 149 150 151 152
	public function output_split($parts, $class, $outertag='span', $innertag='span', $extraclass=null)
	{
		if (empty($parts) && strtolower($outertag) != 'td')
			return;

		$this->output(
			'<'.$outertag.' class="'.$class.(isset($extraclass) ? (' '.$extraclass) : '').'">',
			(strlen(@$parts['prefix']) ? ('<'.$innertag.' class="'.$class.'-pad">'.$parts['prefix'].'</'.$innertag.'>') : '').
			(strlen(@$parts['data']) ? ('<'.$innertag.' class="'.$class.'-data">'.$parts['data'].'</'.$innertag.'>') : '').
			(strlen(@$parts['suffix']) ? ('<'.$innertag.' class="'.$class.'-pad">'.$parts['suffix'].'</'.$innertag.'>') : ''),
			'</'.$outertag.'>'
		);
	}
Scott committed
153 154


Scott committed
155 156 157
	/**
	 * Set some context, which be accessed via $this->context for a function to know where it's being used on the page.
	 */
158 159 160 161
	public function set_context($key, $value)
	{
		$this->context[$key] = $value;
	}
Scott committed
162 163


Scott committed
164 165 166
	/**
	 * Clear some context (used at the end of the appropriate loop).
	 */
167 168 169 170
	public function clear_context($key)
	{
		unset($this->context[$key]);
	}
Scott committed
171 172


Scott committed
173 174 175 176
	/**
	 * Reorder the parts of the page according to the $parts array which contains part keys in their new order. Call this
	 * before main_parts(). See the docs for qa_array_reorder() in util/sort.php for the other parameters.
	 */
177 178
	public function reorder_parts($parts, $beforekey=null, $reorderrelative=true)
	{
Scott committed
179
		require_once QA_INCLUDE_DIR.'util/sort.php';
Scott committed
180

181 182
		qa_array_reorder($this->content, $parts, $beforekey, $reorderrelative);
	}
Scott committed
183 184


Scott committed
185 186 187
	/**
	 * Output the widgets (as provided in $this->content['widgets']) for $region and $place.
	 */
188 189 190 191 192 193 194 195 196 197 198 199
	public function widgets($region, $place)
	{
		if (count(@$this->content['widgets'][$region][$place])) {
			$this->output('<div class="qa-widgets-'.$region.' qa-widgets-'.$region.'-'.$place.'">');

			foreach ($this->content['widgets'][$region][$place] as $module) {
				$this->output('<div class="qa-widget-'.$region.' qa-widget-'.$region.'-'.$place.'">');
				$module->output_widget($region, $place, $this, $this->template, $this->request, $this->content);
				$this->output('</div>');
			}

			$this->output('</div>', '');
Scott committed
200
		}
201
	}
Scott committed
202

203 204
	/**
	 * Pre-output initialization. Immediately called after loading of the module. Content and template variables are
Scott committed
205
	 * already setup at this point. Useful to perform layer initialization in the earliest and safest stage possible.
206 207
	 */
	public function initialize() { }
Scott committed
208

Scott committed
209 210 211
	/**
	 * Post-output cleanup. For now, check that the indenting ended right, and if not, output a warning in an HTML comment.
	 */
212 213
	public function finish()
	{
214
		if ($this->indent !== 0 && !$this->minifyHtml) {
215 216 217 218 219
			echo "<!--\nIt's no big deal, but your HTML could not be indented properly. To fix, please:\n".
				"1. Use this->output() to output all HTML.\n".
				"2. Balance all paired tags like <td>...</td> or <div>...</div>.\n".
				"3. Use a slash at the end of unpaired tags like <img/> or <input/>.\n".
				"Thanks!\n-->\n";
Scott committed
220
		}
221
	}
Scott committed
222 223


224 225 226 227
//	From here on, we have a large number of class methods which output particular pieces of HTML markup
//	The calling chain is initiated from qa-page.php, or qa-ajax-*.php for refreshing parts of a page,
//	For most HTML elements, the name of the function is similar to the element's CSS class, for example:
//	search() outputs <div class="qa-search">, q_list() outputs <div class="qa-q-list">, etc...
Scott committed
228

229 230 231 232
	public function doctype()
	{
		$this->output('<!DOCTYPE html>');
	}
Scott committed
233

234 235 236
	public function html()
	{
		$attribution = '<!-- Powered by Question2Answer - http://www.question2answer.org/ -->';
Scott committed
237
		$extratags = isset($this->content['html_tags']) ? $this->content['html_tags'] : '';
238

239
		$this->output(
Scott committed
240
			'<html' . $extratags . '>',
241 242 243 244 245 246 247 248 249 250 251
			$attribution
		);

		$this->head();
		$this->body();

		$this->output(
			$attribution,
			'</html>'
		);
	}
Scott committed
252

253 254 255 256
	public function head()
	{
		$this->output(
			'<head>',
Scott committed
257
			'<meta charset="' . $this->content['charset'] . '"/>'
258 259 260 261 262 263 264 265 266 267 268 269
		);

		$this->head_title();
		$this->head_metas();
		$this->head_css();
		$this->head_links();
		$this->head_lines();
		$this->head_script();
		$this->head_custom();

		$this->output('</head>');
	}
Scott committed
270

271 272 273
	public function head_title()
	{
		$pagetitle = strlen($this->request) ? strip_tags(@$this->content['title']) : '';
Scott committed
274
		$headtitle = (strlen($pagetitle) ? "$pagetitle - " : '') . $this->content['site_title'];
Scott committed
275

Scott committed
276
		$this->output('<title>' . $headtitle . '</title>');
277
	}
Scott committed
278

279 280
	public function head_metas()
	{
Scott committed
281 282 283
		if (strlen(@$this->content['description'])) {
			$this->output('<meta name="description" content="' . $this->content['description'] . '"/>');
		}
Scott committed
284

Scott committed
285 286 287 288
		if (strlen(@$this->content['keywords'])) {
			// as far as I know, meta keywords have zero effect on search rankings or listings
			$this->output('<meta name="keywords" content="' . $this->content['keywords'] . '"/>');
		}
289 290 291 292
	}

	public function head_links()
	{
Scott committed
293 294 295
		if (isset($this->content['canonical'])) {
			$this->output('<link rel="canonical" href="' . $this->content['canonical'] . '"/>');
		}
296

Scott committed
297 298 299
		if (isset($this->content['feed']['url'])) {
			$this->output('<link rel="alternate" type="application/rss+xml" href="' . $this->content['feed']['url'] . '" title="'.@$this->content['feed']['label'].'"/>');
		}
300 301 302 303 304 305

		// convert page links to rel=prev and rel=next tags
		if (isset($this->content['page_links']['items'])) {
			foreach ($this->content['page_links']['items'] as $page_link) {
				if (in_array($page_link['type'], array('prev', 'next')))
					$this->output('<link rel="' . $page_link['type'] . '" href="' . $page_link['url'] . '" />');
Scott committed
306 307
			}
		}
308
	}
Scott committed
309

310 311 312
	public function head_script()
	{
		if (isset($this->content['script'])) {
Scott committed
313
			foreach ($this->content['script'] as $scriptline) {
314
				$this->output_raw($scriptline);
Scott committed
315
			}
316 317
		}
	}
Scott committed
318

319 320 321
	public function head_css()
	{
		$this->output('<link rel="stylesheet" href="'.$this->rooturl.$this->css_name().'"/>');
Scott committed
322

323
		if (isset($this->content['css_src'])) {
Scott committed
324
			foreach ($this->content['css_src'] as $css_src) {
325
				$this->output('<link rel="stylesheet" href="'.$css_src.'"/>');
Scott committed
326
			}
Scott committed
327 328
		}

329
		if (!empty($this->content['notices'])) {
Scott committed
330
			$this->output(
331 332 333
				'<style>',
				'.qa-body-js-on .qa-notice {display:none;}',
				'</style>'
Scott committed
334
			);
335 336
		}
	}
Scott committed
337

338 339 340 341
	public function css_name()
	{
		return 'qa-styles.css?'.QA_VERSION;
	}
Scott committed
342

343 344 345
	public function head_lines()
	{
		if (isset($this->content['head_lines'])) {
Scott committed
346
			foreach ($this->content['head_lines'] as $line) {
347
				$this->output_raw($line);
Scott committed
348
			}
Scott committed
349
		}
350
	}
Scott committed
351

352 353 354 355
	public function head_custom()
	{
		// abstract method
	}
Scott committed
356

357 358 359 360 361
	public function body()
	{
		$this->output('<body');
		$this->body_tags();
		$this->output('>');
Scott committed
362

363 364 365 366 367
		$this->body_script();
		$this->body_header();
		$this->body_content();
		$this->body_footer();
		$this->body_hidden();
Scott committed
368

369 370
		$this->output('</body>');
	}
Scott committed
371

372 373 374 375 376 377 378
	public function body_hidden()
	{
		$indent = $this->isRTL ? '9999px' : '-9999px';
		$this->output('<div style="position:absolute; left:'.$indent.'; top:-9999px;">');
		$this->waiting_template();
		$this->output('</div>');
	}
Scott committed
379

380 381 382 383
	public function waiting_template()
	{
		$this->output('<span id="qa-waiting-template" class="qa-waiting">...</span>');
	}
Scott committed
384

385 386 387 388 389 390 391 392 393
	public function body_script()
	{
		$this->output(
			'<script>',
			"var b=document.getElementsByTagName('body')[0];",
			"b.className=b.className.replace('qa-body-js-off', 'qa-body-js-on');",
			'</script>'
		);
	}
Scott committed
394

395 396
	public function body_header()
	{
Scott committed
397
		if (isset($this->content['body_header'])) {
398
			$this->output_raw($this->content['body_header']);
Scott committed
399
		}
400
	}
Scott committed
401

402 403
	public function body_footer()
	{
Scott committed
404
		if (isset($this->content['body_footer'])) {
405
			$this->output_raw($this->content['body_footer']);
Scott committed
406
		}
407
	}
Scott committed
408

409 410 411 412
	public function body_content()
	{
		$this->body_prefix();
		$this->notices();
Scott committed
413

414
		$this->output('<div class="qa-body-wrapper">', '');
Scott committed
415

416 417 418 419 420 421 422 423
		$this->widgets('full', 'top');
		$this->header();
		$this->widgets('full', 'high');
		$this->sidepanel();
		$this->main();
		$this->widgets('full', 'low');
		$this->footer();
		$this->widgets('full', 'bottom');
Scott committed
424

425
		$this->output('</div> <!-- END body-wrapper -->');
Scott committed
426

427 428
		$this->body_suffix();
	}
Scott committed
429

430 431
	public function body_tags()
	{
Scott committed
432
		$class = 'qa-template-' . qa_html($this->template);
433
		$class .= empty($this->theme) ? '' : ' qa-theme-' . qa_html($this->theme);
Scott committed
434

435
		if (isset($this->content['categoryids'])) {
Scott committed
436 437 438
			foreach ($this->content['categoryids'] as $categoryid) {
				$class .= ' qa-category-' . qa_html($categoryid);
			}
Scott committed
439 440
		}

Scott committed
441
		$this->output('class="' . $class . ' qa-body-js-off"');
442
	}
Scott committed
443

444 445 446 447
	public function body_prefix()
	{
		// abstract method
	}
Scott committed
448

449 450 451 452
	public function body_suffix()
	{
		// abstract method
	}
Scott committed
453

454 455 456
	public function notices()
	{
		if (!empty($this->content['notices'])) {
Scott committed
457
			foreach ($this->content['notices'] as $notice) {
458
				$this->notice($notice);
Scott committed
459
			}
Scott committed
460
		}
461
	}
Scott committed
462

463 464 465
	public function notice($notice)
	{
		$this->output('<div class="qa-notice" id="'.$notice['id'].'">');
Scott committed
466

467 468
		if (isset($notice['form_tags']))
			$this->output('<form '.$notice['form_tags'].'>');
Scott committed
469

470
		$this->output_raw($notice['content']);
Scott committed
471

472
		$this->output('<input '.$notice['close_tags'].' type="submit" value="X" class="qa-notice-close-button"/> ');
Scott committed
473

474 475 476
		if (isset($notice['form_tags'])) {
			$this->form_hidden_elements(@$notice['form_hidden']);
			$this->output('</form>');
Scott committed
477 478
		}

479 480
		$this->output('</div>');
	}
Scott committed
481

482 483 484
	public function header()
	{
		$this->output('<div class="qa-header">');
Scott committed
485

486 487 488 489
		$this->logo();
		$this->nav_user_search();
		$this->nav_main_sub();
		$this->header_clear();
Scott committed
490

491 492
		$this->output('</div> <!-- END qa-header -->', '');
	}
Scott committed
493

494 495 496 497 498
	public function nav_user_search()
	{
		$this->nav('user');
		$this->search();
	}
Scott committed
499

500 501 502 503 504
	public function nav_main_sub()
	{
		$this->nav('main');
		$this->nav('sub');
	}
Scott committed
505

506 507 508 509 510 511 512 513
	public function logo()
	{
		$this->output(
			'<div class="qa-logo">',
			$this->content['logo'],
			'</div>'
		);
	}
Scott committed
514

515 516 517
	public function search()
	{
		$search = $this->content['search'];
Scott committed
518

519 520 521 522 523
		$this->output(
			'<div class="qa-search">',
			'<form '.$search['form_tags'].'>',
			@$search['form_extra']
		);
Scott committed
524

525 526
		$this->search_field($search);
		$this->search_button($search);
Scott committed
527

528 529 530 531 532
		$this->output(
			'</form>',
			'</div>'
		);
	}
Scott committed
533

534 535 536 537
	public function search_field($search)
	{
		$this->output('<input type="text" '.$search['field_tags'].' value="'.@$search['value'].'" class="qa-search-field"/>');
	}
Scott committed
538

539 540 541 542
	public function search_button($search)
	{
		$this->output('<input type="submit" value="'.$search['button_label'].'" class="qa-search-button"/>');
	}
Scott committed
543

544 545 546
	public function nav($navtype, $level=null)
	{
		$navigation = @$this->content['navigation'][$navtype];
Scott committed
547

548 549
		if ($navtype == 'user' || isset($navigation)) {
			$this->output('<div class="qa-nav-'.$navtype.'">');
Scott committed
550

551 552 553 554 555 556 557 558 559
			if ($navtype == 'user')
				$this->logged_in();

			// reverse order of 'opposite' items since they float right
			foreach (array_reverse($navigation, true) as $key => $navlink) {
				if (@$navlink['opposite']) {
					unset($navigation[$key]);
					$navigation[$key] = $navlink;
				}
Scott committed
560 561
			}

562 563 564 565 566
			$this->set_context('nav_type', $navtype);
			$this->nav_list($navigation, 'nav-'.$navtype, $level);
			$this->nav_clear($navtype);
			$this->clear_context('nav_type');

Scott committed
567 568
			$this->output('</div>');
		}
569
	}
Scott committed
570

571 572 573
	public function nav_list($navigation, $class, $level=null)
	{
		$this->output('<ul class="qa-'.$class.'-list'.(isset($level) ? (' qa-'.$class.'-list-'.$level) : '').'">');
Scott committed
574

575
		$index = 0;
Scott committed
576

577 578 579 580
		foreach ($navigation as $key => $navlink) {
			$this->set_context('nav_key', $key);
			$this->set_context('nav_index', $index++);
			$this->nav_item($key, $navlink, $class, $level);
Scott committed
581 582
		}

583 584
		$this->clear_context('nav_key');
		$this->clear_context('nav_index');
Scott committed
585

586 587
		$this->output('</ul>');
	}
Scott committed
588

589 590 591 592 593 594 595
	public function nav_clear($navtype)
	{
		$this->output(
			'<div class="qa-nav-'.$navtype.'-clear">',
			'</div>'
		);
	}
Scott committed
596

597 598 599 600 601 602
	public function nav_item($key, $navlink, $class, $level=null)
	{
		$suffix = strtr($key, array( // map special character in navigation key
			'$' => '',
			'/' => '-',
		));
Scott committed
603

604 605 606 607 608 609
		$this->output('<li class="qa-'.$class.'-item'.(@$navlink['opposite'] ? '-opp' : '').
			(@$navlink['state'] ? (' qa-'.$class.'-'.$navlink['state']) : '').' qa-'.$class.'-'.$suffix.'">');
		$this->nav_link($navlink, $class);

		if (count(@$navlink['subnav']))
			$this->nav_list($navlink['subnav'], $class, 1+$level);
Scott committed
610

611 612
		$this->output('</li>');
	}
Scott committed
613

614 615 616
	public function nav_link($navlink, $class)
	{
		if (isset($navlink['url'])) {
Scott committed
617
			$this->output(
618 619 620 621 622 623
				'<a href="'.$navlink['url'].'" class="qa-'.$class.'-link'.
				(@$navlink['selected'] ? (' qa-'.$class.'-selected') : '').
				(@$navlink['favorited'] ? (' qa-'.$class.'-favorited') : '').
				'"'.(strlen(@$navlink['popup']) ? (' title="'.$navlink['popup'].'"') : '').
				(isset($navlink['target']) ? (' target="'.$navlink['target'].'"') : '').'>'.$navlink['label'].
				'</a>'
Scott committed
624 625
			);
		}
626 627 628 629 630 631 632
		else {
			$this->output(
				'<span class="qa-'.$class.'-nolink'.(@$navlink['selected'] ? (' qa-'.$class.'-selected') : '').
				(@$navlink['favorited'] ? (' qa-'.$class.'-favorited') : '').'"'.
				(strlen(@$navlink['popup']) ? (' title="'.$navlink['popup'].'"') : '').
				'>'.$navlink['label'].'</span>'
			);
Scott committed
633 634
		}

635 636 637
		if (strlen(@$navlink['note']))
			$this->output('<span class="qa-'.$class.'-note">'.$navlink['note'].'</span>');
	}
Scott committed
638

639 640 641 642
	public function logged_in()
	{
		$this->output_split(@$this->content['loggedin'], 'qa-logged-in', 'div');
	}
Scott committed
643

644 645 646 647 648 649 650
	public function header_clear()
	{
		$this->output(
			'<div class="qa-header-clear">',
			'</div>'
		);
	}
Scott committed
651

652 653 654 655 656 657 658 659 660 661 662 663
	public function sidepanel()
	{
		$this->output('<div class="qa-sidepanel">');
		$this->widgets('side', 'top');
		$this->sidebar();
		$this->widgets('side', 'high');
		$this->widgets('side', 'low');
		$this->output_raw(@$this->content['sidepanel']);
		$this->feed();
		$this->widgets('side', 'bottom');
		$this->output('</div>', '');
	}
Scott committed
664

665 666 667
	public function sidebar()
	{
		$sidebar = @$this->content['sidebar'];
Scott committed
668

669 670 671 672 673 674
		if (!empty($sidebar)) {
			$this->output('<div class="qa-sidebar">');
			$this->output_raw($sidebar);
			$this->output('</div>', '');
		}
	}
Scott committed
675

676 677 678 679 680 681 682 683
	public function feed()
	{
		$feed = @$this->content['feed'];

		if (!empty($feed)) {
			$this->output('<div class="qa-feed">');
			$this->output('<a href="'.$feed['url'].'" class="qa-feed-link">'.@$feed['label'].'</a>');
			$this->output('</div>');
Scott committed
684
		}
685
	}
Scott committed
686

687 688 689
	public function main()
	{
		$content = $this->content;
690
		$hidden = !empty($content['hidden']) ? ' qa-main-hidden' : '';
Scott committed
691
		$extratags = isset($this->content['main_tags']) ? $this->content['main_tags'] : '';
Scott committed
692

Scott committed
693
		$this->output('<div class="qa-main'.$hidden.'"'.$extratags.'>');
Scott committed
694

695
		$this->widgets('main', 'top');
Scott committed
696

697
		$this->page_title_error();
Scott committed
698

699
		$this->widgets('main', 'high');
Scott committed
700

701
		$this->main_parts($content);
Scott committed
702

703
		$this->widgets('main', 'low');
Scott committed
704

705 706
		$this->page_links();
		$this->suggest_next();
Scott committed
707

708
		$this->widgets('main', 'bottom');
Scott committed
709

710 711
		$this->output('</div> <!-- END qa-main -->', '');
	}
Scott committed
712

713 714 715 716 717 718 719
	public function page_title_error()
	{
		if (isset($this->content['title'])) {
			$favorite = isset($this->content['favorite']) ? $this->content['favorite'] : null;

			if (isset($favorite))
				$this->output('<form ' . $favorite['form_tags'] . '>');
Scott committed
720

721 722 723 724 725 726 727 728 729 730
			$this->output('<h1>');
			$this->favorite();
			$this->title();
			$this->output('</h1>');

			if (isset($favorite)) {
				$formhidden = isset($favorite['form_hidden']) ? $favorite['form_hidden'] : null;
				$this->form_hidden_elements($formhidden);
				$this->output('</form>');
			}
Scott committed
731
		}
732 733 734
		if (isset($this->content['error']))
			$this->error($this->content['error']);
	}
Scott committed
735

736 737 738 739 740 741 742 743
	public function favorite()
	{
		$favorite = isset($this->content['favorite']) ? $this->content['favorite'] : null;
		if (isset($favorite)) {
			$favoritetags = isset($favorite['favorite_tags']) ? $favorite['favorite_tags'] : '';
			$this->output('<span class="qa-favoriting" ' . $favoritetags . '>');
			$this->favorite_inner_html($favorite);
			$this->output('</span>');
Scott committed
744
		}
745
	}
Scott committed
746

747 748 749 750 751 752 753 754
	public function title()
	{
		$q_view = @$this->content['q_view'];

		// link title where appropriate
		$url = isset($q_view['url']) ? $q_view['url'] : false;

		if (isset($this->content['title'])) {
Scott committed
755
			$this->output(
756 757 758
				$url ? '<a href="'.$url.'">' : '',
				$this->content['title'],
				$url ? '</a>' : ''
Scott committed
759 760 761
			);
		}

762
		// add closed note in title
763
		if (!empty($q_view['closed']['state']))
764 765
			$this->output(' ['.$q_view['closed']['state'].']');
	}
Scott committed
766

767 768 769 770 771
	public function favorite_inner_html($favorite)
	{
		$this->favorite_button(@$favorite['favorite_add_tags'], 'qa-favorite');
		$this->favorite_button(@$favorite['favorite_remove_tags'], 'qa-unfavorite');
	}
Scott committed
772

773 774 775 776 777
	public function favorite_button($tags, $class)
	{
		if (isset($tags))
			$this->output('<input '.$tags.' type="submit" value="" class="'.$class.'-button"/> ');
	}
Scott committed
778

779 780 781 782 783 784 785 786 787 788
	public function error($error)
	{
		if (strlen($error)) {
			$this->output(
				'<div class="qa-error">',
				$error,
				'</div>'
			);
		}
	}
Scott committed
789

790 791 792 793 794
	public function main_parts($content)
	{
		foreach ($content as $key => $part) {
			$this->set_context('part', $key);
			$this->main_part($key, $part);
Scott committed
795 796
		}

797 798
		$this->clear_context('part');
	}
Scott committed
799

800 801 802 803 804 805
	public function main_part($key, $part)
	{
		$partdiv = (
			strpos($key, 'custom') === 0 ||
			strpos($key, 'form') === 0 ||
			strpos($key, 'q_list') === 0 ||
Scott committed
806
			(strpos($key, 'q_view') === 0 && !isset($this->content['form_q_edit'])) ||
807 808 809 810 811 812
			strpos($key, 'a_form') === 0 ||
			strpos($key, 'a_list') === 0 ||
			strpos($key, 'ranking') === 0 ||
			strpos($key, 'message_list') === 0 ||
			strpos($key, 'nav_list') === 0
		);
Scott committed
813

814 815
		if ($partdiv)
			$this->output('<div class="qa-part-'.strtr($key, '_', '-').'">'); // to help target CSS to page parts
Scott committed
816

817 818
		if (strpos($key, 'custom') === 0)
			$this->output_raw($part);
Scott committed
819

820 821
		elseif (strpos($key, 'form') === 0)
			$this->form($part);
Scott committed
822

823 824
		elseif (strpos($key, 'q_list') === 0)
			$this->q_list_and_form($part);
Scott committed
825

826 827
		elseif (strpos($key, 'q_view') === 0)
			$this->q_view($part);
Scott committed
828

829 830
		elseif (strpos($key, 'a_form') === 0)
			$this->a_form($part);
Scott committed
831

832 833
		elseif (strpos($key, 'a_list') === 0)
			$this->a_list($part);
Scott committed
834

835 836
		elseif (strpos($key, 'ranking') === 0)
			$this->ranking($part);
Scott committed
837

838 839
		elseif (strpos($key, 'message_list') === 0)
			$this->message_list_and_form($part);
Scott committed
840

841 842 843 844
		elseif (strpos($key, 'nav_list') === 0) {
			$this->part_title($part);
			$this->nav_list($part['nav'], $part['type'], 1);
		}
Scott committed
845

846 847 848
		if ($partdiv)
			$this->output('</div>');
	}
Scott committed
849

850 851 852
	public function footer()
	{
		$this->output('<div class="qa-footer">');
Scott committed
853

854 855 856
		$this->nav('footer');
		$this->attribution();
		$this->footer_clear();
Scott committed
857

858 859
		$this->output('</div> <!-- END qa-footer -->', '');
	}
Scott committed
860

861 862 863
	public function attribution()
	{
		// Hi there. I'd really appreciate you displaying this link on your Q2A site. Thank you - Gideon
Scott committed
864

865 866 867 868 869 870
		$this->output(
			'<div class="qa-attribution">',
			'Powered by <a href="http://www.question2answer.org/">Question2Answer</a>',
			'</div>'
		);
	}
Scott committed
871

872 873 874 875 876 877 878
	public function footer_clear()
	{
		$this->output(
			'<div class="qa-footer-clear">',
			'</div>'
		);
	}
Scott committed
879

880 881 882 883
	public function section($title)
	{
		$this->part_title(array('title' => $title));
	}
Scott committed
884

885 886 887 888 889
	public function part_title($part)
	{
		if (strlen(@$part['title']) || strlen(@$part['title_tags']))
			$this->output('<h2'.rtrim(' '.@$part['title_tags']).'>'.@$part['title'].'</h2>');
	}
Scott committed
890

891 892 893 894 895
	public function part_footer($part)
	{
		if (isset($part['footer']))
			$this->output($part['footer']);
	}
Scott committed
896

897 898 899 900
	public function form($form)
	{
		if (!empty($form)) {
			$this->part_title($form);
Scott committed
901

902 903
			if (isset($form['tags']))
				$this->output('<form '.$form['tags'].'>');
Scott committed
904

905
			$this->form_body($form);
Scott committed
906

907 908
			if (isset($form['tags']))
				$this->output('</form>');
Scott committed
909
		}
910
	}
Scott committed
911

912 913 914 915 916 917
	public function form_columns($form)
	{
		if (isset($form['ok']) || !empty($form['fields']) )
			$columns = ($form['style'] == 'wide') ? 3 : 1;
		else
			$columns = 0;
Scott committed
918

919 920
		return $columns;
	}
Scott committed
921

922 923 924 925 926 927 928 929 930 931
	public function form_spacer($form, $columns)
	{
		$this->output(
			'<tr>',
			'<td colspan="'.$columns.'" class="qa-form-'.$form['style'].'-spacer">',
			'&nbsp;',
			'</td>',
			'</tr>'
		);
	}
Scott committed
932

933 934 935 936
	public function form_body($form)
	{
		if (@$form['boxed'])
			$this->output('<div class="qa-form-table-boxed">');
Scott committed
937

938
		$columns = $this->form_columns($form);
Scott committed
939

940 941
		if ($columns)
			$this->output('<table class="qa-form-'.$form['style'].'-table">');
Scott committed
942

943 944 945
		$this->form_ok($form, $columns);
		$this->form_fields($form, $columns);
		$this->form_buttons($form, $columns);
Scott committed
946

947 948
		if ($columns)
			$this->output('</table>');
Scott committed
949

950
		$this->form_hidden($form);
Scott committed
951

952 953 954
		if (@$form['boxed'])
			$this->output('</div>');
	}
Scott committed
955

956 957 958
	public function form_ok($form, $columns)
	{
		if (!empty($form['ok'])) {
Scott committed
959 960
			$this->output(
				'<tr>',
961 962
				'<td colspan="'.$columns.'" class="qa-form-'.$form['style'].'-ok">',
				$form['ok'],
Scott committed
963 964 965 966
				'</td>',
				'</tr>'
			);
		}
967
	}
Scott committed
968

Scott committed
969 970 971 972
	/**
	 * Reorder the fields of $form according to the $keys array which contains the field keys in their new order. Call
	 * before any fields are output. See the docs for qa_array_reorder() in util/sort.php for the other parameters.
	 */
973 974
	public function form_reorder_fields(&$form, $keys, $beforekey=null, $reorderrelative=true)
	{
Scott committed
975
		require_once QA_INCLUDE_DIR.'util/sort.php';
Scott committed
976

977 978 979
		if (is_array($form['fields']))
			qa_array_reorder($form['fields'], $keys, $beforekey, $reorderrelative);
	}
Scott committed
980

981 982 983 984 985
	public function form_fields($form, $columns)
	{
		if (!empty($form['fields'])) {
			foreach ($form['fields'] as $key => $field) {
				$this->set_context('field_key', $key);
Scott committed
986

987 988 989 990
				if (@$field['type'] == 'blank')
					$this->form_spacer($form, $columns);
				else
					$this->form_field_rows($form, $columns, $field);
Scott committed
991 992
			}

993
			$this->clear_context('field_key');
Scott committed
994
		}
995
	}
Scott committed
996

997 998 999
	public function form_field_rows($form, $columns, $field)
	{
		$style = $form['style'];
Scott committed
1000

1001 1002 1003 1004
		if (isset($field['style'])) { // field has different style to most of form
			$style = $field['style'];
			$colspan = $columns;
			$columns = ($style == 'wide') ? 3 : 1;
Scott committed
1005
		}
1006 1007
		else
			$colspan = null;
Scott committed
1008

1009 1010 1011 1012 1013
		$prefixed = (@$field['type'] == 'checkbox') && ($columns == 1) && !empty($field['label']);
		$suffixed = (@$field['type'] == 'select' || @$field['type'] == 'number') && $columns == 1 && !empty($field['label']) && !@$field['loose'];
		$skipdata = @$field['tight'];
		$tworows = ($columns == 1) && (!empty($field['label'])) && (!$skipdata) &&
			( (!($prefixed||$suffixed)) || (!empty($field['error'])) || (!empty($field['note'])) );
Scott committed
1014

1015 1016 1017
		if (isset($field['id'])) {
			if ($columns == 1)
				$this->output('<tbody id="'.$field['id'].'">', '<tr>');
Scott committed
1018
			else
1019 1020 1021 1022
				$this->output('<tr id="'.$field['id'].'">');
		}
		else
			$this->output('<tr>');
Scott committed
1023

1024 1025
		if ($columns > 1 || !empty($field['label']))
			$this->form_label($field, $style, $columns, $prefixed, $suffixed, $colspan);
Scott committed
1026

1027 1028 1029 1030 1031 1032
		if ($tworows) {
			$this->output(
				'</tr>',
				'<tr>'
			);
		}
Scott committed
1033

1034 1035
		if (!$skipdata)
			$this->form_data($field, $style, $columns, !($prefixed||$suffixed), $colspan);
Scott committed
1036

1037
		$this->output('</tr>');
Scott committed
1038

1039 1040 1041
		if ($columns == 1 && isset($field['id']))
			$this->output('</tbody>');
	}
Scott committed
1042

1043 1044 1045
	public function form_label($field, $style, $columns, $prefixed, $suffixed, $colspan)
	{
		$extratags = '';
Scott committed
1046

1047 1048
		if ($columns > 1 && (@$field['type'] == 'select-radio' || @$field['rows'] > 1))
			$extratags .= ' style="vertical-align:top;"';
Scott committed
1049

1050 1051
		if (isset($colspan))
			$extratags .= ' colspan="'.$colspan.'"';
Scott committed
1052

1053
		$this->output('<td class="qa-form-'.$style.'-label"'.$extratags.'>');
Scott committed
1054

1055 1056 1057 1058
		if ($prefixed) {
			$this->output('<label>');
			$this->form_field($field, $style);
		}
Scott committed
1059

1060
		$this->output(@$field['label']);
Scott committed
1061

1062 1063
		if ($prefixed)
			$this->output('</label>');
Scott committed
1064

1065 1066 1067
		if ($suffixed) {
			$this->output('&nbsp;');
			$this->form_field($field, $style);
Scott committed
1068 1069
		}

1070 1071
		$this->output('</td>');
	}
Scott committed
1072

1073 1074 1075 1076 1077 1078
	public function form_data($field, $style, $columns, $showfield, $colspan)
	{
		if ($showfield || (!empty($field['error'])) || (!empty($field['note']))) {
			$this->output(
				'<td class="qa-form-'.$style.'-data"'.(isset($colspan) ? (' colspan="'.$colspan.'"') : '').'>'
			);
Scott committed
1079

1080 1081
			if ($showfield)
				$this->form_field($field, $style);
Scott committed
1082

1083 1084
			if (!empty($field['error'])) {
				if (@$field['note_force'])
Scott committed
1085 1086
					$this->form_note($field, $style, $columns);

1087
				$this->form_error($field, $style, $columns);
Scott committed
1088
			}
1089 1090
			elseif (!empty($field['note']))
				$this->form_note($field, $style, $columns);
Scott committed
1091

1092 1093 1094
			$this->output('</td>');
		}
	}
Scott committed
1095

1096 1097 1098
	public function form_field($field, $style)
	{
		$this->form_prefix($field, $style);
Scott committed
1099

1100
		$this->output_raw(@$field['html_prefix']);
Scott committed
1101

1102 1103 1104 1105
		switch (@$field['type']) {
			case 'checkbox':
				$this->form_checkbox($field, $style);
				break;
Scott committed
1106

1107 1108 1109
			case 'static':
				$this->form_static($field, $style);
				break;
Scott committed
1110

1111 1112 1113
			case 'password':
				$this->form_password($field, $style);
				break;
Scott committed
1114

1115 1116 1117
			case 'number':
				$this->form_number($field, $style);
				break;
Scott committed
1118

1119 1120 1121
			case 'select':
				$this->form_select($field, $style);
				break;
Scott committed
1122

1123 1124 1125
			case 'select-radio':
				$this->form_select_radio($field, $style);
				break;
Scott committed
1126

1127 1128 1129
			case 'image':
				$this->form_image($field, $style);
				break;
Scott committed
1130

1131 1132 1133
			case 'custom':
				$this->output_raw(@$field['html']);
				break;
Scott committed
1134

1135 1136 1137 1138 1139 1140
			default:
				if (@$field['type'] == 'textarea' || @$field['rows'] > 1)
					$this->form_text_multi_row($field, $style);
				else
					$this->form_text_single_row($field, $style);
				break;
Scott committed
1141 1142
		}

1143
		$this->output_raw(@$field['html_suffix']);
Scott committed
1144

1145 1146
		$this->form_suffix($field, $style);
	}
Scott committed
1147

Scott committed
1148 1149 1150 1151
	/**
	 * Reorder the buttons of $form according to the $keys array which contains the button keys in their new order. Call
	 * before any buttons are output. See the docs for qa_array_reorder() in util/sort.php for the other parameters.
	 */
1152 1153
	public function form_reorder_buttons(&$form, $keys, $beforekey=null, $reorderrelative=true)
	{
Scott committed
1154
		require_once QA_INCLUDE_DIR.'util/sort.php';
Scott committed
1155

1156 1157 1158
		if (is_array($form['buttons']))
			qa_array_reorder($form['buttons'], $keys, $beforekey, $reorderrelative);
	}
Scott committed
1159

1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
	public function form_buttons($form, $columns)
	{
		if (!empty($form['buttons'])) {
			$style = @$form['style'];

			if ($columns) {
				$this->output(
					'<tr>',
					'<td colspan="'.$columns.'" class="qa-form-'.$style.'-buttons">'
				);
			}
Scott committed
1171

1172 1173
			foreach ($form['buttons'] as $key => $button) {
				$this->set_context('button_key', $key);
Scott committed
1174

1175 1176 1177 1178 1179
				if (empty($button))
					$this->form_button_spacer($style);
				else {
					$this->form_button_data($button, $key, $style);
					$this->form_button_note($button, $style);
Scott committed
1180 1181 1182
				}
			}

1183
			$this->clear_context('button_key');
Scott committed
1184

1185
			if ($columns) {
Scott committed
1186
				$this->output(
1187 1188
					'</td>',
					'</tr>'
Scott committed
1189 1190 1191
				);
			}
		}
1192
	}
Scott committed
1193

1194 1195 1196
	public function form_button_data($button, $key, $style)
	{
		$baseclass = 'qa-form-'.$style.'-button qa-form-'.$style.'-button-'.$key;
Scott committed
1197

1198 1199 1200
		$this->output('<input'.rtrim(' '.@$button['tags']).' value="'.@$button['label'].'" title="'.@$button['popup'].'" type="submit"'.
			(isset($style) ? (' class="'.$baseclass.'"') : '').'/>');
	}
Scott committed
1201

1202 1203 1204 1205 1206 1207 1208 1209 1210
	public function form_button_note($button, $style)
	{
		if (!empty($button['note'])) {
			$this->output(
				'<span class="qa-form-'.$style.'-note">',
				$button['note'],
				'</span>',
				'<br/>'
			);
Scott committed
1211
		}
1212
	}
Scott committed
1213

1214 1215 1216 1217
	public function form_button_spacer($style)
	{
		$this->output('<span class="qa-form-'.$style.'-buttons-spacer">&nbsp;</span>');
	}
Scott committed
1218

1219 1220 1221 1222
	public function form_hidden($form)
	{
		$this->form_hidden_elements(@$form['hidden']);
	}
Scott committed
1223

1224 1225
	public function form_hidden_elements($hidden)
	{
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237
		if (empty($hidden))
			return;

		foreach ($hidden as $name => $value) {
			if (is_array($value)) {
				// new method of outputting tags
				$this->output('<input '.@$value['tags'].' type="hidden" value="'.@$value['value'].'"/>');
			}
			else {
				// old method
				$this->output('<input name="'.$name.'" type="hidden" value="'.$value.'"/>');
			}
Scott committed
1238
		}
1239
	}
Scott committed
1240

1241 1242 1243 1244 1245
	public function form_prefix($field, $style)
	{
		if (!empty($field['prefix']))
			$this->output('<span class="qa-form-'.$style.'-prefix">'.$field['prefix'].'</span>');
	}
Scott committed
1246

1247 1248 1249 1250 1251
	public function form_suffix($field, $style)
	{
		if (!empty($field['suffix']))
			$this->output('<span class="qa-form-'.$style.'-suffix">'.$field['suffix'].'</span>');
	}
Scott committed
1252

1253 1254 1255 1256
	public function form_checkbox($field, $style)
	{
		$this->output('<input '.@$field['tags'].' type="checkbox" value="1"'.(@$field['value'] ? ' checked' : '').' class="qa-form-'.$style.'-checkbox"/>');
	}
Scott committed
1257

1258 1259 1260 1261
	public function form_static($field, $style)
	{
		$this->output('<span class="qa-form-'.$style.'-static">'.@$field['value'].'</span>');
	}
Scott committed
1262

1263 1264 1265 1266
	public function form_password($field, $style)
	{
		$this->output('<input '.@$field['tags'].' type="password" value="'.@$field['value'].'" class="qa-form-'.$style.'-text"/>');
	}
Scott committed
1267

1268 1269 1270 1271
	public function form_number($field, $style)
	{
		$this->output('<input '.@$field['tags'].' type="text" value="'.@$field['value'].'" class="qa-form-'.$style.'-number"/>');
	}
Scott committed
1272

1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292
	/**
	 * Output a <select> element. The $field array may contain the following keys:
	 *   options: (required) a key-value array containing all the options in the select.
	 *   tags: any attributes to be added to the select.
	 *   value: the selected value from the 'options' parameter.
	 *   match_by: whether to match the 'value' (default) or 'key' of each option to determine if it is to be selected.
	 */
	public function form_select($field, $style)
	{
		$this->output('<select ' . (isset($field['tags']) ? $field['tags'] : '') . ' class="qa-form-' . $style . '-select">');

		// Only match by key if it is explicitly specified. Otherwise, for backwards compatibility, match by value
		$matchbykey = isset($field['match_by']) && $field['match_by'] === 'key';

		foreach ($field['options'] as $key => $value) {
			$selected = isset($field['value']) && (
				($matchbykey && $key === $field['value']) ||
				(!$matchbykey && $value === $field['value'])
			);
			$this->output('<option value="' . $key . '"' . ($selected ? ' selected' : '') . '>' . $value . '</option>');
Scott committed
1293 1294
		}

1295 1296
		$this->output('</select>');
	}
Scott committed
1297

1298 1299 1300
	public function form_select_radio($field, $style)
	{
		$radios = 0;
Scott committed
1301

1302 1303 1304
		foreach ($field['options'] as $tag => $value) {
			if ($radios++)
				$this->output('<br/>');
Scott committed
1305

1306
			$this->output('<input '.@$field['tags'].' type="radio" value="'.$tag.'"'.(($value == @$field['value']) ? ' checked' : '').' class="qa-form-'.$style.'-radio"/> '.$value);
Scott committed
1307
		}
1308
	}
Scott committed
1309

1310 1311 1312 1313
	public function form_image($field, $style)
	{
		$this->output('<div class="qa-form-'.$style.'-image">'.@$field['html'].'</div>');
	}
Scott committed
1314

1315 1316 1317 1318
	public function form_text_single_row($field, $style)
	{
		$this->output('<input '.@$field['tags'].' type="text" value="'.@$field['value'].'" class="qa-form-'.$style.'-text"/>');
	}
Scott committed
1319

1320 1321 1322 1323
	public function form_text_multi_row($field, $style)
	{
		$this->output('<textarea '.@$field['tags'].' rows="'.(int)$field['rows'].'" cols="40" class="qa-form-'.$style.'-text">'.@$field['value'].'</textarea>');
	}
Scott committed
1324

1325 1326 1327
	public function form_error($field, $style, $columns)
	{
		$tag = ($columns > 1) ? 'span' : 'div';
Scott committed
1328

1329 1330
		$this->output('<'.$tag.' class="qa-form-'.$style.'-error">'.$field['error'].'</'.$tag.'>');
	}
Scott committed
1331

1332 1333 1334
	public function form_note($field, $style, $columns)
	{
		$tag = ($columns > 1) ? 'span' : 'div';
Scott committed
1335

1336 1337
		$this->output('<'.$tag.' class="qa-form-'.$style.'-note">'.@$field['note'].'</'.$tag.'>');
	}
Scott committed
1338

1339 1340 1341
	public function ranking($ranking)
	{
		$this->part_title($ranking);
Scott committed
1342

1343 1344 1345
		if (!isset($ranking['type']))
			$ranking['type'] = 'items';
		$class = 'qa-top-'.$ranking['type'];
Scott committed
1346

1347 1348 1349
		if (!$this->ranking_block_layout) {
			// old, less semantic table layout
			$this->ranking_table($ranking, $class);
Scott committed
1350
		}
1351 1352 1353 1354 1355 1356
		else {
			// new block layout
			foreach ($ranking['items'] as $item) {
				$this->output('<span class="qa-ranking-item '.$class.'-item">');
				$this->ranking_item($item, $class);
				$this->output('</span>');
Scott committed
1357
			}
1358
		}
Scott committed
1359

1360 1361
		$this->part_footer($ranking);
	}
Scott committed
1362

1363 1364 1365 1366 1367 1368
	public function ranking_item($item, $class, $spacer=false) // $spacer is deprecated
	{
		if (!$this->ranking_block_layout) {
			// old table layout
			$this->ranking_table_item($item, $class, $spacer);
			return;
Scott committed
1369 1370
		}

1371 1372
		if (isset($item['count']))
			$this->ranking_count($item, $class);
Scott committed
1373

1374 1375
		if (isset($item['avatar']))
			$this->avatar($item, $class);
Scott committed
1376

1377
		$this->ranking_label($item, $class);
Scott committed
1378

1379 1380 1381
		if (isset($item['score']))
			$this->ranking_score($item, $class);
	}
Scott committed
1382

1383 1384 1385 1386 1387
	public function ranking_cell($content, $class)
	{
		$tag = $this->ranking_block_layout ? 'span': 'td';
		$this->output('<'.$tag.' class="'.$class.'">' . $content . '</'.$tag.'>');
	}
Scott committed
1388

1389 1390 1391 1392
	public function ranking_count($item, $class)
	{
		$this->ranking_cell($item['count'].' &#215;', $class.'-count');
	}
Scott committed
1393

1394 1395 1396 1397
	public function ranking_label($item, $class)
	{
		$this->ranking_cell($item['label'], $class.'-label');
	}
Scott committed
1398

1399 1400 1401 1402
	public function ranking_score($item, $class)
	{
		$this->ranking_cell($item['score'], $class.'-score');
	}
Scott committed
1403

1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423
	/**
	 * @deprecated Table-based layout of users/tags is deprecated from 1.7 onwards and may be
	 * removed in a future version. Themes can switch to the new layout by setting the member
	 * variable $ranking_block_layout to false.
	 */
	public function ranking_table($ranking, $class)
	{
		$rows = min($ranking['rows'], count($ranking['items']));

		if ($rows > 0) {
			$this->output('<table class="'.$class.'-table">');
			$columns = ceil(count($ranking['items']) / $rows);

			for ($row = 0; $row < $rows; $row++) {
				$this->set_context('ranking_row', $row);
				$this->output('<tr>');

				for ($column = 0; $column < $columns; $column++) {
					$this->set_context('ranking_column', $column);
					$this->ranking_table_item(@$ranking['items'][$column*$rows+$row], $class, $column>0);
Scott committed
1424
				}
1425 1426 1427

				$this->clear_context('ranking_column');
				$this->output('</tr>');
Scott committed
1428
			}
1429 1430
			$this->clear_context('ranking_row');
			$this->output('</table>');
Scott committed
1431
		}
1432
	}
Scott committed
1433

1434 1435 1436 1437 1438 1439 1440
	/**
	 * @deprecated See ranking_table above.
	 */
	public function ranking_table_item($item, $class, $spacer)
	{
		if ($spacer)
			$this->ranking_spacer($class);
Scott committed
1441

1442 1443 1444
		if (empty($item)) {
			$this->ranking_spacer($class);
			$this->ranking_spacer($class);
Scott committed
1445

1446 1447 1448
		} else {
			if (isset($item['count']))
				$this->ranking_count($item, $class);
Scott committed
1449

1450 1451
			if (isset($item['avatar']))
				$item['label'] = $item['avatar'].' '.$item['label'];
Scott committed
1452

1453
			$this->ranking_label($item, $class);
Scott committed
1454

1455 1456
			if (isset($item['score']))
				$this->ranking_score($item, $class);
Scott committed
1457
		}
1458
	}
Scott committed
1459

1460 1461 1462 1463 1464 1465 1466
	/**
	 * @deprecated See ranking_table above.
	 */
	public function ranking_spacer($class)
	{
		$this->output('<td class="'.$class.'-spacer">&nbsp;</td>');
	}
Scott committed
1467 1468


1469 1470 1471 1472
	public function message_list_and_form($list)
	{
		if (!empty($list)) {
			$this->part_title($list);
Scott committed
1473

1474
			$this->error(@$list['error']);
Scott committed
1475

1476 1477 1478 1479 1480
			if (!empty($list['form'])) {
				$this->output('<form '.$list['form']['tags'].'>');
				unset($list['form']['tags']); // we already output the tags before the messages
				$this->message_list_form($list);
			}
Scott committed
1481

1482
			$this->message_list($list);
Scott committed
1483

1484 1485
			if (!empty($list['form'])) {
				$this->output('</form>');
Scott committed
1486 1487
			}
		}
1488
	}
Scott committed
1489

1490 1491 1492 1493 1494 1495
	public function message_list_form($list)
	{
		if (!empty($list['form'])) {
			$this->output('<div class="qa-message-list-form">');
			$this->form($list['form']);
			$this->output('</div>');
Scott committed
1496
		}
1497
	}
Scott committed
1498

1499 1500 1501 1502
	public function message_list($list)
	{
		if (isset($list['messages'])) {
			$this->output('<div class="qa-message-list" '.@$list['tags'].'>');
Scott committed
1503

1504 1505
			foreach ($list['messages'] as $message)
				$this->message_item($message);
Scott committed
1506

1507
			$this->output('</div> <!-- END qa-message-list -->', '');
Scott committed
1508
		}
1509
	}
Scott committed
1510

1511 1512 1513 1514 1515 1516 1517 1518
	public function message_item($message)
	{
		$this->output('<div class="qa-message-item" '.@$message['tags'].'>');
		$this->message_content($message);
		$this->post_avatar_meta($message, 'qa-message');
		$this->message_buttons($message);
		$this->output('</div> <!-- END qa-message-item -->', '');
	}
Scott committed
1519

1520 1521 1522 1523 1524 1525
	public function message_content($message)
	{
		if (!empty($message['content'])) {
			$this->output('<div class="qa-message-content">');
			$this->output_raw($message['content']);
			$this->output('</div>');
Scott committed
1526
		}
1527
	}
Scott committed
1528

1529 1530 1531 1532 1533 1534
	public function message_buttons($item)
	{
		if (!empty($item['form'])) {
			$this->output('<div class="qa-message-buttons">');
			$this->form($item['form']);
			$this->output('</div>');
Scott committed
1535
		}
1536
	}
Scott committed
1537

1538 1539 1540
	public function list_vote_disabled($items)
	{
		$disabled = false;
Scott committed
1541

1542 1543
		if (count($items)) {
			$disabled = true;
Scott committed
1544

1545 1546 1547
			foreach ($items as $item) {
				if (@$item['vote_on_page'] != 'disabled')
					$disabled = false;
Scott committed
1548 1549 1550
			}
		}

1551 1552
		return $disabled;
	}
Scott committed
1553

1554 1555 1556 1557
	public function q_list_and_form($q_list)
	{
		if (empty($q_list))
			return;
Scott committed
1558

1559
		$this->part_title($q_list);
Scott committed
1560

1561 1562
		if (!empty($q_list['form']))
			$this->output('<form '.$q_list['form']['tags'].'>');
Scott committed
1563

1564
		$this->q_list($q_list);
Scott committed
1565

1566 1567 1568 1569
		if (!empty($q_list['form'])) {
			unset($q_list['form']['tags']); // we already output the tags before the qs
			$this->q_list_form($q_list);
			$this->output('</form>');
Scott committed
1570 1571
		}

1572 1573
		$this->part_footer($q_list);
	}
Scott committed
1574

1575 1576 1577 1578 1579 1580
	public function q_list_form($q_list)
	{
		if (!empty($q_list['form'])) {
			$this->output('<div class="qa-q-list-form">');
			$this->form($q_list['form']);
			$this->output('</div>');
Scott committed
1581
		}
1582
	}
Scott committed
1583

1584 1585 1586 1587 1588 1589
	public function q_list($q_list)
	{
		if (isset($q_list['qs'])) {
			$this->output('<div class="qa-q-list'.($this->list_vote_disabled($q_list['qs']) ? ' qa-q-list-vote-disabled' : '').'">', '');
			$this->q_list_items($q_list['qs']);
			$this->output('</div> <!-- END qa-q-list -->', '');
Scott committed
1590
		}
1591
	}
Scott committed
1592

1593 1594 1595 1596 1597
	public function q_list_items($q_items)
	{
		foreach ($q_items as $q_item)
			$this->q_list_item($q_item);
	}
Scott committed
1598

1599 1600 1601
	public function q_list_item($q_item)
	{
		$this->output('<div class="qa-q-list-item'.rtrim(' '.@$q_item['classes']).'" '.@$q_item['tags'].'>');
Scott committed
1602

1603 1604 1605
		$this->q_item_stats($q_item);
		$this->q_item_main($q_item);
		$this->q_item_clear();
Scott committed
1606

1607 1608
		$this->output('</div> <!-- END qa-q-list-item -->', '');
	}
Scott committed
1609

1610 1611 1612
	public function q_item_stats($q_item)
	{
		$this->output('<div class="qa-q-item-stats">');
Scott committed
1613

1614 1615
		$this->voting($q_item);
		$this->a_count($q_item);
Scott committed
1616

1617 1618 1619 1620 1621 1622
		$this->output('</div>');
	}

	public function q_item_main($q_item)
	{
		$this->output('<div class="qa-q-item-main">');
Scott committed
1623

1624 1625 1626
		$this->view_count($q_item);
		$this->q_item_title($q_item);
		$this->q_item_content($q_item);
Scott committed
1627

1628 1629 1630
		$this->post_avatar_meta($q_item, 'qa-q-item');
		$this->post_tags($q_item, 'qa-q-item');
		$this->q_item_buttons($q_item);
Scott committed
1631

1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648
		$this->output('</div>');
	}

	public function q_item_clear()
	{
		$this->output(
			'<div class="qa-q-item-clear">',
			'</div>'
		);
	}

	public function q_item_title($q_item)
	{
		$this->output(
			'<div class="qa-q-item-title">',
			'<a href="'.$q_item['url'].'">'.$q_item['title'].'</a>',
			// add closed note in title
1649
			empty($q_item['closed']['state']) ? '' : ' ['.$q_item['closed']['state'].']',
1650 1651 1652 1653 1654 1655 1656 1657 1658
			'</div>'
		);
	}

	public function q_item_content($q_item)
	{
		if (!empty($q_item['content'])) {
			$this->output('<div class="qa-q-item-content">');
			$this->output_raw($q_item['content']);
Scott committed
1659 1660
			$this->output('</div>');
		}
1661
	}
Scott committed
1662

1663 1664 1665 1666 1667 1668
	public function q_item_buttons($q_item)
	{
		if (!empty($q_item['form'])) {
			$this->output('<div class="qa-q-item-buttons">');
			$this->form($q_item['form']);
			$this->output('</div>');
Scott committed
1669
		}
1670
	}
Scott committed
1671

1672 1673 1674 1675 1676 1677
	public function voting($post)
	{
		if (isset($post['vote_view'])) {
			$this->output('<div class="qa-voting '.(($post['vote_view'] == 'updown') ? 'qa-voting-updown' : 'qa-voting-net').'" '.@$post['vote_tags'].'>');
			$this->voting_inner_html($post);
			$this->output('</div>');
Scott committed
1678
		}
1679
	}
Scott committed
1680

1681 1682 1683 1684 1685 1686
	public function voting_inner_html($post)
	{
		$this->vote_buttons($post);
		$this->vote_count($post);
		$this->vote_clear();
	}
Scott committed
1687

1688 1689 1690
	public function vote_buttons($post)
	{
		$this->output('<div class="qa-vote-buttons '.(($post['vote_view'] == 'updown') ? 'qa-vote-buttons-updown' : 'qa-vote-buttons-net').'">');
Scott committed
1691

1692
		switch (@$post['vote_state'])
Scott committed
1693
		{
1694 1695 1696
			case 'voted_up':
				$this->post_hover_button($post, 'vote_up_tags', '+', 'qa-vote-one-button qa-voted-up');
				break;
Scott committed
1697

1698 1699 1700
			case 'voted_up_disabled':
				$this->post_disabled_button($post, 'vote_up_tags', '+', 'qa-vote-one-button qa-vote-up');
				break;
Scott committed
1701

1702 1703 1704
			case 'voted_down':
				$this->post_hover_button($post, 'vote_down_tags', '&ndash;', 'qa-vote-one-button qa-voted-down');
				break;
Scott committed
1705

1706 1707 1708
			case 'voted_down_disabled':
				$this->post_disabled_button($post, 'vote_down_tags', '&ndash;', 'qa-vote-one-button qa-vote-down');
				break;
Scott committed
1709

1710 1711 1712 1713
			case 'up_only':
				$this->post_hover_button($post, 'vote_up_tags', '+', 'qa-vote-first-button qa-vote-up');
				$this->post_disabled_button($post, 'vote_down_tags', '', 'qa-vote-second-button qa-vote-down');
				break;
Scott committed
1714

1715 1716 1717 1718
			case 'enabled':
				$this->post_hover_button($post, 'vote_up_tags', '+', 'qa-vote-first-button qa-vote-up');
				$this->post_hover_button($post, 'vote_down_tags', '&ndash;', 'qa-vote-second-button qa-vote-down');
				break;
Scott committed
1719

1720 1721 1722 1723 1724
			default:
				$this->post_disabled_button($post, 'vote_up_tags', '', 'qa-vote-first-button qa-vote-up');
				$this->post_disabled_button($post, 'vote_down_tags', '', 'qa-vote-second-button qa-vote-down');
				break;
		}
Scott committed
1725

1726 1727
		$this->output('</div>');
	}
Scott committed
1728

1729 1730 1731 1732
	public function vote_count($post)
	{
		// You can also use $post['upvotes_raw'], $post['downvotes_raw'], $post['netvotes_raw'] to get
		// raw integer vote counts, for graphing or showing in other non-textual ways
Scott committed
1733

1734 1735 1736 1737 1738
		$this->output('<div class="qa-vote-count '.(($post['vote_view'] == 'updown') ? 'qa-vote-count-updown' : 'qa-vote-count-net').'"'.@$post['vote_count_tags'].'>');

		if ($post['vote_view'] == 'updown') {
			$this->output_split($post['upvotes_view'], 'qa-upvote-count');
			$this->output_split($post['downvotes_view'], 'qa-downvote-count');
Scott committed
1739
		}
1740 1741
		else
			$this->output_split($post['netvotes_view'], 'qa-netvote-count');
Scott committed
1742

1743 1744
		$this->output('</div>');
	}
Scott committed
1745

1746 1747 1748 1749 1750 1751 1752
	public function vote_clear()
	{
		$this->output(
			'<div class="qa-vote-clear">',
			'</div>'
		);
	}
Scott committed
1753

1754 1755 1756
	public function a_count($post)
	{
		// You can also use $post['answers_raw'] to get a raw integer count of answers
Scott committed
1757

1758 1759 1760
		$this->output_split(@$post['answers'], 'qa-a-count', 'span', 'span',
			@$post['answer_selected'] ? 'qa-a-count-selected' : (@$post['answers_raw'] ? null : 'qa-a-count-zero'));
	}
Scott committed
1761

1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773
	public function view_count($post)
	{
		// You can also use $post['views_raw'] to get a raw integer count of views

		$this->output_split(@$post['views'], 'qa-view-count');
	}

	public function avatar($item, $class, $prefix=null)
	{
		if (isset($item['avatar'])) {
			if (isset($prefix))
				$this->output($prefix);
Scott committed
1774 1775

			$this->output(
1776 1777 1778
				'<span class="'.$class.'-avatar">',
				$item['avatar'],
				'</span>'
Scott committed
1779 1780
			);
		}
1781
	}
Scott committed
1782

1783 1784 1785
	public function a_selection($post)
	{
		$this->output('<div class="qa-a-selection">');
Scott committed
1786

1787 1788 1789 1790 1791 1792
		if (isset($post['select_tags']))
			$this->post_hover_button($post, 'select_tags', '', 'qa-a-select');
		elseif (isset($post['unselect_tags']))
			$this->post_hover_button($post, 'unselect_tags', '', 'qa-a-unselect');
		elseif ($post['selected'])
			$this->output('<div class="qa-a-selected">&nbsp;</div>');
Scott committed
1793

1794 1795
		if (isset($post['select_text']))
			$this->output('<div class="qa-a-selected-text">'.@$post['select_text'].'</div>');
Scott committed
1796

1797 1798
		$this->output('</div>');
	}
Scott committed
1799

1800 1801 1802 1803 1804
	public function post_hover_button($post, $element, $value, $class)
	{
		if (isset($post[$element]))
			$this->output('<input '.$post[$element].' type="submit" value="'.$value.'" class="'.$class.'-button"/> ');
	}
Scott committed
1805

1806 1807 1808 1809 1810
	public function post_disabled_button($post, $element, $value, $class)
	{
		if (isset($post[$element]))
			$this->output('<input '.$post[$element].' type="submit" value="'.$value.'" class="'.$class.'-disabled" disabled="disabled"/> ');
	}
Scott committed
1811

1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826
	public function post_avatar_meta($post, $class, $avatarprefix=null, $metaprefix=null, $metaseparator='<br/>')
	{
		$this->output('<span class="'.$class.'-avatar-meta">');
		$this->avatar($post, $class, $avatarprefix);
		$this->post_meta($post, $class, $metaprefix, $metaseparator);
		$this->output('</span>');
	}

	/**
	 * @deprecated Deprecated from 1.7; please use avatar() instead.
	 */
	public function post_avatar($post, $class, $prefix=null)
	{
		$this->avatar($post, $class, $prefix);
	}
Scott committed
1827

1828 1829 1830
	public function post_meta($post, $class, $prefix=null, $separator='<br/>')
	{
		$this->output('<span class="'.$class.'-meta">');
Scott committed
1831

1832 1833
		if (isset($prefix))
			$this->output($prefix);
Scott committed
1834

1835
		$order = explode('^', @$post['meta_order']);
Scott committed
1836

1837 1838 1839 1840 1841
		foreach ($order as $element) {
			switch ($element) {
				case 'what':
					$this->post_meta_what($post, $class);
					break;
Scott committed
1842

1843 1844 1845
				case 'when':
					$this->post_meta_when($post, $class);
					break;
Scott committed
1846

1847 1848 1849
				case 'where':
					$this->post_meta_where($post, $class);
					break;
Scott committed
1850

1851 1852 1853 1854
				case 'who':
					$this->post_meta_who($post, $class);
					break;
			}
Scott committed
1855 1856
		}

1857
		$this->post_meta_flags($post, $class);
Scott committed
1858

1859 1860
		if (!empty($post['what_2'])) {
			$this->output($separator);
Scott committed
1861 1862 1863 1864

			foreach ($order as $element) {
				switch ($element) {
					case 'what':
1865
						$this->output('<span class="'.$class.'-what">'.$post['what_2'].'</span>');
Scott committed
1866 1867 1868
						break;

					case 'when':
1869
						$this->output_split(@$post['when_2'], $class.'-when');
Scott committed
1870 1871 1872
						break;

					case 'who':
1873
						$this->output_split(@$post['who_2'], $class.'-who');
Scott committed
1874 1875 1876 1877 1878
						break;
				}
			}
		}

1879 1880
		$this->output('</span>');
	}
Scott committed
1881

1882 1883 1884 1885 1886 1887
	public function post_meta_what($post, $class)
	{
		if (isset($post['what'])) {
			$classes = $class.'-what';
			if (@$post['what_your'])
				$classes .= ' '.$class.'-what-your';
Scott committed
1888

1889 1890 1891 1892
			if (isset($post['what_url']))
				$this->output('<a href="'.$post['what_url'].'" class="'.$classes.'">'.$post['what'].'</a>');
			else
				$this->output('<span class="'.$classes.'">'.$post['what'].'</span>');
Scott committed
1893
		}
1894
	}
Scott committed
1895

1896 1897 1898 1899
	public function post_meta_when($post, $class)
	{
		$this->output_split(@$post['when'], $class.'-when');
	}
Scott committed
1900

1901 1902 1903 1904
	public function post_meta_where($post, $class)
	{
		$this->output_split(@$post['where'], $class.'-where');
	}
Scott committed
1905

1906 1907 1908 1909
	public function post_meta_who($post, $class)
	{
		if (isset($post['who'])) {
			$this->output('<span class="'.$class.'-who">');
Scott committed
1910

1911 1912
			if (strlen(@$post['who']['prefix']))
				$this->output('<span class="'.$class.'-who-pad">'.$post['who']['prefix'].'</span>');
Scott committed
1913

1914 1915
			if (isset($post['who']['data']))
				$this->output('<span class="'.$class.'-who-data">'.$post['who']['data'].'</span>');
Scott committed
1916

1917 1918
			if (isset($post['who']['title']))
				$this->output('<span class="'.$class.'-who-title">'.$post['who']['title'].'</span>');
Scott committed
1919

1920
			// You can also use $post['level'] to get the author's privilege level (as a string)
Scott committed
1921

1922 1923 1924 1925
			if (isset($post['who']['points'])) {
				$post['who']['points']['prefix'] = '('.$post['who']['points']['prefix'];
				$post['who']['points']['suffix'] .= ')';
				$this->output_split($post['who']['points'], $class.'-who-points');
Scott committed
1926 1927
			}

1928 1929
			if (strlen(@$post['who']['suffix']))
				$this->output('<span class="'.$class.'-who-pad">'.$post['who']['suffix'].'</span>');
Scott committed
1930

1931
			$this->output('</span>');
Scott committed
1932
		}
1933
	}
Scott committed
1934

1935 1936 1937 1938
	public function post_meta_flags($post, $class)
	{
		$this->output_split(@$post['flags'], $class.'-flags');
	}
Scott committed
1939

1940 1941 1942 1943 1944 1945
	public function post_tags($post, $class)
	{
		if (!empty($post['q_tags'])) {
			$this->output('<div class="'.$class.'-tags">');
			$this->post_tag_list($post, $class);
			$this->output('</div>');
Scott committed
1946
		}
1947
	}
Scott committed
1948

1949 1950 1951
	public function post_tag_list($post, $class)
	{
		$this->output('<ul class="'.$class.'-tag-list">');
Scott committed
1952

1953 1954
		foreach ($post['q_tags'] as $taghtml)
			$this->post_tag_item($taghtml, $class);
Scott committed
1955

1956 1957
		$this->output('</ul>');
	}
Scott committed
1958

1959 1960 1961 1962
	public function post_tag_item($taghtml, $class)
	{
		$this->output('<li class="'.$class.'-tag-item">'.$taghtml.'</li>');
	}
Scott committed
1963

1964 1965 1966
	public function page_links()
	{
		$page_links = @$this->content['page_links'];
Scott committed
1967

1968 1969
		if (!empty($page_links)) {
			$this->output('<div class="qa-page-links">');
Scott committed
1970

1971 1972 1973
			$this->page_links_label(@$page_links['label']);
			$this->page_links_list(@$page_links['items']);
			$this->page_links_clear();
Scott committed
1974

1975
			$this->output('</div>');
Scott committed
1976
		}
1977
	}
Scott committed
1978

1979 1980 1981 1982 1983
	public function page_links_label($label)
	{
		if (!empty($label))
			$this->output('<span class="qa-page-links-label">'.$label.'</span>');
	}
Scott committed
1984

1985 1986 1987 1988
	public function page_links_list($page_items)
	{
		if (!empty($page_items)) {
			$this->output('<ul class="qa-page-links-list">');
Scott committed
1989

1990
			$index = 0;
Scott committed
1991

1992 1993 1994
			foreach ($page_items as $page_link) {
				$this->set_context('page_index', $index++);
				$this->page_links_item($page_link);
Scott committed
1995

1996 1997
				if ($page_link['ellipsis'])
					$this->page_links_item(array('type' => 'ellipsis'));
Scott committed
1998 1999
			}

2000
			$this->clear_context('page_index');
Scott committed
2001

2002
			$this->output('</ul>');
Scott committed
2003
		}
2004
	}
Scott committed
2005

2006 2007 2008 2009 2010 2011
	public function page_links_item($page_link)
	{
		$this->output('<li class="qa-page-links-item">');
		$this->page_link_content($page_link);
		$this->output('</li>');
	}
Scott committed
2012

2013 2014 2015 2016
	public function page_link_content($page_link)
	{
		$label = @$page_link['label'];
		$url = @$page_link['url'];
Scott committed
2017

2018 2019 2020 2021
		switch ($page_link['type']) {
			case 'this':
				$this->output('<span class="qa-page-selected">'.$label.'</span>');
				break;
Scott committed
2022

2023 2024 2025
			case 'prev':
				$this->output('<a href="'.$url.'" class="qa-page-prev">&laquo; '.$label.'</a>');
				break;
Scott committed
2026

2027 2028 2029
			case 'next':
				$this->output('<a href="'.$url.'" class="qa-page-next">'.$label.' &raquo;</a>');
				break;
Scott committed
2030

2031 2032 2033 2034 2035 2036 2037
			case 'ellipsis':
				$this->output('<span class="qa-page-ellipsis">...</span>');
				break;

			default:
				$this->output('<a href="'.$url.'" class="qa-page-link">'.$label.'</a>');
				break;
Scott committed
2038
		}
2039
	}
Scott committed
2040

2041 2042 2043 2044 2045 2046 2047
	public function page_links_clear()
	{
		$this->output(
			'<div class="qa-page-links-clear">',
			'</div>'
		);
	}
Scott committed
2048

2049 2050 2051
	public function suggest_next()
	{
		$suggest = @$this->content['suggest_next'];
Scott committed
2052

2053 2054 2055
		if (!empty($suggest)) {
			$this->output('<div class="qa-suggest-next">');
			$this->output($suggest);
Scott committed
2056 2057
			$this->output('</div>');
		}
2058
	}
Scott committed
2059

2060 2061 2062 2063
	public function q_view($q_view)
	{
		if (!empty($q_view)) {
			$this->output('<div class="qa-q-view'.(@$q_view['hidden'] ? ' qa-q-view-hidden' : '').rtrim(' '.@$q_view['classes']).'"'.rtrim(' '.@$q_view['tags']).'>');
Scott committed
2064 2065

			if (isset($q_view['main_form_tags']))
2066 2067 2068
				$this->output('<form '.$q_view['main_form_tags'].'>'); // form for voting buttons

			$this->q_view_stats($q_view);
Scott committed
2069 2070

			if (isset($q_view['main_form_tags'])) {
2071
				$this->form_hidden_elements(@$q_view['voting_form_hidden']);
Scott committed
2072 2073 2074
				$this->output('</form>');
			}

2075 2076
			$this->q_view_main($q_view);
			$this->q_view_clear();
Scott committed
2077

2078
			$this->output('</div> <!-- END qa-q-view -->', '');
Scott committed
2079
		}
2080
	}
Scott committed
2081

2082 2083 2084
	public function q_view_stats($q_view)
	{
		$this->output('<div class="qa-q-view-stats">');
Scott committed
2085

2086 2087
		$this->voting($q_view);
		$this->a_count($q_view);
Scott committed
2088

2089 2090
		$this->output('</div>');
	}
Scott committed
2091

2092 2093 2094
	public function q_view_main($q_view)
	{
		$this->output('<div class="qa-q-view-main">');
Scott committed
2095

2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111
		if (isset($q_view['main_form_tags']))
			$this->output('<form '.$q_view['main_form_tags'].'>'); // form for buttons on question

		$this->view_count($q_view);
		$this->q_view_content($q_view);
		$this->q_view_extra($q_view);
		$this->q_view_follows($q_view);
		$this->q_view_closed($q_view);
		$this->post_tags($q_view, 'qa-q-view');
		$this->post_avatar_meta($q_view, 'qa-q-view');
		$this->q_view_buttons($q_view);
		$this->c_list(@$q_view['c_list'], 'qa-q-view');

		if (isset($q_view['main_form_tags'])) {
			$this->form_hidden_elements(@$q_view['buttons_form_hidden']);
			$this->output('</form>');
Scott committed
2112 2113
		}

2114 2115 2116 2117 2118 2119 2120
		$this->c_form(@$q_view['c_form']);

		$this->output('</div> <!-- END qa-q-view-main -->');
	}

	public function q_view_content($q_view)
	{
Scott committed
2121 2122 2123 2124 2125
		$content = isset($q_view['content']) ? $q_view['content'] : '';

		$this->output('<div class="qa-q-view-content">');
		$this->output_raw($content);
		$this->output('</div>');
2126
	}
Scott committed
2127

2128 2129 2130
	public function q_view_follows($q_view)
	{
		if (!empty($q_view['follows']))
Scott committed
2131
			$this->output(
2132 2133 2134
				'<div class="qa-q-view-follows">',
				$q_view['follows']['label'],
				'<a href="'.$q_view['follows']['url'].'" class="qa-q-view-follows-link">'.$q_view['follows']['title'].'</a>',
Scott committed
2135 2136
				'</div>'
			);
2137
	}
Scott committed
2138

2139 2140 2141 2142
	public function q_view_closed($q_view)
	{
		if (!empty($q_view['closed'])) {
			$haslink = isset($q_view['closed']['url']);
Scott committed
2143

2144 2145 2146 2147 2148 2149 2150 2151
			$this->output(
				'<div class="qa-q-view-closed">',
				$q_view['closed']['label'],
				($haslink ? ('<a href="'.$q_view['closed']['url'].'"') : '<span').' class="qa-q-view-closed-content">',
				$q_view['closed']['content'],
				$haslink ? '</a>' : '</span>',
				'</div>'
			);
Scott committed
2152
		}
2153
	}
Scott committed
2154

2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165
	public function q_view_extra($q_view)
	{
		if (!empty($q_view['extra'])) {
			$this->output(
				'<div class="qa-q-view-extra">',
				$q_view['extra']['label'],
				'<span class="qa-q-view-extra-content">',
				$q_view['extra']['content'],
				'</span>',
				'</div>'
			);
Scott committed
2166
		}
2167
	}
Scott committed
2168

2169 2170 2171 2172 2173 2174
	public function q_view_buttons($q_view)
	{
		if (!empty($q_view['form'])) {
			$this->output('<div class="qa-q-view-buttons">');
			$this->form($q_view['form']);
			$this->output('</div>');
Scott committed
2175
		}
2176
	}
Scott committed
2177

2178 2179 2180 2181 2182 2183 2184
	public function q_view_clear()
	{
		$this->output(
			'<div class="qa-q-view-clear">',
			'</div>'
		);
	}
Scott committed
2185

2186 2187 2188 2189
	public function a_form($a_form)
	{
		$this->output('<div class="qa-a-form"'.(isset($a_form['id']) ? (' id="'.$a_form['id'].'"') : '').
			(@$a_form['collapse'] ? ' style="display:none;"' : '').'>');
Scott committed
2190

2191 2192
		$this->form($a_form);
		$this->c_list(@$a_form['c_list'], 'qa-a-item');
Scott committed
2193

2194 2195
		$this->output('</div> <!-- END qa-a-form -->', '');
	}
Scott committed
2196

2197 2198 2199 2200
	public function a_list($a_list)
	{
		if (!empty($a_list)) {
			$this->part_title($a_list);
Scott committed
2201

2202 2203 2204
			$this->output('<div class="qa-a-list'.($this->list_vote_disabled($a_list['as']) ? ' qa-a-list-vote-disabled' : '').'" '.@$a_list['tags'].'>', '');
			$this->a_list_items($a_list['as']);
			$this->output('</div> <!-- END qa-a-list -->', '');
Scott committed
2205
		}
2206
	}
Scott committed
2207

2208 2209 2210 2211 2212
	public function a_list_items($a_items)
	{
		foreach ($a_items as $a_item)
			$this->a_list_item($a_item);
	}
Scott committed
2213

2214 2215 2216
	public function a_list_item($a_item)
	{
		$extraclass = @$a_item['classes'].($a_item['hidden'] ? ' qa-a-list-item-hidden' : ($a_item['selected'] ? ' qa-a-list-item-selected' : ''));
Scott committed
2217

2218
		$this->output('<div class="qa-a-list-item '.$extraclass.'" '.@$a_item['tags'].'>');
Scott committed
2219

2220 2221
		if (isset($a_item['main_form_tags']))
			$this->output('<form '.$a_item['main_form_tags'].'>'); // form for voting buttons
Scott committed
2222

2223
		$this->voting($a_item);
Scott committed
2224

2225 2226 2227 2228
		if (isset($a_item['main_form_tags'])) {
			$this->form_hidden_elements(@$a_item['voting_form_hidden']);
			$this->output('</form>');
		}
Scott committed
2229

2230 2231
		$this->a_item_main($a_item);
		$this->a_item_clear();
Scott committed
2232

2233 2234
		$this->output('</div> <!-- END qa-a-list-item -->', '');
	}
Scott committed
2235

2236 2237 2238
	public function a_item_main($a_item)
	{
		$this->output('<div class="qa-a-item-main">');
Scott committed
2239

2240 2241
		if (isset($a_item['main_form_tags']))
			$this->output('<form '.$a_item['main_form_tags'].'>'); // form for buttons on answer
Scott committed
2242

2243 2244 2245 2246
		if ($a_item['hidden'])
			$this->output('<div class="qa-a-item-hidden">');
		elseif ($a_item['selected'])
			$this->output('<div class="qa-a-item-selected">');
Scott committed
2247

2248 2249 2250 2251
		$this->a_selection($a_item);
		$this->error(@$a_item['error']);
		$this->a_item_content($a_item);
		$this->post_avatar_meta($a_item, 'qa-a-item');
Scott committed
2252

2253 2254
		if ($a_item['hidden'] || $a_item['selected'])
			$this->output('</div>');
Scott committed
2255

2256
		$this->a_item_buttons($a_item);
Scott committed
2257

2258
		$this->c_list(@$a_item['c_list'], 'qa-a-item');
Scott committed
2259

2260 2261 2262
		if (isset($a_item['main_form_tags'])) {
			$this->form_hidden_elements(@$a_item['buttons_form_hidden']);
			$this->output('</form>');
Scott committed
2263 2264
		}

2265
		$this->c_form(@$a_item['c_form']);
Scott committed
2266

2267 2268
		$this->output('</div> <!-- END qa-a-item-main -->');
	}
Scott committed
2269

2270 2271 2272 2273 2274 2275 2276
	public function a_item_clear()
	{
		$this->output(
			'<div class="qa-a-item-clear">',
			'</div>'
		);
	}
Scott committed
2277

2278 2279 2280 2281 2282 2283
	public function a_item_content($a_item)
	{
		$this->output('<div class="qa-a-item-content">');
		$this->output_raw($a_item['content']);
		$this->output('</div>');
	}
Scott committed
2284

2285 2286 2287 2288 2289 2290
	public function a_item_buttons($a_item)
	{
		if (!empty($a_item['form'])) {
			$this->output('<div class="qa-a-item-buttons">');
			$this->form($a_item['form']);
			$this->output('</div>');
Scott committed
2291
		}
2292
	}
Scott committed
2293

2294 2295 2296 2297
	public function c_form($c_form)
	{
		$this->output('<div class="qa-c-form"'.(isset($c_form['id']) ? (' id="'.$c_form['id'].'"') : '').
			(@$c_form['collapse'] ? ' style="display:none;"' : '').'>');
Scott committed
2298

2299
		$this->form($c_form);
Scott committed
2300

2301 2302
		$this->output('</div> <!-- END qa-c-form -->', '');
	}
Scott committed
2303

2304 2305 2306 2307 2308 2309
	public function c_list($c_list, $class)
	{
		if (!empty($c_list)) {
			$this->output('', '<div class="'.$class.'-c-list"'.(@$c_list['hidden'] ? ' style="display:none;"' : '').' '.@$c_list['tags'].'>');
			$this->c_list_items($c_list['cs']);
			$this->output('</div> <!-- END qa-c-list -->', '');
Scott committed
2310
		}
2311
	}
Scott committed
2312

2313 2314 2315 2316 2317
	public function c_list_items($c_items)
	{
		foreach ($c_items as $c_item)
			$this->c_list_item($c_item);
	}
Scott committed
2318

2319 2320 2321
	public function c_list_item($c_item)
	{
		$extraclass = @$c_item['classes'].(@$c_item['hidden'] ? ' qa-c-item-hidden' : '');
Scott committed
2322

2323
		$this->output('<div class="qa-c-list-item '.$extraclass.'" '.@$c_item['tags'].'>');
Scott committed
2324

2325 2326
		$this->c_item_main($c_item);
		$this->c_item_clear();
Scott committed
2327

2328 2329
		$this->output('</div> <!-- END qa-c-item -->');
	}
Scott committed
2330

2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346
	public function c_item_main($c_item)
	{
		$this->error(@$c_item['error']);

		if (isset($c_item['expand_tags']))
			$this->c_item_expand($c_item);
		elseif (isset($c_item['url']))
			$this->c_item_link($c_item);
		else
			$this->c_item_content($c_item);

		$this->output('<div class="qa-c-item-footer">');
		$this->post_avatar_meta($c_item, 'qa-c-item');
		$this->c_item_buttons($c_item);
		$this->output('</div>');
	}
Scott committed
2347

2348 2349 2350 2351 2352 2353
	public function c_item_link($c_item)
	{
		$this->output(
			'<a href="'.$c_item['url'].'" class="qa-c-item-link">'.$c_item['title'].'</a>'
		);
	}
Scott committed
2354

2355 2356 2357 2358 2359 2360
	public function c_item_expand($c_item)
	{
		$this->output(
			'<a href="'.$c_item['url'].'" '.$c_item['expand_tags'].' class="qa-c-item-expand">'.$c_item['title'].'</a>'
		);
	}
Scott committed
2361

2362 2363 2364 2365 2366 2367
	public function c_item_content($c_item)
	{
		$this->output('<div class="qa-c-item-content">');
		$this->output_raw($c_item['content']);
		$this->output('</div>');
	}
Scott committed
2368

2369 2370 2371 2372 2373
	public function c_item_buttons($c_item)
	{
		if (!empty($c_item['form'])) {
			$this->output('<div class="qa-c-item-buttons">');
			$this->form($c_item['form']);
Scott committed
2374 2375 2376 2377
			$this->output('</div>');
		}
	}

2378 2379 2380 2381 2382 2383 2384 2385 2386
	public function c_item_clear()
	{
		$this->output(
			'<div class="qa-c-item-clear">',
			'</div>'
		);
	}


Scott committed
2387 2388 2389
	/**
	 * Generic method to output a basic list of question links.
	 */
2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401
	public function q_title_list($q_list, $attrs=null)
	{
		$this->output('<ul class="qa-q-title-list">');
		foreach ($q_list as $q) {
			$this->output(
				'<li class="qa-q-title-item">',
				'<a href="' . qa_q_path_html($q['postid'], $q['title']) . '" ' . $attrs . '>' . qa_html($q['title']) . '</a>',
				'</li>'
			);
		}
		$this->output('</ul>');
	}
Scott committed
2402

Scott committed
2403 2404 2405
	/**
	 * Output block of similar questions when asking.
	 */
2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419
	public function q_ask_similar($q_list, $pretext='')
	{
		if (!count($q_list))
			return;

		$this->output('<div class="qa-ask-similar">');

		if (strlen($pretext) > 0)
			$this->output('<p class="qa-ask-similar-title">'.$pretext.'</p>');
		$this->q_title_list($q_list, 'target="_blank"');

		$this->output('</div>');
	}
}