qa-theme-base.php 65.1 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
<?php
/*
	Question2Answer by Gideon Greenspan and contributors
	http://www.question2answer.org/

	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
*/

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


/*
	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.
*/

39 40
class qa_html_theme_base
{
41
	/** @var string */
42
	public $template;
43 44

	/** @var array */
45
	public $content;
46 47

	/** @var string */
48
	public $rooturl;
49 50

	/** @var string */
51 52
	public $request;

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
	/**
	 * Whether text direction is Right-To-Left
	 *
	 * @var bool
	 */
	public $isRTL;

	/**
	 * Whether to indent the HTML
	 *
	 * @var bool
	 */
	protected $minifyHtml;

	/** @var int */
68
	protected $indent = 0;
69 70

	/** @var int */
71
	protected $lines = 0;
72 73

	/** @var array */
74
	protected $context = array();
Scott committed
75

76 77 78 79
	/**
	 * Whether to use new block layout in rankings (true) or fall back to tables (false)
	 * @var bool
	 */
80
	protected $ranking_block_layout = false;
81 82 83 84 85

	/**
	 * Theme 'slug' to use as CSS class
	 * @var string
	 */
86
	protected $theme;
Scott committed
87 88


Scott committed
89 90
	/**
	 * Initialize the object and assign local variables.
91 92 93 94
	 * @param string $template
	 * @param string $content
	 * @param string $rooturl
	 * @param string $request
Scott committed
95
	 */
96 97 98 99 100 101 102
	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';
103
		$this->minifyHtml = !empty($content['options']['minify_html']);
104
	}
105

106 107 108
	/**
	 * @deprecated PHP4-style constructor deprecated from 1.7; please use proper `__construct`
	 * function instead.
109 110 111 112
	 * @param string $template
	 * @param string $content
	 * @param string $rooturl
	 * @param string $request
113 114 115 116 117
	 */
	public function qa_html_theme_base($template, $content, $rooturl, $request)
	{
		self::__construct($template, $content, $rooturl, $request);
	}
Scott committed
118

119

Scott committed
120 121 122 123
	/**
	 * 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.
124
	 * @param array $elements
Scott committed
125
	 */
126 127 128
	public function output_array($elements)
	{
		foreach ($elements as $element) {
129
			$line = str_replace('/>', '>', $element);
130

131 132
			if ($this->minifyHtml) {
				if (strlen($line))
Scott committed
133 134 135
					echo $line . "\n";
			} else {
				$delta = substr_count($element, '<') - substr_count($element, '<!') - 2 * substr_count($element, '</') - substr_count($element, '/>');
136

137 138 139
				if ($delta < 0) {
					$this->indent += $delta;
				}
140

Scott committed
141
				echo str_repeat("\t", max(0, $this->indent)) . $line . "\n";
142 143 144 145

				if ($delta > 0) {
					$this->indent += $delta;
				}
Scott committed
146
			}
147 148

			$this->lines++;
Scott committed
149
		}
150
	}
Scott committed
151 152


Scott committed
153 154 155
	/**
	 * Output each passed parameter on a separate line - see output_array() comments.
	 */
156 157 158 159 160
	public function output() // other parameters picked up via func_get_args()
	{
		$args = func_get_args();
		$this->output_array($args);
	}
Scott committed
161 162


Scott committed
163 164 165
	/**
	 * 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.
166
	 * @param string $html
Scott committed
167
	 */
168 169 170
	public function output_raw($html)
	{
		if (strlen($html))
Scott committed
171
			echo str_repeat("\t", max(0, $this->indent)) . $html . "\n";
172
	}
Scott committed
173 174


Scott committed
175 176 177
	/**
	 * 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.
178 179
	 * @param array $parts
	 * @param string $class
Scott committed
180 181 182
	 * @param string $outertag
	 * @param string $innertag
	 * @param string $extraclass
Scott committed
183
	 */
Scott committed
184
	public function output_split($parts, $class, $outertag = 'span', $innertag = 'span', $extraclass = null)
185 186 187 188 189
	{
		if (empty($parts) && strtolower($outertag) != 'td')
			return;

		$this->output(
Scott committed
190 191 192 193 194
			'<' . $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 . '>'
195 196
		);
	}
Scott committed
197 198


Scott committed
199 200
	/**
	 * Set some context, which be accessed via $this->context for a function to know where it's being used on the page.
201 202
	 * @param string $key
	 * @param string $value
Scott committed
203
	 */
204 205 206 207
	public function set_context($key, $value)
	{
		$this->context[$key] = $value;
	}
Scott committed
208 209


Scott committed
210 211
	/**
	 * Clear some context (used at the end of the appropriate loop).
212
	 * @param string $key
Scott committed
213
	 */
214 215 216 217
	public function clear_context($key)
	{
		unset($this->context[$key]);
	}
Scott committed
218 219


Scott committed
220 221 222
	/**
	 * 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.
223 224
	 * @param array $parts
	 * @param string|null $beforekey
Scott committed
225
	 * @param bool $reorderrelative
Scott committed
226
	 */
Scott committed
227
	public function reorder_parts($parts, $beforekey = null, $reorderrelative = true)
228
	{
Scott committed
229
		require_once QA_INCLUDE_DIR . 'util/sort.php';
Scott committed
230

231 232
		qa_array_reorder($this->content, $parts, $beforekey, $reorderrelative);
	}
Scott committed
233 234


Scott committed
235 236
	/**
	 * Output the widgets (as provided in $this->content['widgets']) for $region and $place.
237 238
	 * @param string $region
	 * @param string $place
Scott committed
239
	 */
240 241
	public function widgets($region, $place)
	{
Scott committed
242 243
		$widgetsHere = isset($this->content['widgets'][$region][$place]) ? $this->content['widgets'][$region][$place] : array();
		if (is_array($widgetsHere) && count($widgetsHere) > 0) {
Scott committed
244
			$this->output('<div class="qa-widgets-' . $region . ' qa-widgets-' . $region . '-' . $place . '">');
245

Scott committed
246
			foreach ($widgetsHere as $module) {
Scott committed
247
				$this->output('<div class="qa-widget-' . $region . ' qa-widget-' . $region . '-' . $place . '">');
248 249 250 251 252
				$module->output_widget($region, $place, $this, $this->template, $this->request, $this->content);
				$this->output('</div>');
			}

			$this->output('</div>', '');
Scott committed
253
		}
254
	}
Scott committed
255

256 257
	/**
	 * Pre-output initialization. Immediately called after loading of the module. Content and template variables are
Scott committed
258
	 * already setup at this point. Useful to perform layer initialization in the earliest and safest stage possible.
259
	 */
Scott committed
260 261 262 263
	public function initialize()
	{
		// abstract method
	}
Scott committed
264

Scott committed
265 266 267
	/**
	 * Post-output cleanup. For now, check that the indenting ended right, and if not, output a warning in an HTML comment.
	 */
268 269
	public function finish()
	{
270
		if ($this->indent !== 0 && !$this->minifyHtml) {
Scott committed
271 272 273 274
			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" .
275
				"Thanks!\n-->\n";
Scott committed
276
		}
277
	}
Scott committed
278 279


Scott committed
280
	// From here on, we have a large number of class methods which output particular pieces of HTML markup
281
	// The calling chain is initiated from qa-page.php, or ajax/*.php for refreshing parts of a page,
Scott committed
282 283
	// 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
284

285 286 287 288
	public function doctype()
	{
		$this->output('<!DOCTYPE html>');
	}
Scott committed
289

290 291 292
	public function html()
	{
		$attribution = '<!-- Powered by Question2Answer - http://www.question2answer.org/ -->';
Scott committed
293
		$extratags = isset($this->content['html_tags']) ? $this->content['html_tags'] : '';
294

295
		$this->output(
Scott committed
296
			'<html ' . $extratags . '>',
297 298 299 300 301 302 303 304 305 306 307
			$attribution
		);

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

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

309 310 311 312
	public function head()
	{
		$this->output(
			'<head>',
Scott committed
313
			'<meta charset="' . $this->content['charset'] . '"/>'
314 315 316 317 318 319 320 321 322 323 324 325
		);

		$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
326

327 328 329
	public function head_title()
	{
		$pagetitle = strlen($this->request) ? strip_tags(@$this->content['title']) : '';
Scott committed
330
		$headtitle = (strlen($pagetitle) ? "$pagetitle - " : '') . $this->content['site_title'];
Scott committed
331

Scott committed
332
		$this->output('<title>' . $headtitle . '</title>');
333
	}
Scott committed
334

335 336
	public function head_metas()
	{
Scott committed
337 338 339
		if (strlen(@$this->content['description'])) {
			$this->output('<meta name="description" content="' . $this->content['description'] . '"/>');
		}
Scott committed
340

Scott committed
341 342 343 344
		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'] . '"/>');
		}
345 346 347 348
	}

	public function head_links()
	{
Scott committed
349 350 351
		if (isset($this->content['canonical'])) {
			$this->output('<link rel="canonical" href="' . $this->content['canonical'] . '"/>');
		}
352

Scott committed
353
		if (isset($this->content['feed']['url'])) {
Scott committed
354
			$this->output('<link rel="alternate" type="application/rss+xml" href="' . $this->content['feed']['url'] . '" title="' . @$this->content['feed']['label'] . '"/>');
Scott committed
355
		}
356 357 358 359 360 361

		// 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
362 363
			}
		}
364
	}
Scott committed
365

366 367 368
	public function head_script()
	{
		if (isset($this->content['script'])) {
Scott committed
369
			foreach ($this->content['script'] as $scriptline) {
370
				$this->output_raw($scriptline);
Scott committed
371
			}
372 373
		}
	}
Scott committed
374

375 376
	public function head_css()
	{
Scott committed
377
		$this->output('<link rel="stylesheet" href="' . $this->rooturl . $this->css_name() . '"/>');
Scott committed
378

379
		if (isset($this->content['css_src'])) {
Scott committed
380
			foreach ($this->content['css_src'] as $css_src) {
Scott committed
381
				$this->output('<link rel="stylesheet" href="' . $css_src . '"/>');
Scott committed
382
			}
Scott committed
383 384
		}

385
		if (!empty($this->content['notices'])) {
Scott committed
386
			$this->output(
387 388 389
				'<style>',
				'.qa-body-js-on .qa-notice {display:none;}',
				'</style>'
Scott committed
390
			);
391 392
		}
	}
Scott committed
393

394 395
	public function css_name()
	{
Scott committed
396
		return 'qa-styles.css?' . QA_VERSION;
397
	}
Scott committed
398

399 400 401
	public function head_lines()
	{
		if (isset($this->content['head_lines'])) {
Scott committed
402
			foreach ($this->content['head_lines'] as $line) {
403
				$this->output_raw($line);
Scott committed
404
			}
Scott committed
405
		}
406
	}
Scott committed
407

408 409 410 411
	public function head_custom()
	{
		// abstract method
	}
Scott committed
412

413 414 415 416 417
	public function body()
	{
		$this->output('<body');
		$this->body_tags();
		$this->output('>');
Scott committed
418

419 420 421 422 423
		$this->body_script();
		$this->body_header();
		$this->body_content();
		$this->body_footer();
		$this->body_hidden();
Scott committed
424

425 426
		$this->output('</body>');
	}
Scott committed
427

428 429
	public function body_hidden()
	{
430
		$this->output('<div style="position:absolute;overflow:hidden;clip:rect(0 0 0 0);height:0;width:0;margin:0;padding:0;border:0;">');
431 432 433
		$this->waiting_template();
		$this->output('</div>');
	}
Scott committed
434

435 436 437 438
	public function waiting_template()
	{
		$this->output('<span id="qa-waiting-template" class="qa-waiting">...</span>');
	}
Scott committed
439

440 441 442 443
	public function body_script()
	{
		$this->output(
			'<script>',
Scott committed
444 445
			"var b = document.getElementsByTagName('body')[0];",
			"b.className = b.className.replace('qa-body-js-off', 'qa-body-js-on');",
446 447 448
			'</script>'
		);
	}
Scott committed
449

450 451
	public function body_header()
	{
Scott committed
452
		if (isset($this->content['body_header'])) {
453
			$this->output_raw($this->content['body_header']);
Scott committed
454
		}
455
	}
Scott committed
456

457 458
	public function body_footer()
	{
Scott committed
459
		if (isset($this->content['body_footer'])) {
460
			$this->output_raw($this->content['body_footer']);
Scott committed
461
		}
462
	}
Scott committed
463

464 465 466 467
	public function body_content()
	{
		$this->body_prefix();
		$this->notices();
Scott committed
468

469
		$extratags = isset($this->content['wrapper_tags']) ? $this->content['wrapper_tags'] : '';
470
		$this->output('<div class="qa-body-wrapper"' . $extratags . '>', '');
Scott committed
471

472 473 474 475 476 477 478 479
		$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
480

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

483 484
		$this->body_suffix();
	}
Scott committed
485

486 487
	public function body_tags()
	{
Scott committed
488
		$class = 'qa-template-' . qa_html($this->template);
489
		$class .= empty($this->theme) ? '' : ' qa-theme-' . qa_html($this->theme);
Scott committed
490

491
		if (isset($this->content['categoryids'])) {
Scott committed
492 493 494
			foreach ($this->content['categoryids'] as $categoryid) {
				$class .= ' qa-category-' . qa_html($categoryid);
			}
Scott committed
495 496
		}

Scott committed
497
		$this->output('class="' . $class . ' qa-body-js-off"');
498
	}
Scott committed
499

500 501 502 503
	public function body_prefix()
	{
		// abstract method
	}
Scott committed
504

505 506 507 508
	public function body_suffix()
	{
		// abstract method
	}
Scott committed
509

510 511 512
	public function notices()
	{
		if (!empty($this->content['notices'])) {
Scott committed
513
			foreach ($this->content['notices'] as $notice) {
514
				$this->notice($notice);
Scott committed
515
			}
Scott committed
516
		}
517
	}
Scott committed
518

519 520
	public function notice($notice)
	{
Scott committed
521
		$this->output('<div class="qa-notice" id="' . $notice['id'] . '">');
Scott committed
522

523
		if (isset($notice['form_tags']))
Scott committed
524
			$this->output('<form ' . $notice['form_tags'] . '>');
Scott committed
525

526
		$this->output_raw($notice['content']);
Scott committed
527

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

530 531 532
		if (isset($notice['form_tags'])) {
			$this->form_hidden_elements(@$notice['form_hidden']);
			$this->output('</form>');
Scott committed
533 534
		}

535 536
		$this->output('</div>');
	}
Scott committed
537

538 539 540
	public function header()
	{
		$this->output('<div class="qa-header">');
Scott committed
541

542 543 544 545
		$this->logo();
		$this->nav_user_search();
		$this->nav_main_sub();
		$this->header_clear();
Scott committed
546

547 548
		$this->output('</div> <!-- END qa-header -->', '');
	}
Scott committed
549

550 551 552 553 554
	public function nav_user_search()
	{
		$this->nav('user');
		$this->search();
	}
Scott committed
555

556 557 558 559 560
	public function nav_main_sub()
	{
		$this->nav('main');
		$this->nav('sub');
	}
Scott committed
561

562 563 564 565 566 567 568 569
	public function logo()
	{
		$this->output(
			'<div class="qa-logo">',
			$this->content['logo'],
			'</div>'
		);
	}
Scott committed
570

571 572 573
	public function search()
	{
		$search = $this->content['search'];
Scott committed
574

575 576
		$this->output(
			'<div class="qa-search">',
Scott committed
577
			'<form ' . $search['form_tags'] . '>',
578 579
			@$search['form_extra']
		);
Scott committed
580

581 582
		$this->search_field($search);
		$this->search_button($search);
Scott committed
583

584 585 586 587 588
		$this->output(
			'</form>',
			'</div>'
		);
	}
Scott committed
589

590 591
	public function search_field($search)
	{
Scott committed
592
		$this->output('<input type="text" ' . $search['field_tags'] . ' value="' . @$search['value'] . '" class="qa-search-field"/>');
593
	}
Scott committed
594

595 596
	public function search_button($search)
	{
Scott committed
597
		$this->output('<input type="submit" value="' . $search['button_label'] . '" class="qa-search-button"/>');
598
	}
Scott committed
599

Scott committed
600
	public function nav($navtype, $level = null)
601 602
	{
		$navigation = @$this->content['navigation'][$navtype];
Scott committed
603

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

607 608 609 610 611 612 613 614 615
			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
616 617
			}

618
			$this->set_context('nav_type', $navtype);
Scott committed
619
			$this->nav_list($navigation, 'nav-' . $navtype, $level);
620 621 622
			$this->nav_clear($navtype);
			$this->clear_context('nav_type');

Scott committed
623 624
			$this->output('</div>');
		}
625
	}
Scott committed
626

Scott committed
627
	public function nav_list($navigation, $class, $level = null)
628
	{
Scott committed
629
		$this->output('<ul class="qa-' . $class . '-list' . (isset($level) ? (' qa-' . $class . '-list-' . $level) : '') . '">');
Scott committed
630

631
		$index = 0;
Scott committed
632

633 634 635 636
		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
637 638
		}

639 640
		$this->clear_context('nav_key');
		$this->clear_context('nav_index');
Scott committed
641

642 643
		$this->output('</ul>');
	}
Scott committed
644

645 646 647
	public function nav_clear($navtype)
	{
		$this->output(
Scott committed
648
			'<div class="qa-nav-' . $navtype . '-clear">',
649 650 651
			'</div>'
		);
	}
Scott committed
652

Scott committed
653
	public function nav_item($key, $navlink, $class, $level = null)
654 655 656 657 658
	{
		$suffix = strtr($key, array( // map special character in navigation key
			'$' => '',
			'/' => '-',
		));
Scott committed
659

Scott committed
660 661
		$this->output('<li class="qa-' . $class . '-item' . (@$navlink['opposite'] ? '-opp' : '') .
			(@$navlink['state'] ? (' qa-' . $class . '-' . $navlink['state']) : '') . ' qa-' . $class . '-' . $suffix . '">');
662 663
		$this->nav_link($navlink, $class);

Scott committed
664 665 666 667
		$subnav = isset($navlink['subnav']) ? $navlink['subnav'] : array();
		if (is_array($subnav) && count($subnav) > 0) {
			$this->nav_list($subnav, $class, 1 + $level);
		}
Scott committed
668

669 670
		$this->output('</li>');
	}
Scott committed
671

672 673 674
	public function nav_link($navlink, $class)
	{
		if (isset($navlink['url'])) {
Scott committed
675
			$this->output(
Scott committed
676 677 678 679 680
				'<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'] .
681
				'</a>'
Scott committed
682
			);
Scott committed
683
		} else {
684
			$this->output(
Scott committed
685 686 687 688
				'<span class="qa-' . $class . '-nolink' . (@$navlink['selected'] ? (' qa-' . $class . '-selected') : '') .
				(@$navlink['favorited'] ? (' qa-' . $class . '-favorited') : '') . '"' .
				(strlen(@$navlink['popup']) ? (' title="' . $navlink['popup'] . '"') : '') .
				'>' . $navlink['label'] . '</span>'
689
			);
Scott committed
690 691
		}

692
		if (strlen(@$navlink['note']))
Scott committed
693
			$this->output('<span class="qa-' . $class . '-note">' . $navlink['note'] . '</span>');
694
	}
Scott committed
695

696 697 698 699
	public function logged_in()
	{
		$this->output_split(@$this->content['loggedin'], 'qa-logged-in', 'div');
	}
Scott committed
700

701 702 703 704 705 706 707
	public function header_clear()
	{
		$this->output(
			'<div class="qa-header-clear">',
			'</div>'
		);
	}
Scott committed
708

709 710
	public function sidepanel()
	{
Félicie committed
711
		// $this->output('<div class="qa-sidepanel">');
712 713 714 715 716 717 718 719 720
		$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
721

722 723 724
	public function sidebar()
	{
		$sidebar = @$this->content['sidebar'];
Scott committed
725

726 727 728 729 730 731
		if (!empty($sidebar)) {
			$this->output('<div class="qa-sidebar">');
			$this->output_raw($sidebar);
			$this->output('</div>', '');
		}
	}
Scott committed
732

733 734 735 736 737
	public function feed()
	{
		$feed = @$this->content['feed'];

		if (!empty($feed)) {
Félicie committed
738 739
			// $this->output('<div class="qa-feed">');
			// $this->output('<a href="' . $feed['url'] . '" class="qa-feed-link">' . @$feed['label'] . '</a>');
740
			$this->output('</div>');
Scott committed
741
		}
742
	}
Scott committed
743

744 745 746
	public function main()
	{
		$content = $this->content;
747
		$hidden = !empty($content['hidden']) ? ' qa-main-hidden' : '';
Scott committed
748

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

751
		$this->widgets('main', 'top');
Scott committed
752

753
		$this->page_title_error();
Scott committed
754

755
		$this->widgets('main', 'high');
Scott committed
756

757
		$this->main_parts($content);
Scott committed
758

759
		$this->widgets('main', 'low');
Scott committed
760

761 762
		$this->page_links();
		$this->suggest_next();
Scott committed
763

764
		$this->widgets('main', 'bottom');
Scott committed
765

766 767
		$this->output('</div> <!-- END qa-main -->', '');
	}
Scott committed
768

769 770 771 772 773 774 775
	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
776

777
			$this->output('<div class="qa-main-heading">');
778
			$this->favorite();
779
			$this->output('<h1>');
780 781
			$this->title();
			$this->output('</h1>');
782
			$this->output('</div>');
783 784 785 786 787 788

			if (isset($favorite)) {
				$formhidden = isset($favorite['form_hidden']) ? $favorite['form_hidden'] : null;
				$this->form_hidden_elements($formhidden);
				$this->output('</form>');
			}
Scott committed
789
		}
790 791 792

		if (isset($this->content['success']))
			$this->success($this->content['success']);
793 794 795
		if (isset($this->content['error']))
			$this->error($this->content['error']);
	}
Scott committed
796

797 798 799 800 801 802 803 804
	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
805
		}
806
	}
Scott committed
807

808 809 810 811 812 813 814 815
	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
816
			$this->output(
Scott committed
817
				$url ? '<a href="' . $url . '">' : '',
818 819
				$this->content['title'],
				$url ? '</a>' : ''
Scott committed
820 821 822
			);
		}

823
		// add closed note in title
824
		if (!empty($q_view['closed']['state']))
Scott committed
825
			$this->output(' [' . $q_view['closed']['state'] . ']');
826
	}
Scott committed
827

828 829 830 831 832
	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
833

834 835 836
	public function favorite_button($tags, $class)
	{
		if (isset($tags))
Scott committed
837
			$this->output('<input ' . $tags . ' type="submit" value="" class="' . $class . '-button"/> ');
838
	}
Scott committed
839

840 841 842 843 844 845 846 847 848 849
	public function error($error)
	{
		if (strlen($error)) {
			$this->output(
				'<div class="qa-error">',
				$error,
				'</div>'
			);
		}
	}
Scott committed
850

851 852 853 854 855 856 857 858 859 860 861
	public function success($message)
	{
		if (strlen($message)) {
			$this->output(
				'<div class="qa-success">',
				$message,
				'</div>'
			);
		}
	}

862 863 864 865 866
	public function main_parts($content)
	{
		foreach ($content as $key => $part) {
			$this->set_context('part', $key);
			$this->main_part($key, $part);
Scott committed
867 868
		}

869 870
		$this->clear_context('part');
	}
Scott committed
871

872 873
	public function main_part($key, $part)
	{
Scott committed
874 875
		$isRanking = strpos($key, 'ranking') === 0;

876 877 878 879
		$partdiv = (
			strpos($key, 'custom') === 0 ||
			strpos($key, 'form') === 0 ||
			strpos($key, 'q_list') === 0 ||
Scott committed
880
			(strpos($key, 'q_view') === 0 && !isset($this->content['form_q_edit'])) ||
881 882
			strpos($key, 'a_form') === 0 ||
			strpos($key, 'a_list') === 0 ||
Scott committed
883
			$isRanking ||
884 885 886
			strpos($key, 'message_list') === 0 ||
			strpos($key, 'nav_list') === 0
		);
Scott committed
887

Scott committed
888 889 890 891 892
		if ($partdiv) {
			$class = 'qa-part-' . strtr($key, '_', '-');
			if ($isRanking)
				$class .= ' qa-ranking-' . $part['type'] . '-' . (isset($part['sort']) ? $part['sort'] : 'points');
			// help target CSS to page parts
Scott committed
893
			$this->output('<div class="' . $class . '">');
Scott committed
894
		}
Scott committed
895

896 897
		if (strpos($key, 'custom') === 0)
			$this->output_raw($part);
Scott committed
898

899 900
		elseif (strpos($key, 'form') === 0)
			$this->form($part);
Scott committed
901

902 903
		elseif (strpos($key, 'q_list') === 0)
			$this->q_list_and_form($part);
Scott committed
904

905 906
		elseif (strpos($key, 'q_view') === 0)
			$this->q_view($part);
Scott committed
907

908 909
		elseif (strpos($key, 'a_form') === 0)
			$this->a_form($part);
Scott committed
910

911 912
		elseif (strpos($key, 'a_list') === 0)
			$this->a_list($part);
Scott committed
913

914 915
		elseif (strpos($key, 'ranking') === 0)
			$this->ranking($part);
Scott committed
916

917 918
		elseif (strpos($key, 'message_list') === 0)
			$this->message_list_and_form($part);
Scott committed
919

920 921 922 923
		elseif (strpos($key, 'nav_list') === 0) {
			$this->part_title($part);
			$this->nav_list($part['nav'], $part['type'], 1);
		}
Scott committed
924

925 926 927
		if ($partdiv)
			$this->output('</div>');
	}
Scott committed
928

929 930 931
	public function footer()
	{
		$this->output('<div class="qa-footer">');
Scott committed
932

933 934 935
		$this->nav('footer');
		$this->attribution();
		$this->footer_clear();
Scott committed
936

937 938
		$this->output('</div> <!-- END qa-footer -->', '');
	}
Scott committed
939

940 941 942
	public function attribution()
	{
		// Hi there. I'd really appreciate you displaying this link on your Q2A site. Thank you - Gideon
Scott committed
943

944 945
		$this->output(
			'<div class="qa-attribution">',
Félicie committed
946
			'Très fortement inspiré de <a href="http://www.question2answer.org/">Question2Answer</a>',
947 948 949
			'</div>'
		);
	}
Scott committed
950

951 952 953 954 955 956 957
	public function footer_clear()
	{
		$this->output(
			'<div class="qa-footer-clear">',
			'</div>'
		);
	}
Scott committed
958

959 960 961 962
	public function section($title)
	{
		$this->part_title(array('title' => $title));
	}
Scott committed
963

964 965 966
	public function part_title($part)
	{
		if (strlen(@$part['title']) || strlen(@$part['title_tags']))
Scott committed
967
			$this->output('<h2' . rtrim(' ' . @$part['title_tags']) . '>' . @$part['title'] . '</h2>');
968
	}
Scott committed
969

970 971 972 973 974
	public function part_footer($part)
	{
		if (isset($part['footer']))
			$this->output($part['footer']);
	}
Scott committed
975

976 977 978 979
	public function form($form)
	{
		if (!empty($form)) {
			$this->part_title($form);
Scott committed
980

981
			if (isset($form['tags']))
Scott committed
982
				$this->output('<form ' . $form['tags'] . '>');
Scott committed
983

984
			$this->form_body($form);
Scott committed
985

986 987
			if (isset($form['tags']))
				$this->output('</form>');
Scott committed
988
		}
989
	}
Scott committed
990

991 992
	public function form_columns($form)
	{
Scott committed
993
		if (isset($form['ok']) || !empty($form['fields']))
Scott committed
994
			$columns = ($form['style'] == 'wide') ? 2 : 1;
995 996
		else
			$columns = 0;
Scott committed
997

998 999
		return $columns;
	}
Scott committed
1000

1001 1002 1003 1004
	public function form_spacer($form, $columns)
	{
		$this->output(
			'<tr>',
Scott committed
1005
			'<td colspan="' . $columns . '" class="qa-form-' . $form['style'] . '-spacer">',
1006 1007 1008 1009 1010
			'&nbsp;',
			'</td>',
			'</tr>'
		);
	}
Scott committed
1011

1012 1013 1014 1015
	public function form_body($form)
	{
		if (@$form['boxed'])
			$this->output('<div class="qa-form-table-boxed">');
Scott committed
1016

1017
		$columns = $this->form_columns($form);
Scott committed
1018

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

1022 1023 1024
		$this->form_ok($form, $columns);
		$this->form_fields($form, $columns);
		$this->form_buttons($form, $columns);
Scott committed
1025

1026 1027
		if ($columns)
			$this->output('</table>');
Scott committed
1028

1029
		$this->form_hidden($form);
Scott committed
1030

1031 1032 1033
		if (@$form['boxed'])
			$this->output('</div>');
	}
Scott committed
1034

1035 1036 1037
	public function form_ok($form, $columns)
	{
		if (!empty($form['ok'])) {
Scott committed
1038 1039
			$this->output(
				'<tr>',
Scott committed
1040
				'<td colspan="' . $columns . '" class="qa-form-' . $form['style'] . '-ok">',
1041
				$form['ok'],
Scott committed
1042 1043 1044 1045
				'</td>',
				'</tr>'
			);
		}
1046
	}
Scott committed
1047

Scott committed
1048 1049 1050
	/**
	 * 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.
1051 1052
	 * @param array
	 * @param array $keys
Scott committed
1053 1054
	 * @param mixed $beforekey
	 * @param bool $reorderrelative
Scott committed
1055
	 */
Scott committed
1056
	public function form_reorder_fields(&$form, $keys, $beforekey = null, $reorderrelative = true)
1057
	{
Scott committed
1058
		require_once QA_INCLUDE_DIR . 'util/sort.php';
Scott committed
1059

1060 1061 1062
		if (is_array($form['fields']))
			qa_array_reorder($form['fields'], $keys, $beforekey, $reorderrelative);
	}
Scott committed
1063

1064 1065 1066 1067 1068
	public function form_fields($form, $columns)
	{
		if (!empty($form['fields'])) {
			foreach ($form['fields'] as $key => $field) {
				$this->set_context('field_key', $key);
Scott committed
1069

1070 1071 1072 1073
				if (@$field['type'] == 'blank')
					$this->form_spacer($form, $columns);
				else
					$this->form_field_rows($form, $columns, $field);
Scott committed
1074 1075
			}

1076
			$this->clear_context('field_key');
Scott committed
1077
		}
1078
	}
Scott committed
1079

1080 1081 1082
	public function form_field_rows($form, $columns, $field)
	{
		$style = $form['style'];
Scott committed
1083

1084 1085 1086
		if (isset($field['style'])) { // field has different style to most of form
			$style = $field['style'];
			$colspan = $columns;
Scott committed
1087
			$columns = ($style == 'wide') ? 2 : 1;
Scott committed
1088
		} else
1089
			$colspan = null;
Scott committed
1090

1091 1092 1093 1094
		$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) &&
Scott committed
1095
			((!($prefixed || $suffixed)) || (!empty($field['error'])) || (!empty($field['note'])));
Scott committed
1096

1097 1098
		if (isset($field['id'])) {
			if ($columns == 1)
Scott committed
1099
				$this->output('<tbody id="' . $field['id'] . '">', '<tr>');
Scott committed
1100
			else
Scott committed
1101 1102
				$this->output('<tr id="' . $field['id'] . '">');
		} else
1103
			$this->output('<tr>');
Scott committed
1104

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

1108 1109 1110 1111 1112 1113
		if ($tworows) {
			$this->output(
				'</tr>',
				'<tr>'
			);
		}
Scott committed
1114

1115
		if (!$skipdata)
Scott committed
1116
			$this->form_data($field, $style, $columns, !($prefixed || $suffixed), $colspan);
Scott committed
1117

1118
		$this->output('</tr>');
Scott committed
1119

1120 1121 1122
		if ($columns == 1 && isset($field['id']))
			$this->output('</tbody>');
	}
Scott committed
1123

1124 1125 1126
	public function form_label($field, $style, $columns, $prefixed, $suffixed, $colspan)
	{
		$extratags = '';
Scott committed
1127

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

1131
		if (isset($colspan))
Scott committed
1132
			$extratags .= ' colspan="' . $colspan . '"';
Scott committed
1133

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

1136 1137 1138 1139
		if ($prefixed) {
			$this->output('<label>');
			$this->form_field($field, $style);
		}
Scott committed
1140

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

1143 1144
		if ($prefixed)
			$this->output('</label>');
Scott committed
1145

1146 1147 1148
		if ($suffixed) {
			$this->output('&nbsp;');
			$this->form_field($field, $style);
Scott committed
1149 1150
		}

1151 1152
		$this->output('</td>');
	}
Scott committed
1153

1154 1155 1156 1157
	public function form_data($field, $style, $columns, $showfield, $colspan)
	{
		if ($showfield || (!empty($field['error'])) || (!empty($field['note']))) {
			$this->output(
Scott committed
1158
				'<td class="qa-form-' . $style . '-data"' . (isset($colspan) ? (' colspan="' . $colspan . '"') : '') . '>'
1159
			);
Scott committed
1160

1161 1162
			if ($showfield)
				$this->form_field($field, $style);
Scott committed
1163

1164 1165
			if (!empty($field['error'])) {
				if (@$field['note_force'])
Scott committed
1166 1167
					$this->form_note($field, $style, $columns);

1168
				$this->form_error($field, $style, $columns);
Scott committed
1169
			} elseif (!empty($field['note']))
1170
				$this->form_note($field, $style, $columns);
Scott committed
1171

1172 1173 1174
			$this->output('</td>');
		}
	}
Scott committed
1175

1176 1177 1178
	public function form_field($field, $style)
	{
		$this->form_prefix($field, $style);
Scott committed
1179

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

1182 1183 1184 1185
		switch (@$field['type']) {
			case 'checkbox':
				$this->form_checkbox($field, $style);
				break;
Scott committed
1186

1187 1188 1189
			case 'static':
				$this->form_static($field, $style);
				break;
Scott committed
1190

1191 1192 1193
			case 'password':
				$this->form_password($field, $style);
				break;
Scott committed
1194

1195 1196 1197
			case 'number':
				$this->form_number($field, $style);
				break;
Scott committed
1198

1199 1200 1201 1202
			case 'file':
				$this->form_file($field, $style);
				break;

1203 1204 1205
			case 'select':
				$this->form_select($field, $style);
				break;
Scott committed
1206

1207 1208 1209
			case 'select-radio':
				$this->form_select_radio($field, $style);
				break;
Scott committed
1210

1211 1212 1213
			case 'image':
				$this->form_image($field, $style);
				break;
Scott committed
1214

1215 1216 1217 1218
			case 'email':
				$this->form_email($field, $style);
				break;

1219 1220 1221
			case 'custom':
				$this->output_raw(@$field['html']);
				break;
Scott committed
1222

1223 1224 1225 1226 1227 1228
			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
1229 1230
		}

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

1233 1234
		$this->form_suffix($field, $style);
	}
Scott committed
1235

Scott committed
1236 1237 1238
	/**
	 * 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.
1239 1240
	 * @param array $form
	 * @param array $keys
Scott committed
1241 1242
	 * @param mixed $beforekey
	 * @param bool $reorderrelative
Scott committed
1243
	 */
Scott committed
1244
	public function form_reorder_buttons(&$form, $keys, $beforekey = null, $reorderrelative = true)
1245
	{
Scott committed
1246
		require_once QA_INCLUDE_DIR . 'util/sort.php';
Scott committed
1247

1248 1249 1250
		if (is_array($form['buttons']))
			qa_array_reorder($form['buttons'], $keys, $beforekey, $reorderrelative);
	}
Scott committed
1251

1252 1253 1254 1255 1256 1257 1258 1259
	public function form_buttons($form, $columns)
	{
		if (!empty($form['buttons'])) {
			$style = @$form['style'];

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

1264 1265
			foreach ($form['buttons'] as $key => $button) {
				$this->set_context('button_key', $key);
Scott committed
1266

1267 1268 1269 1270 1271
				if (empty($button))
					$this->form_button_spacer($style);
				else {
					$this->form_button_data($button, $key, $style);
					$this->form_button_note($button, $style);
Scott committed
1272 1273 1274
				}
			}

1275
			$this->clear_context('button_key');
Scott committed
1276

1277
			if ($columns) {
Scott committed
1278
				$this->output(
1279 1280
					'</td>',
					'</tr>'
Scott committed
1281 1282 1283
				);
			}
		}
1284
	}
Scott committed
1285

1286 1287
	public function form_button_data($button, $key, $style)
	{
Scott committed
1288
		$baseclass = 'qa-form-' . $style . '-button qa-form-' . $style . '-button-' . $key;
Scott committed
1289

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

1294 1295 1296 1297
	public function form_button_note($button, $style)
	{
		if (!empty($button['note'])) {
			$this->output(
Scott committed
1298
				'<span class="qa-form-' . $style . '-note">',
1299 1300 1301 1302
				$button['note'],
				'</span>',
				'<br/>'
			);
Scott committed
1303
		}
1304
	}
Scott committed
1305

1306 1307
	public function form_button_spacer($style)
	{
Scott committed
1308
		$this->output('<span class="qa-form-' . $style . '-buttons-spacer">&nbsp;</span>');
1309
	}
Scott committed
1310

1311 1312 1313 1314
	public function form_hidden($form)
	{
		$this->form_hidden_elements(@$form['hidden']);
	}
Scott committed
1315

1316 1317
	public function form_hidden_elements($hidden)
	{
1318 1319 1320 1321 1322 1323
		if (empty($hidden))
			return;

		foreach ($hidden as $name => $value) {
			if (is_array($value)) {
				// new method of outputting tags
Scott committed
1324 1325
				$this->output('<input ' . @$value['tags'] . ' type="hidden" value="' . @$value['value'] . '"/>');
			} else {
1326
				// old method
Scott committed
1327
				$this->output('<input name="' . $name . '" type="hidden" value="' . $value . '"/>');
1328
			}
Scott committed
1329
		}
1330
	}
Scott committed
1331

1332 1333 1334
	public function form_prefix($field, $style)
	{
		if (!empty($field['prefix']))
Scott committed
1335
			$this->output('<span class="qa-form-' . $style . '-prefix">' . $field['prefix'] . '</span>');
1336
	}
Scott committed
1337

1338 1339 1340
	public function form_suffix($field, $style)
	{
		if (!empty($field['suffix']))
Scott committed
1341
			$this->output('<span class="qa-form-' . $style . '-suffix">' . $field['suffix'] . '</span>');
1342
	}
Scott committed
1343

1344 1345
	public function form_checkbox($field, $style)
	{
Scott committed
1346
		$this->output('<input ' . @$field['tags'] . ' type="checkbox" value="1"' . (@$field['value'] ? ' checked' : '') . ' class="qa-form-' . $style . '-checkbox"/>');
1347
	}
Scott committed
1348

1349 1350
	public function form_static($field, $style)
	{
Scott committed
1351
		$this->output('<span class="qa-form-' . $style . '-static">' . @$field['value'] . '</span>');
1352
	}
Scott committed
1353

1354 1355
	public function form_password($field, $style)
	{
Scott committed
1356
		$this->output('<input ' . @$field['tags'] . ' type="password" value="' . @$field['value'] . '" class="qa-form-' . $style . '-text"/>');
1357
	}
Scott committed
1358

1359 1360
	public function form_number($field, $style)
	{
Scott committed
1361
		$this->output('<input ' . @$field['tags'] . ' type="text" value="' . @$field['value'] . '" class="qa-form-' . $style . '-number"/>');
1362
	}
Scott committed
1363

1364 1365
	public function form_file($field, $style)
	{
Scott committed
1366
		$this->output('<input ' . @$field['tags'] . ' type="file" class="qa-form-' . $style . '-file"/>');
1367 1368
	}

1369 1370 1371 1372 1373 1374
	/**
	 * 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.
Scott committed
1375 1376
	 * @param $field
	 * @param $style
1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390
	 */
	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
1391 1392
		}

1393 1394
		$this->output('</select>');
	}
Scott committed
1395

1396 1397 1398
	public function form_select_radio($field, $style)
	{
		$radios = 0;
Scott committed
1399

1400 1401 1402
		foreach ($field['options'] as $tag => $value) {
			if ($radios++)
				$this->output('<br/>');
Scott committed
1403

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

1408 1409
	public function form_image($field, $style)
	{
Scott committed
1410
		$this->output('<div class="qa-form-' . $style . '-image">' . @$field['html'] . '</div>');
1411
	}
Scott committed
1412

1413 1414 1415 1416 1417
	public function form_email($field, $style)
	{
		$this->output('<input ' . @$field['tags'] . ' type="email" value="' . @$field['value'] . '" class="qa-form-' . $style . '-email"/>');
	}

1418 1419
	public function form_text_single_row($field, $style)
	{
Scott committed
1420
		$this->output('<input ' . @$field['tags'] . ' type="text" value="' . @$field['value'] . '" class="qa-form-' . $style . '-text"/>');
1421
	}
Scott committed
1422

1423 1424
	public function form_text_multi_row($field, $style)
	{
Scott committed
1425
		$this->output('<textarea ' . @$field['tags'] . ' rows="' . (int)$field['rows'] . '" cols="40" class="qa-form-' . $style . '-text">' . @$field['value'] . '</textarea>');
1426
	}
Scott committed
1427

1428 1429 1430
	public function form_error($field, $style, $columns)
	{
		$tag = ($columns > 1) ? 'span' : 'div';
Scott committed
1431

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

1435 1436 1437
	public function form_note($field, $style, $columns)
	{
		$tag = ($columns > 1) ? 'span' : 'div';
Scott committed
1438

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

1442 1443 1444
	public function ranking($ranking)
	{
		$this->part_title($ranking);
Scott committed
1445

1446 1447
		if (!isset($ranking['type']))
			$ranking['type'] = 'items';
Scott committed
1448
		$class = 'qa-top-' . $ranking['type'];
Scott committed
1449

1450 1451 1452
		if (!$this->ranking_block_layout) {
			// old, less semantic table layout
			$this->ranking_table($ranking, $class);
Scott committed
1453
		} else {
1454 1455
			// new block layout
			foreach ($ranking['items'] as $item) {
Félicie committed
1456
				
1457 1458
				$this->ranking_item($item, $class);
				$this->output('</span>');
Scott committed
1459
			}
1460
		}
Scott committed
1461

1462 1463
		$this->part_footer($ranking);
	}
Scott committed
1464

Scott committed
1465
	public function ranking_item($item, $class, $spacer = false) // $spacer is deprecated
1466 1467 1468 1469 1470
	{
		if (!$this->ranking_block_layout) {
			// old table layout
			$this->ranking_table_item($item, $class, $spacer);
			return;
Scott committed
1471 1472
		}

1473 1474
		if (isset($item['avatar']))
			$this->avatar($item, $class);
Scott committed
1475

1476
		$this->ranking_label($item, $class);
Scott committed
1477

1478 1479 1480
		if (isset($item['score']))
			$this->ranking_score($item, $class);
	}
Scott committed
1481

1482 1483
	public function ranking_cell($content, $class)
	{
Scott committed
1484 1485
		$tag = $this->ranking_block_layout ? 'span' : 'td';
		$this->output('<' . $tag . ' class="' . $class . '">' . $content . '</' . $tag . '>');
1486
	}
Scott committed
1487

1488 1489
	public function ranking_count($item, $class)
	{
Scott committed
1490
		$this->ranking_cell($item['count'] . ' &#215;', $class . '-count');
1491
	}
Scott committed
1492

1493 1494
	public function ranking_label($item, $class)
	{
Scott committed
1495
		$this->ranking_cell($item['label'], $class . '-label');
1496
	}
Scott committed
1497

1498 1499
	public function ranking_score($item, $class)
	{
Scott committed
1500
		$this->ranking_cell($item['score'], $class . '-score');
1501
	}
Scott committed
1502

1503 1504 1505 1506
	/**
	 * @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.
Scott committed
1507
	 * @param $ranking
1508
	 * @param string $class
1509 1510 1511 1512 1513 1514
	 */
	public function ranking_table($ranking, $class)
	{
		$rows = min($ranking['rows'], count($ranking['items']));

		if ($rows > 0) {
Scott committed
1515
			$this->output('<table class="' . $class . '-table">');
1516 1517 1518 1519 1520 1521 1522 1523
			$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);
Scott committed
1524
					$this->ranking_table_item(@$ranking['items'][$column * $rows + $row], $class, $column > 0);
Scott committed
1525
				}
1526 1527 1528

				$this->clear_context('ranking_column');
				$this->output('</tr>');
Scott committed
1529
			}
1530 1531
			$this->clear_context('ranking_row');
			$this->output('</table>');
Scott committed
1532
		}
1533
	}
Scott committed
1534

1535 1536
	/**
	 * @deprecated See ranking_table above.
Scott committed
1537
	 * @param $item
1538
	 * @param string $class
Scott committed
1539
	 * @param $spacer
1540 1541 1542 1543 1544
	 */
	public function ranking_table_item($item, $class, $spacer)
	{
		if ($spacer)
			$this->ranking_spacer($class);
Scott committed
1545

1546 1547 1548
		if (empty($item)) {
			$this->ranking_spacer($class);
			$this->ranking_spacer($class);
Scott committed
1549

1550 1551 1552
		} else {
			if (isset($item['count']))
				$this->ranking_count($item, $class);
Scott committed
1553

1554
			if (isset($item['avatar']))
Scott committed
1555
				$item['label'] = $item['avatar'] . ' ' . $item['label'];
Scott committed
1556

1557
			$this->ranking_label($item, $class);
Scott committed
1558

1559 1560
			if (isset($item['score']))
				$this->ranking_score($item, $class);
Scott committed
1561
		}
1562
	}
Scott committed
1563

1564 1565
	/**
	 * @deprecated See ranking_table above.
1566
	 * @param string $class
1567 1568 1569
	 */
	public function ranking_spacer($class)
	{
Scott committed
1570
		$this->output('<td class="' . $class . '-spacer">&nbsp;</td>');
1571
	}
Scott committed
1572 1573


1574 1575 1576 1577
	public function message_list_and_form($list)
	{
		if (!empty($list)) {
			$this->part_title($list);
Scott committed
1578

1579
			$this->error(@$list['error']);
Scott committed
1580

1581
			if (!empty($list['form'])) {
Scott committed
1582
				$this->output('<form ' . $list['form']['tags'] . '>');
1583 1584 1585
				unset($list['form']['tags']); // we already output the tags before the messages
				$this->message_list_form($list);
			}
Scott committed
1586

1587
			$this->message_list($list);
Scott committed
1588

1589 1590
			if (!empty($list['form'])) {
				$this->output('</form>');
Scott committed
1591 1592
			}
		}
1593
	}
Scott committed
1594

1595 1596 1597 1598 1599 1600
	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
1601
		}
1602
	}
Scott committed
1603

1604 1605 1606
	public function message_list($list)
	{
		if (isset($list['messages'])) {
Scott committed
1607
			$this->output('<div class="qa-message-list" ' . @$list['tags'] . '>');
Scott committed
1608

Scott committed
1609
			foreach ($list['messages'] as $message) {
1610
				$this->message_item($message);
Scott committed
1611
			}
Scott committed
1612

1613
			$this->output('</div> <!-- END qa-message-list -->', '');
Scott committed
1614
		}
1615
	}
Scott committed
1616

1617 1618
	public function message_item($message)
	{
Scott committed
1619
		$this->output('<div class="qa-message-item" ' . @$message['tags'] . '>');
1620 1621 1622 1623 1624
		$this->message_content($message);
		$this->post_avatar_meta($message, 'qa-message');
		$this->message_buttons($message);
		$this->output('</div> <!-- END qa-message-item -->', '');
	}
Scott committed
1625

1626 1627 1628 1629 1630 1631
	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
1632
		}
1633
	}
Scott committed
1634

1635 1636 1637 1638 1639 1640
	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
1641
		}
1642
	}
Scott committed
1643

1644 1645 1646
	public function list_vote_disabled($items)
	{
		$disabled = false;
Scott committed
1647

1648 1649
		if (count($items)) {
			$disabled = true;
Scott committed
1650

1651 1652 1653
			foreach ($items as $item) {
				if (@$item['vote_on_page'] != 'disabled')
					$disabled = false;
Scott committed
1654 1655 1656
			}
		}

1657 1658
		return $disabled;
	}
Scott committed
1659

1660 1661 1662 1663
	public function q_list_and_form($q_list)
	{
		if (empty($q_list))
			return;
Scott committed
1664

1665
		$this->part_title($q_list);
Scott committed
1666

1667
		if (!empty($q_list['form']))
Scott committed
1668
			$this->output('<form ' . $q_list['form']['tags'] . '>');
Scott committed
1669

1670
		$this->q_list($q_list);
Scott committed
1671

1672 1673 1674 1675
		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
1676 1677
		}

1678 1679
		$this->part_footer($q_list);
	}
Scott committed
1680

1681 1682 1683 1684 1685 1686
	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
1687
		}
1688
	}
Scott committed
1689

1690 1691 1692
	public function q_list($q_list)
	{
		if (isset($q_list['qs'])) {
Scott committed
1693
			$this->output('<div class="qa-q-list' . ($this->list_vote_disabled($q_list['qs']) ? ' qa-q-list-vote-disabled' : '') . '">', '');
1694 1695
			$this->q_list_items($q_list['qs']);
			$this->output('</div> <!-- END qa-q-list -->', '');
Scott committed
1696
		}
1697
	}
Scott committed
1698

1699 1700
	public function q_list_items($q_items)
	{
Scott committed
1701
		foreach ($q_items as $q_item) {
1702
			$this->q_list_item($q_item);
Scott committed
1703
		}
1704
	}
Scott committed
1705

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

1710 1711 1712
		$this->q_item_stats($q_item);
		$this->q_item_main($q_item);
		$this->q_item_clear();
Scott committed
1713

1714 1715
		$this->output('</div> <!-- END qa-q-list-item -->', '');
	}
Scott committed
1716

1717 1718 1719
	public function q_item_stats($q_item)
	{
		$this->output('<div class="qa-q-item-stats">');
Scott committed
1720

1721 1722
		$this->voting($q_item);
		$this->a_count($q_item);
Scott committed
1723

1724 1725 1726 1727 1728 1729
		$this->output('</div>');
	}

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

1731 1732 1733
		$this->view_count($q_item);
		$this->q_item_title($q_item);
		$this->q_item_content($q_item);
Scott committed
1734

1735 1736 1737
		$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
1738

1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753
		$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">',
Scott committed
1754
			'<a href="' . $q_item['url'] . '">' . $q_item['title'] . '</a>',
1755
			// add closed note in title
Scott committed
1756
			empty($q_item['closed']['state']) ? '' : ' [' . $q_item['closed']['state'] . ']',
1757 1758 1759 1760 1761 1762 1763 1764 1765
			'</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
1766 1767
			$this->output('</div>');
		}
1768
	}
Scott committed
1769

1770 1771 1772 1773 1774 1775
	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
1776
		}
1777
	}
Scott committed
1778

1779 1780 1781
	public function voting($post)
	{
		if (isset($post['vote_view'])) {
Scott committed
1782
			$this->output('<div class="qa-voting ' . (($post['vote_view'] == 'updown') ? 'qa-voting-updown' : 'qa-voting-net') . '" ' . @$post['vote_tags'] . '>');
1783 1784
			$this->voting_inner_html($post);
			$this->output('</div>');
Scott committed
1785
		}
1786
	}
Scott committed
1787

1788 1789 1790 1791 1792 1793
	public function voting_inner_html($post)
	{
		$this->vote_buttons($post);
		$this->vote_count($post);
		$this->vote_clear();
	}
Scott committed
1794

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

Scott committed
1799
		switch (@$post['vote_state']) {
1800 1801 1802
			case 'voted_up':
				$this->post_hover_button($post, 'vote_up_tags', '+', 'qa-vote-one-button qa-voted-up');
				break;
Scott committed
1803

1804 1805 1806
			case 'voted_up_disabled':
				$this->post_disabled_button($post, 'vote_up_tags', '+', 'qa-vote-one-button qa-vote-up');
				break;
Scott committed
1807

1808 1809 1810
			case 'voted_down':
				$this->post_hover_button($post, 'vote_down_tags', '&ndash;', 'qa-vote-one-button qa-voted-down');
				break;
Scott committed
1811

1812 1813 1814
			case 'voted_down_disabled':
				$this->post_disabled_button($post, 'vote_down_tags', '&ndash;', 'qa-vote-one-button qa-vote-down');
				break;
Scott committed
1815

1816 1817 1818 1819
			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
1820

1821 1822 1823 1824
			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
1825

1826 1827 1828 1829 1830
			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
1831

1832 1833
		$this->output('</div>');
	}
Scott committed
1834

1835 1836 1837 1838
	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
1839

Scott committed
1840
		$this->output('<div class="qa-vote-count ' . (($post['vote_view'] == 'updown') ? 'qa-vote-count-updown' : 'qa-vote-count-net') . '"' . @$post['vote_count_tags'] . '>');
1841 1842 1843 1844

		if ($post['vote_view'] == 'updown') {
			$this->output_split($post['upvotes_view'], 'qa-upvote-count');
			$this->output_split($post['downvotes_view'], 'qa-downvote-count');
1845
		} else {
1846
			$this->output_split($post['netvotes_view'], 'qa-netvote-count');
1847
		}
Scott committed
1848

1849 1850
		$this->output('</div>');
	}
Scott committed
1851

1852 1853 1854 1855 1856 1857 1858
	public function vote_clear()
	{
		$this->output(
			'<div class="qa-vote-clear">',
			'</div>'
		);
	}
Scott committed
1859

1860 1861 1862
	public function a_count($post)
	{
		// You can also use $post['answers_raw'] to get a raw integer count of answers
Scott committed
1863

1864 1865 1866
		$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
1867

1868 1869 1870 1871 1872 1873 1874
	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');
	}

Scott committed
1875
	public function avatar($item, $class, $prefix = null)
1876 1877 1878 1879
	{
		if (isset($item['avatar'])) {
			if (isset($prefix))
				$this->output($prefix);
Scott committed
1880 1881

			$this->output(
Scott committed
1882
				'<span class="' . $class . '-avatar">',
1883 1884
				$item['avatar'],
				'</span>'
Scott committed
1885 1886
			);
		}
1887
	}
Scott committed
1888

1889 1890 1891
	public function a_selection($post)
	{
		$this->output('<div class="qa-a-selection">');
Scott committed
1892

1893 1894 1895 1896 1897 1898
		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
1899

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

1903 1904
		$this->output('</div>');
	}
Scott committed
1905

1906 1907 1908
	public function post_hover_button($post, $element, $value, $class)
	{
		if (isset($post[$element]))
Scott committed
1909
			$this->output('<input ' . $post[$element] . ' type="submit" value="' . $value . '" class="' . $class . '-button"/> ');
1910
	}
Scott committed
1911

1912 1913 1914
	public function post_disabled_button($post, $element, $value, $class)
	{
		if (isset($post[$element]))
Scott committed
1915
			$this->output('<input ' . $post[$element] . ' type="submit" value="' . $value . '" class="' . $class . '-disabled" disabled="disabled"/> ');
1916
	}
Scott committed
1917

Scott committed
1918
	public function post_avatar_meta($post, $class, $avatarprefix = null, $metaprefix = null, $metaseparator = '<br/>')
1919
	{
Scott committed
1920
		$this->output('<span class="' . $class . '-avatar-meta">');
1921 1922 1923 1924 1925 1926 1927
		$this->avatar($post, $class, $avatarprefix);
		$this->post_meta($post, $class, $metaprefix, $metaseparator);
		$this->output('</span>');
	}

	/**
	 * @deprecated Deprecated from 1.7; please use avatar() instead.
1928 1929
	 * @param array $post
	 * @param string $class
Scott committed
1930
	 * @param string $prefix
1931
	 */
Scott committed
1932
	public function post_avatar($post, $class, $prefix = null)
1933 1934 1935
	{
		$this->avatar($post, $class, $prefix);
	}
Scott committed
1936

Scott committed
1937
	public function post_meta($post, $class, $prefix = null, $separator = '<br/>')
1938
	{
Scott committed
1939
		$this->output('<span class="' . $class . '-meta">');
Scott committed
1940

1941 1942
		if (isset($prefix))
			$this->output($prefix);
Scott committed
1943

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

1946 1947 1948 1949 1950
		foreach ($order as $element) {
			switch ($element) {
				case 'what':
					$this->post_meta_what($post, $class);
					break;
Scott committed
1951

1952 1953 1954
				case 'when':
					$this->post_meta_when($post, $class);
					break;
Scott committed
1955

1956 1957 1958
				case 'where':
					$this->post_meta_where($post, $class);
					break;
Scott committed
1959

1960 1961 1962 1963
				case 'who':
					$this->post_meta_who($post, $class);
					break;
			}
Scott committed
1964 1965
		}

1966
		$this->post_meta_flags($post, $class);
Scott committed
1967

1968 1969
		if (!empty($post['what_2'])) {
			$this->output($separator);
Scott committed
1970 1971 1972 1973

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

					case 'when':
Scott committed
1978
						$this->output_split(@$post['when_2'], $class . '-when');
Scott committed
1979 1980 1981
						break;

					case 'who':
Scott committed
1982
						$this->output_split(@$post['who_2'], $class . '-who');
Scott committed
1983 1984 1985 1986 1987
						break;
				}
			}
		}

1988 1989
		$this->output('</span>');
	}
Scott committed
1990

1991 1992 1993
	public function post_meta_what($post, $class)
	{
		if (isset($post['what'])) {
Scott committed
1994
			$classes = $class . '-what';
Scott committed
1995
			if (isset($post['what_your']) && $post['what_your']) {
Scott committed
1996
				$classes .= ' ' . $class . '-what-your';
Scott committed
1997
			}
Scott committed
1998

Scott committed
1999 2000 2001 2002
			if (isset($post['what_url'])) {
				$tags = isset($post['what_url_tags']) ? $post['what_url_tags'] : '';
				$this->output('<a href="' . $post['what_url'] . '" class="' . $classes . '"' . $tags . '>' . $post['what'] . '</a>');
			} else {
Scott committed
2003
				$this->output('<span class="' . $classes . '">' . $post['what'] . '</span>');
Scott committed
2004
			}
Scott committed
2005
		}
2006
	}
Scott committed
2007

2008 2009
	public function post_meta_when($post, $class)
	{
Scott committed
2010
		$this->output_split(@$post['when'], $class . '-when');
2011
	}
Scott committed
2012

2013 2014
	public function post_meta_where($post, $class)
	{
Scott committed
2015
		$this->output_split(@$post['where'], $class . '-where');
2016
	}
Scott committed
2017

2018 2019 2020
	public function post_meta_who($post, $class)
	{
		if (isset($post['who'])) {
Scott committed
2021
			$this->output('<span class="' . $class . '-who">');
Scott committed
2022

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

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

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

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

2034
			if (isset($post['who']['points'])) {
Scott committed
2035
				$post['who']['points']['prefix'] = '(' . $post['who']['points']['prefix'];
2036
				$post['who']['points']['suffix'] .= ')';
Scott committed
2037
				$this->output_split($post['who']['points'], $class . '-who-points');
Scott committed
2038 2039
			}

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

2043
			$this->output('</span>');
Scott committed
2044
		}
2045
	}
Scott committed
2046

2047 2048
	public function post_meta_flags($post, $class)
	{
Scott committed
2049
		$this->output_split(@$post['flags'], $class . '-flags');
2050
	}
Scott committed
2051

2052 2053 2054
	public function post_tags($post, $class)
	{
		if (!empty($post['q_tags'])) {
Scott committed
2055
			$this->output('<div class="' . $class . '-tags">');
2056 2057
			$this->post_tag_list($post, $class);
			$this->output('</div>');
Scott committed
2058
		}
2059
	}
Scott committed
2060

2061 2062
	public function post_tag_list($post, $class)
	{
Scott committed
2063
		$this->output('<ul class="' . $class . '-tag-list">');
Scott committed
2064

Scott committed
2065
		foreach ($post['q_tags'] as $taghtml) {
2066
			$this->post_tag_item($taghtml, $class);
Scott committed
2067
		}
Scott committed
2068

2069 2070
		$this->output('</ul>');
	}
Scott committed
2071

2072 2073
	public function post_tag_item($taghtml, $class)
	{
Scott committed
2074
		$this->output('<li class="' . $class . '-tag-item">' . $taghtml . '</li>');
2075
	}
Scott committed
2076

2077 2078 2079
	public function page_links()
	{
		$page_links = @$this->content['page_links'];
Scott committed
2080

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

2084 2085 2086
			$this->page_links_label(@$page_links['label']);
			$this->page_links_list(@$page_links['items']);
			$this->page_links_clear();
Scott committed
2087

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

2092 2093 2094
	public function page_links_label($label)
	{
		if (!empty($label))
Scott committed
2095
			$this->output('<span class="qa-page-links-label">' . $label . '</span>');
2096
	}
Scott committed
2097

2098 2099 2100 2101
	public function page_links_list($page_items)
	{
		if (!empty($page_items)) {
			$this->output('<ul class="qa-page-links-list">');
Scott committed
2102

2103
			$index = 0;
Scott committed
2104

2105 2106 2107
			foreach ($page_items as $page_link) {
				$this->set_context('page_index', $index++);
				$this->page_links_item($page_link);
Scott committed
2108

2109 2110
				if ($page_link['ellipsis'])
					$this->page_links_item(array('type' => 'ellipsis'));
Scott committed
2111 2112
			}

2113
			$this->clear_context('page_index');
Scott committed
2114

2115
			$this->output('</ul>');
Scott committed
2116
		}
2117
	}
Scott committed
2118

2119 2120 2121 2122 2123 2124
	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
2125

2126 2127 2128 2129
	public function page_link_content($page_link)
	{
		$label = @$page_link['label'];
		$url = @$page_link['url'];
Scott committed
2130

2131 2132
		switch ($page_link['type']) {
			case 'this':
Scott committed
2133
				$this->output('<span class="qa-page-selected">' . $label . '</span>');
2134
				break;
Scott committed
2135

2136
			case 'prev':
Scott committed
2137
				$this->output('<a href="' . $url . '" class="qa-page-prev">&laquo; ' . $label . '</a>');
2138
				break;
Scott committed
2139

2140
			case 'next':
Scott committed
2141
				$this->output('<a href="' . $url . '" class="qa-page-next">' . $label . ' &raquo;</a>');
2142
				break;
Scott committed
2143

2144 2145 2146 2147 2148
			case 'ellipsis':
				$this->output('<span class="qa-page-ellipsis">...</span>');
				break;

			default:
Scott committed
2149
				$this->output('<a href="' . $url . '" class="qa-page-link">' . $label . '</a>');
2150
				break;
Scott committed
2151
		}
2152
	}
Scott committed
2153

2154 2155 2156 2157 2158 2159 2160
	public function page_links_clear()
	{
		$this->output(
			'<div class="qa-page-links-clear">',
			'</div>'
		);
	}
Scott committed
2161

2162 2163 2164
	public function suggest_next()
	{
		$suggest = @$this->content['suggest_next'];
Scott committed
2165

2166 2167 2168
		if (!empty($suggest)) {
			$this->output('<div class="qa-suggest-next">');
			$this->output($suggest);
Scott committed
2169 2170
			$this->output('</div>');
		}
2171
	}
Scott committed
2172

2173 2174 2175
	public function q_view($q_view)
	{
		if (!empty($q_view)) {
Scott committed
2176
			$this->output('<div class="qa-q-view' . (@$q_view['hidden'] ? ' qa-q-view-hidden' : '') . rtrim(' ' . @$q_view['classes']) . '"' . rtrim(' ' . @$q_view['tags']) . '>');
Scott committed
2177

2178 2179 2180
			if (isset($q_view['main_form_tags'])) {
				$this->output('<form ' . $q_view['main_form_tags'] . '>'); // form for question voting buttons
			}
2181 2182

			$this->q_view_stats($q_view);
Scott committed
2183 2184

			if (isset($q_view['main_form_tags'])) {
2185
				$this->form_hidden_elements(@$q_view['voting_form_hidden']);
Scott committed
2186 2187 2188
				$this->output('</form>');
			}

2189 2190
			$this->q_view_main($q_view);
			$this->q_view_clear();
Scott committed
2191

2192
			$this->output('</div> <!-- END qa-q-view -->', '');
Scott committed
2193
		}
2194
	}
Scott committed
2195

2196 2197 2198
	public function q_view_stats($q_view)
	{
		$this->output('<div class="qa-q-view-stats">');
Scott committed
2199

2200 2201
		$this->voting($q_view);
		$this->a_count($q_view);
Scott committed
2202

2203 2204
		$this->output('</div>');
	}
Scott committed
2205

2206 2207 2208
	public function q_view_main($q_view)
	{
		$this->output('<div class="qa-q-view-main">');
Scott committed
2209

2210
		if (isset($q_view['main_form_tags'])) {
Scott committed
2211
			$this->output('<form ' . $q_view['main_form_tags'] . '>'); // form for buttons on question
2212
		}
2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225

		$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);

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

2228
		$this->c_list(@$q_view['c_list'], 'qa-q-view');
2229 2230 2231 2232 2233 2234 2235
		$this->c_form(@$q_view['c_form']);

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

	public function q_view_content($q_view)
	{
Scott committed
2236 2237
		$content = isset($q_view['content']) ? $q_view['content'] : '';

Scott committed
2238
		$this->output('<div class="qa-q-view-content qa-post-content">');
Scott committed
2239 2240
		$this->output_raw($content);
		$this->output('</div>');
2241
	}
Scott committed
2242

2243 2244 2245
	public function q_view_follows($q_view)
	{
		if (!empty($q_view['follows']))
Scott committed
2246
			$this->output(
2247 2248
				'<div class="qa-q-view-follows">',
				$q_view['follows']['label'],
Scott committed
2249
				'<a href="' . $q_view['follows']['url'] . '" class="qa-q-view-follows-link">' . $q_view['follows']['title'] . '</a>',
Scott committed
2250 2251
				'</div>'
			);
2252
	}
Scott committed
2253

2254 2255 2256 2257
	public function q_view_closed($q_view)
	{
		if (!empty($q_view['closed'])) {
			$haslink = isset($q_view['closed']['url']);
Scott committed
2258

2259 2260 2261
			$this->output(
				'<div class="qa-q-view-closed">',
				$q_view['closed']['label'],
Scott committed
2262
				($haslink ? ('<a href="' . $q_view['closed']['url'] . '"') : '<span') . ' class="qa-q-view-closed-content">',
2263 2264 2265 2266
				$q_view['closed']['content'],
				$haslink ? '</a>' : '</span>',
				'</div>'
			);
Scott committed
2267
		}
2268
	}
Scott committed
2269

2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280
	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
2281
		}
2282
	}
Scott committed
2283

2284 2285 2286 2287 2288 2289
	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
2290
		}
2291
	}
Scott committed
2292

2293 2294 2295 2296 2297 2298 2299
	public function q_view_clear()
	{
		$this->output(
			'<div class="qa-q-view-clear">',
			'</div>'
		);
	}
Scott committed
2300

2301 2302
	public function a_form($a_form)
	{
Scott committed
2303 2304
		$this->output('<div class="qa-a-form"' . (isset($a_form['id']) ? (' id="' . $a_form['id'] . '"') : '') .
			(@$a_form['collapse'] ? ' style="display:none;"' : '') . '>');
Scott committed
2305

2306 2307
		$this->form($a_form);
		$this->c_list(@$a_form['c_list'], 'qa-a-item');
Scott committed
2308

2309 2310
		$this->output('</div> <!-- END qa-a-form -->', '');
	}
Scott committed
2311

2312 2313 2314 2315
	public function a_list($a_list)
	{
		if (!empty($a_list)) {
			$this->part_title($a_list);
Scott committed
2316

Scott committed
2317
			$this->output('<div class="qa-a-list' . ($this->list_vote_disabled($a_list['as']) ? ' qa-a-list-vote-disabled' : '') . '" ' . @$a_list['tags'] . '>', '');
2318 2319
			$this->a_list_items($a_list['as']);
			$this->output('</div> <!-- END qa-a-list -->', '');
Scott committed
2320
		}
2321
	}
Scott committed
2322

2323 2324
	public function a_list_items($a_items)
	{
Scott committed
2325
		foreach ($a_items as $a_item) {
2326
			$this->a_list_item($a_item);
Scott committed
2327
		}
2328
	}
Scott committed
2329

2330 2331
	public function a_list_item($a_item)
	{
Scott committed
2332
		$extraclass = @$a_item['classes'] . ($a_item['hidden'] ? ' qa-a-list-item-hidden' : ($a_item['selected'] ? ' qa-a-list-item-selected' : ''));
Scott committed
2333

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

2336 2337 2338
		if (isset($a_item['main_form_tags'])) {
			$this->output('<form ' . $a_item['main_form_tags'] . '>'); // form for answer voting buttons
		}
Scott committed
2339

2340
		$this->voting($a_item);
Scott committed
2341

2342 2343 2344 2345
		if (isset($a_item['main_form_tags'])) {
			$this->form_hidden_elements(@$a_item['voting_form_hidden']);
			$this->output('</form>');
		}
Scott committed
2346

2347 2348
		$this->a_item_main($a_item);
		$this->a_item_clear();
Scott committed
2349

2350 2351
		$this->output('</div> <!-- END qa-a-list-item -->', '');
	}
Scott committed
2352

2353 2354 2355
	public function a_item_main($a_item)
	{
		$this->output('<div class="qa-a-item-main">');
Scott committed
2356

2357
		if (isset($a_item['main_form_tags'])) {
Scott committed
2358
			$this->output('<form ' . $a_item['main_form_tags'] . '>'); // form for buttons on answer
2359
		}
Scott committed
2360

2361 2362 2363 2364
		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
2365

2366 2367 2368 2369
		$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
2370

2371 2372
		if ($a_item['hidden'] || $a_item['selected'])
			$this->output('</div>');
Scott committed
2373

2374
		$this->a_item_buttons($a_item);
Scott committed
2375

2376 2377 2378
		if (isset($a_item['main_form_tags'])) {
			$this->form_hidden_elements(@$a_item['buttons_form_hidden']);
			$this->output('</form>');
Scott committed
2379 2380
		}

2381
		$this->c_list(@$a_item['c_list'], 'qa-a-item');
2382
		$this->c_form(@$a_item['c_form']);
Scott committed
2383

2384 2385
		$this->output('</div> <!-- END qa-a-item-main -->');
	}
Scott committed
2386

2387 2388 2389 2390 2391 2392 2393
	public function a_item_clear()
	{
		$this->output(
			'<div class="qa-a-item-clear">',
			'</div>'
		);
	}
Scott committed
2394

2395 2396
	public function a_item_content($a_item)
	{
Scott committed
2397 2398 2399 2400
		if (!isset($a_item['content'])) {
			$a_item['content'] = '';
		}

Scott committed
2401
		$this->output('<div class="qa-a-item-content qa-post-content">');
2402 2403 2404
		$this->output_raw($a_item['content']);
		$this->output('</div>');
	}
Scott committed
2405

2406 2407 2408 2409 2410 2411
	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
2412
		}
2413
	}
Scott committed
2414

2415 2416
	public function c_form($c_form)
	{
Scott committed
2417 2418
		$this->output('<div class="qa-c-form"' . (isset($c_form['id']) ? (' id="' . $c_form['id'] . '"') : '') .
			(@$c_form['collapse'] ? ' style="display:none;"' : '') . '>');
Scott committed
2419

2420
		$this->form($c_form);
Scott committed
2421

2422 2423
		$this->output('</div> <!-- END qa-c-form -->', '');
	}
Scott committed
2424

2425 2426 2427
	public function c_list($c_list, $class)
	{
		if (!empty($c_list)) {
Scott committed
2428
			$this->output('', '<div class="' . $class . '-c-list"' . (@$c_list['hidden'] ? ' style="display:none;"' : '') . ' ' . @$c_list['tags'] . '>');
2429 2430
			$this->c_list_items($c_list['cs']);
			$this->output('</div> <!-- END qa-c-list -->', '');
Scott committed
2431
		}
2432
	}
Scott committed
2433

2434 2435
	public function c_list_items($c_items)
	{
Scott committed
2436
		foreach ($c_items as $c_item) {
2437
			$this->c_list_item($c_item);
Scott committed
2438
		}
2439
	}
Scott committed
2440

2441 2442
	public function c_list_item($c_item)
	{
Scott committed
2443
		$extraclass = @$c_item['classes'] . (@$c_item['hidden'] ? ' qa-c-item-hidden' : '');
Scott committed
2444

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

2447 2448 2449 2450
		if (isset($c_item['vote_view']) && isset($c_item['main_form_tags'])) {
			// form for comment voting buttons
			$this->output('<form ' . $c_item['main_form_tags'] . '>');
			$this->voting($c_item);
2451 2452 2453 2454
			$this->form_hidden_elements(@$c_item['voting_form_hidden']);
			$this->output('</form>');
		}

2455 2456
		$this->c_item_main($c_item);
		$this->c_item_clear();
Scott committed
2457

2458 2459
		$this->output('</div> <!-- END qa-c-item -->');
	}
Scott committed
2460

2461 2462
	public function c_item_main($c_item)
	{
2463 2464 2465 2466
		if (isset($c_item['main_form_tags'])) {
			$this->output('<form ' . $c_item['main_form_tags'] . '>'); // form for buttons on comment
		}

2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479
		$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>');
2480 2481 2482 2483 2484

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

2487 2488 2489
	public function c_item_link($c_item)
	{
		$this->output(
Scott committed
2490
			'<a href="' . $c_item['url'] . '" class="qa-c-item-link">' . $c_item['title'] . '</a>'
2491 2492
		);
	}
Scott committed
2493

2494 2495 2496
	public function c_item_expand($c_item)
	{
		$this->output(
Scott committed
2497
			'<a href="' . $c_item['url'] . '" ' . $c_item['expand_tags'] . ' class="qa-c-item-expand">' . $c_item['title'] . '</a>'
2498 2499
		);
	}
Scott committed
2500

2501 2502
	public function c_item_content($c_item)
	{
Scott committed
2503 2504 2505 2506
		if (!isset($c_item['content'])) {
			$c_item['content'] = '';
		}

Scott committed
2507
		$this->output('<div class="qa-c-item-content qa-post-content">');
2508 2509 2510
		$this->output_raw($c_item['content']);
		$this->output('</div>');
	}
Scott committed
2511

2512 2513 2514 2515 2516
	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
2517 2518 2519 2520
			$this->output('</div>');
		}
	}

2521 2522 2523 2524 2525 2526 2527 2528 2529
	public function c_item_clear()
	{
		$this->output(
			'<div class="qa-c-item-clear">',
			'</div>'
		);
	}


Scott committed
2530 2531
	/**
	 * Generic method to output a basic list of question links.
Scott committed
2532 2533
	 * @param array $q_list
	 * @param string $attrs
Scott committed
2534
	 */
Scott committed
2535
	public function q_title_list($q_list, $attrs = null)
2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546
	{
		$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
2547

Scott committed
2548 2549
	/**
	 * Output block of similar questions when asking.
Scott committed
2550 2551
	 * @param array $q_list
	 * @param string $pretext
Scott committed
2552
	 */
Scott committed
2553
	public function q_ask_similar($q_list, $pretext = '')
2554 2555 2556 2557 2558 2559 2560
	{
		if (!count($q_list))
			return;

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

		if (strlen($pretext) > 0)
Scott committed
2561
			$this->output('<p class="qa-ask-similar-title">' . $pretext . '</p>');
2562 2563 2564 2565 2566
		$this->q_title_list($q_list, 'target="_blank"');

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