diff options
191 files changed, 7072 insertions, 5015 deletions
diff --git a/.travis.yml b/.travis.yml index bfe58bee7d..4f4705e4f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,13 +32,7 @@ script:    - sh -c "if [ '$TRAVIS_PHP_VERSION' = '5.5' -a '$DB' = 'mysql' ]; then ../phpBB/vendor/bin/phing sniff; fi"    - cd ..    - phpBB/vendor/bin/phpunit --configuration travis/phpunit-$DB-travis.xml -   -notifications: -  email: -    recipients: -      - dev-team@phpbb.com -    on_success: change -    on_failure: change +  - sh -c "if [ '$TRAVIS_PULL_REQUEST' != 'false' ]; then git-tools/commit-msg-hook-range.sh $TRAVIS_COMMIT_RANGE; fi"  matrix:    allow_failures: diff --git a/build/build.xml b/build/build.xml index ebc1f0be8b..d4ba927d96 100644 --- a/build/build.xml +++ b/build/build.xml @@ -165,8 +165,6 @@  	<target name="package" depends="clean,prepare,prepare-new-version,old-version-diffs">  		<exec dir="build" command="php -f package.php '${versions}' > logs/package.log" escape="false" /> -		<exec dir="build" command="php -f build_diff.php '${prevversion}' '${newversion}' > logs/build_diff.log" escape="false" /> -  		<exec dir="build" escape="false"  			command="diff -crNEBwd old_versions/release-${prevversion}/language new_version/phpBB3/language >  				save/save_${prevversion}_to_${newversion}/language/phpbb-${prevversion}_to_${newversion}_language.patch" /> diff --git a/build/build_diff.php b/build/build_diff.php deleted file mode 100755 index 68bac65a66..0000000000 --- a/build/build_diff.php +++ /dev/null @@ -1,409 +0,0 @@ -#!/usr/bin/env php -<?php -/** -* -* @package build -* @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -if ($_SERVER['argc'] != 3) -{ -	die("Please specify the previous and current version as arguments (e.g. build_diff.php '1.0.2' '1.0.3')."); -} - -$old_version = trim($_SERVER['argv'][1]); -$new_version = trim($_SERVER['argv'][2]); - -$substitute_old = $old_version; -$substitute_new = $new_version; -$simple_name_old = 'release-' . $old_version; -$simple_name_new = 'release-' . $new_version; -$echo_changes = false; - -// DO NOT EVER USE THE FOLLOWING! Fix the script to generate proper changes, -// do NOT manually create them. - -// Set this to true to just compress the changes and do not build them again -// This should be used for building custom modified txt file. ;) -$package_changed_files = false; - -//$debug_file = 'includes/functions_user.php'; //'styles/prosilver/style.cfg'; -$debug_file = false; - -if ($debug_file !== false) -{ -	$echo_changes = false; -} - -$s_name = 'save_' . $substitute_old . '_to_' . $substitute_new; - -$location = dirname(__FILE__); - -if (!$package_changed_files) -{ -	if (!$echo_changes) -	{ -		// Create directory... -		run_command("mkdir $location/save/{$s_name}"); -		run_command("mkdir $location/save/{$s_name}/language"); -		run_command("mkdir $location/save/{$s_name}/prosilver"); -		run_command("mkdir $location/save/{$s_name}/subsilver2"); -	} -} - -// Build code changes and place them into 'save' -if (!$package_changed_files) -{ -	build_code_changes('language'); -	build_code_changes('prosilver'); -	build_code_changes('subsilver2'); -} - -// Package code changes -$code_changes_filename = 'phpBB-' . $substitute_old . '_to_' . $substitute_new . '-codechanges'; - -if (!$echo_changes) -{ -	// Now compress the files... -	// Build Main phpBB Release -	$compress_programs = array( -//		'tar.gz'	=> 'tar -czf', -		'tar.bz2'	=> 'tar -cjf', -		'zip'		=> 'zip -r' -	); - -	chdir($location . '/save/' . $s_name); -	foreach ($compress_programs as $extension => $compress_command) -	{ -		echo "Packaging code changes for $extension\n"; -		run_command("rm ./../../new_version/release_files/{$code_changes_filename}.{$extension}"); -		flush(); - -		// Build Package -		run_command("$compress_command ./../../new_version/release_files/{$code_changes_filename}.{$extension} *"); -		flush(); -	} -} - -/** -* $output_format can be: language, prosilver and subsilver2 -*/ -function build_code_changes($output_format) -{ -	global $substitute_new, $substitute_old, $simple_name_old, $simple_name_new, $echo_changes, $package_changed_files, $location, $debug_file, $s_name; - -	// Global array holding the data entries -	$data = array( -		'header'		=> array(), -		'diff'			=> array(), -	); - -	// Read diff file and prepare the output filedata... -	//$patch_filename = '../new_version/patches/phpBB-' . $substitute_old . '_to_' . $substitute_new . '.patch'; -	$release_filename = 'phpbb-' . $substitute_old . '_to_' . $substitute_new . '_' . $output_format . '.txt'; - -	if (!$package_changed_files) -	{ -		if (!$echo_changes) -		{ -			$fp = fopen('save/' . $s_name . '/' . $output_format . '/' . $release_filename, 'wb'); - -			if (!$fp) -			{ -				die('Unable to create ' . $release_filename); -			} -		} -	} - -	include_once($location . '/build_helper.php'); -	$package = new build_package(array($substitute_old, $substitute_new), false); - -	$titles = array( -		'language'		=> 'phpBB ' . $substitute_old . ' to phpBB ' . $substitute_new . ' Language Pack Changes', -		'prosilver'		=> 'phpBB ' . $substitute_old . ' to phpBB ' . $substitute_new . ' prosilver Changes', -		'subsilver2'	=> 'phpBB ' . $substitute_old . ' to phpBB ' . $substitute_new . ' subsilver2 Changes', -	); - -	$data['header'] = array( -		'title'		=> $titles[$output_format], -		'intro'		=> ' - -These are the ' . $titles[$output_format] . ' summed up into a little Mod. These changes are only partial and do not include any code changes, therefore not meant for updating phpBB. - -	', -		'included_files'	=> array(), -	); - -	// We collect the files we want to diff first (ironically we grab this from a diff file) -	if (!$echo_changes) -	{ -		echo "\n\nCollecting Filenames:"; -	} - -	// We re-create the patch file -	foreach ($package->old_packages as $_package_name => $dest_package_filename) -	{ -		chdir($package->locations['old_versions']); - -		if (!$echo_changes) -		{ -			echo "\n\n" . 'Creating patch/diff files for phpBB-' . $dest_package_filename . $package->get('new_version_number'); -		} - -		$dest_package_filename = $location . '/save/' . $s_name . '/phpBB-' . $dest_package_filename . $package->get('new_version_number') . '.patch'; -		$package->run_command('diff ' . $package->diff_options . ' ' . $_package_name . ' ' . $package->get('simple_name') . ' > ' . $dest_package_filename); - -		// Parse this diff to determine file changes from the checked versions and save them -		$result = $package->collect_diff_files($dest_package_filename, $_package_name); -		$package->run_command('rm ' . $dest_package_filename); -	} - -	chdir($location); - -	$filenames = array(); -	foreach ($result['files'] as $filename) -	{ -		if ($debug_file !== false && $filename != $debug_file) -		{ -			continue; -		} - -		// Decide which files to compare... -		switch ($output_format) -		{ -			case 'language': -				if (strpos($filename, 'language/en/') !== 0) -				{ -					continue 2; -				} -			break; - -			case 'prosilver': -				if (strpos($filename, 'styles/prosilver/') !== 0) -				{ -					continue 2; -				} -			break; - -			case 'subsilver2': -				if (strpos($filename, 'styles/subsilver2/') !== 0) -				{ -					continue 2; -				} -			break; -		} - -		if (!file_exists($location . '/old_versions/' . $simple_name_old . '/' . $filename)) -		{ -			// New file... include it -			$data['header']['included_files'][] = array( -				'old'				=> $location . '/old_versions/' . $simple_name_old . '/' . $filename, -				'new'				=> $location . '/old_versions/' . $simple_name_new . '/' . $filename, -				'phpbb_filename'	=> $filename, -			); -			continue; -		} - -		$filenames[] = array( -			'old'				=> $location . '/old_versions/' . $simple_name_old . '/' . $filename, -			'new'				=> $location . '/old_versions/' . $simple_name_new . '/' . $filename, -			'phpbb_filename'	=> $filename, -		); -	} - -	// Now let us go through the filenames list and create a more comprehensive diff -	if (!$echo_changes) -	{ -		fwrite($fp, build_header($output_format, $filenames, $data['header'])); -	} -	else -	{ -		//echo build_header('text', $filenames, $data['header']); -	} - -	// Copy files... -	$files_to_copy = array(); - -	foreach ($data['header']['included_files'] as $filename) -	{ -		$files_to_copy[] = $filename['phpbb_filename']; -	} - -	// First step is to copy the new version over (clean structure) -	if (!$echo_changes && sizeof($files_to_copy)) -	{ -		foreach ($files_to_copy as $file) -		{ -			// Create directory? -			$dirname = dirname($file); - -			if ($dirname) -			{ -				$dirname = explode('/', $dirname); -				$__dir = array(); - -				foreach ($dirname as $i => $dir) -				{ -					$__dir[] = $dir; -					run_command("mkdir -p $location/save/" . $s_name . '/' . $output_format . '/' . implode('/', $__dir)); -				} -			} - -			$source_file = $location . '/new_version/phpBB3/' . $file; -			$dest_file = $location . '/save/' . $s_name . '/' . $output_format . '/'; -			$dest_file .= $file; - -			$command = "cp -p $source_file $dest_file"; -			$result = trim(`$command`); -			echo "- Copied File: " . $source_file . " -> " . $dest_file . "\n"; -		} -	} - -	include_once('diff_class.php'); - -	if (!$echo_changes) -	{ -		echo "\n\nDiffing Codebases:"; -	} - -	foreach ($filenames as $file_ary) -	{ -		if (!file_exists($file_ary['old'])) -		{ -			$lines1 = array(); -		} -		else -		{ -			$lines1 = file($file_ary['old']); -		} -		$lines2 = file($file_ary['new']); - -		if (!sizeof($lines1)) -		{ -			// New File -		} -		else -		{ -			$diff = new Diff($lines1, $lines2); -			$fmt = new BBCodeDiffFormatter(false, 5, $debug_file); - -			if (!$echo_changes) -			{ -				fwrite($fp, $fmt->format_open($file_ary['phpbb_filename'])); -				fwrite($fp, $fmt->format($diff, $lines1)); -				fwrite($fp, $fmt->format_close($file_ary['phpbb_filename'])); -			} -			else -			{ -				echo $fmt->format_open($file_ary['phpbb_filename']); -				echo $fmt->format($diff, $lines1); -				echo $fmt->format_close($file_ary['phpbb_filename']); -			} - -			if ($debug_file !== false) -			{ -				echo $fmt->format_open($file_ary['phpbb_filename']); -				echo $fmt->format($diff, $lines1); -				echo $fmt->format_close($file_ary['phpbb_filename']); -				exit; -			} -		} -	} - -	if (!$echo_changes) -	{ -		fwrite($fp, build_footer($output_format)); - -		// Close file -		fclose($fp); - -		chmod('save/' . $s_name . '/' . $output_format . '/' . $release_filename, 0666); -	} -	else -	{ -		echo build_footer($output_format); -	} -} - -/** -* Build Footer -*/ -function build_footer($mode) -{ -	$html = ''; - -	$html .= "# \n"; -	$html .= "#-----[ SAVE/CLOSE ALL FILES ]------------------------------------------ \n"; -	$html .= "# \n"; -	$html .= "# EoM"; - -	return $html; -} - -/** -* Build Header -*/ -function build_header($mode, $filenames, $header) -{ -	global $substitute_old; - -	$html = ''; - -	$html .= "############################################################## \n"; -	$html .= "## Title: " . $header['title'] . "\n"; -	$html .= "## Author: naderman < naderman@phpbb.com > (Nils Adermann) http://www.phpbb.com \n"; -	$html .= "## Description: \n"; - -	$intr = explode("\n", $header['intro']); -	$introduction = ''; -	foreach ($intr as $_line) -	{ -		$introduction .= wordwrap($_line, 80) . "\n"; -	} -	$intr = explode("\n", $introduction); - -	foreach ($intr as $_line) -	{ -		$html .= "##		" . $_line . "\n"; -	} -	$html .= "## \n"; -	$html .= "## Files To Edit: \n"; - -	foreach ($filenames as $file_ary) -	{ -		$html .= "##		" . $file_ary['phpbb_filename'] . "\n"; -	} -	$html .= "##\n"; -	if (sizeof($header['included_files'])) -	{ -		$html .= "## Included Files: \n"; -		foreach ($header['included_files'] as $filename) -		{ -			$html .= "##		{$filename['phpbb_filename']}\n"; -		} -	} -	$html .= "## License: http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 \n"; -	$html .= "############################################################## \n"; -	$html .= "\n"; - -	// COPY Statement? -	if (sizeof($header['included_files'])) -	{ -		$html .= "#\n#-----[ COPY ]------------------------------------------\n#\n"; -		foreach ($header['included_files'] as $filename) -		{ -			$html .= "copy {$filename['phpbb_filename']} to {$filename['phpbb_filename']}\n"; -		} -		$html .= "\n"; -	} - -	return $html; -} - -function run_command($command) -{ -	$result = trim(`$command`); -	echo "\n- Command Run: " . $command . "\n"; -} diff --git a/build/diff_class.php b/build/diff_class.php deleted file mode 100644 index 2d8555400d..0000000000 --- a/build/diff_class.php +++ /dev/null @@ -1,1710 +0,0 @@ -<?php -/** -* -* @package build -* @copyright (c) 2000 Geoffrey T. Dairiki <dairiki@dairiki.org> -* @copyright (c) 2010 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -/** -* Class used internally by WikiDiff to actually compute the diffs. -* -* The algorithm used here is mostly lifted from the perl module -* Algorithm::Diff (version 1.06) by Ned Konz, which is available at: -*   http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip -* -* More ideas are taken from: -*   http://www.ics.uci.edu/~eppstein/161/960229.html -* -* Some ideas are (and a bit of code) are from from analyze.c, from GNU -* diffutils-2.7, which can be found at: -*   ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz -* -* Finally, some ideas (subdivision by NCHUNKS > 2, and some optimizations) -* are my own. -*/ -class _WikiDiffEngine -{ -	var $edits;	// List of editing operation to convert XV to YV. -	var $xv = array(), $yv = array(); - -	function _WikiDiffEngine($from_lines, $to_lines) -	{ -		$n_from = sizeof($from_lines); -		$n_to = sizeof($to_lines); - -		$endskip = 0; -		// Ignore trailing and leading matching lines. -		while ($n_from > 0 && $n_to > 0) -		{ -			if ($from_lines[$n_from - 1] != $to_lines[$n_to - 1]) -			{ -				break; -			} - -			$n_from--; -			$n_to--; -			$endskip++; -		} - -		for ($skip = 0; $skip < min($n_from, $n_to); $skip++) -		{ -			if ($from_lines[$skip] != $to_lines[$skip]) -			{ -				break; -			} -		} -		$n_from -= $skip; -		$n_to -= $skip; - -		$xlines = array(); -		$ylines = array(); - -		// Ignore lines which do not exist in both files. -		for ($x = 0; $x < $n_from; $x++) -		{ -			$xhash[$from_lines[$x + $skip]] = 1; -		} - -		for ($y = 0; $y < $n_to; $y++) -		{ -			$line = $to_lines[$y + $skip]; -			$ylines[] = $line; - -			if (($this->ychanged[$y] = empty($xhash[$line]))) -			{ -				continue; -			} -			$yhash[$line] = 1; -			$this->yv[] = $line; -			$this->yind[] = $y; -		} - -		for ($x = 0; $x < $n_from; $x++) -		{ -			$line = $from_lines[$x + $skip]; -			$xlines[] = $line; - -			if (($this->xchanged[$x] = empty($yhash[$line]))) -			{ -				continue;	// fixme? what happens to yhash/xhash when -							// there are two identical lines?? -			} -			$this->xv[] = $line; -			$this->xind[] = $x; -		} - -		// Find the LCS. -		$this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv)); - -		// Merge edits when possible -		$this->_shift_boundaries($xlines, $this->xchanged, $this->ychanged); -		$this->_shift_boundaries($ylines, $this->ychanged, $this->xchanged); - -		// Compute the edit operations. -		$this->edits = array(); - -		if ($skip) -		{ -			$this->edits[] = $skip; -		} - -		$x = 0; -		$y = 0; - -		while ($x < $n_from || $y < $n_to) -		{ -			// Skip matching "snake". -			$x0 = $x; -			$ncopy = 0; -			while ($x < $n_from && $y < $n_to && !$this->xchanged[$x] && !$this->ychanged[$y]) -			{ -				++$x; -				++$y; -				++$ncopy; -			} - -			if ($x > $x0) -			{ -				$this->edits[] = $x - $x0; -			} - -			// Find deletes. -			$x0 = $x; -			$ndelete = 0; - -			while ($x < $n_from && $this->xchanged[$x]) -			{ -				++$x; -				++$ndelete; -			} - -			if ($x > $x0) -			{ -				$this->edits[] = -($x - $x0); -			} - -			// Find adds. -			if (isset($this->ychanged[$y]) && $this->ychanged[$y]) -			{ -				$adds = array(); -				while ($y < $n_to && $this->ychanged[$y]) -				{ -					$adds[] = $ylines[$y++]; -				} -				$this->edits[] = $adds; -			} -		} - -		if (!empty($endskip)) -		{ -			$this->edits[] = $endskip; -		} -	} - -	/* Divide the Largest Common Subsequence (LCS) of the sequences -	* [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally -	* sized segments. -	* -	* Returns (LCS, PTS).  LCS is the length of the LCS. PTS is an -	* array of NCHUNKS+1 (X, Y) indexes giving the diving points between -	* sub sequences.  The first sub-sequence is contained in [X0, X1), -	* [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on.  Note -	* that (X0, Y0) == (XOFF, YOFF) and -	* (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). -	* -	* This function assumes that the first lines of the specified portions -	* of the two files do not match, and likewise that the last lines do not -	* match.  The caller must trim matching lines from the beginning and end -	* of the portions it is going to specify. -	*/ -	function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) -	{ -		$flip = false; - -		if ($xlim - $xoff > $ylim - $yoff) -		{ -			// Things seems faster (I'm not sure I understand why) -			// when the shortest sequence in X. -			$flip = true; -			list ($xoff, $xlim, $yoff, $ylim) = array( $yoff, $ylim, $xoff, $xlim); -		} - -		if ($flip) -		{ -			for ($i = $ylim - 1; $i >= $yoff; $i--) -			{ -				$ymatches[$this->xv[$i]][] = $i; -			} -		} -		else -		{ -			for ($i = $ylim - 1; $i >= $yoff; $i--) -			{ -				$ymatches[$this->yv[$i]][] = $i; -			} -		} - -		$this->lcs = 0; -		$this->seq[0]= $yoff - 1; -		$this->in_seq = array(); -		$ymids[0] = array(); - -		$numer = $xlim - $xoff + $nchunks - 1; -		$x = $xoff; - -		for ($chunk = 0; $chunk < $nchunks; $chunk++) -		{ -			if ($chunk > 0) -			{ -				for ($i = 0; $i <= $this->lcs; $i++) -				{ -					$ymids[$i][$chunk-1] = $this->seq[$i]; -				} -			} - -			$x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); - -			for ( ; $x < $x1; $x++) -			{ -				$_index = $flip ? $this->yv[$x] : $this->xv[$x]; -				$matches = (isset($ymatches[$_index])) ? $ymatches[$_index] : array(); - -				if (!$matches) -				{ -					continue; -				} -				reset($matches); - -				while (list ($junk, $y) = each($matches)) -				{ -					if (!isset($this->in_seq[$y]) || !$this->in_seq[$y]) -					{ -						$k = $this->_lcs_pos($y); -						//if (!$k) die('assertion "!$k" failed'); -						$ymids[$k] = $ymids[$k-1]; -						break; -					} -				} - -				while (list ($junk, $y) = each($matches)) -				{ -					if ($y > $this->seq[$k-1]) -					{ -						//if ($y >= $this->seq[$k]) die('assertion failed'); -						// Optimization: this is a common case: -						//  next match is just replacing previous match. -						$this->in_seq[$this->seq[$k]] = false; -						$this->seq[$k] = $y; -						$this->in_seq[$y] = 1; -					} -					else if (!isset($this->in_seq[$y]) || !$this->in_seq[$y]) -					{ -						$k = $this->_lcs_pos($y); -						//if (!$k) die('assertion "!$k" failed'); -						$ymids[$k] = $ymids[$k-1]; -					} -				} -			} -		} - -		$seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); -		$ymid = $ymids[$this->lcs]; - -		for ($n = 0; $n < $nchunks - 1; $n++) -		{ -			$x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); -			$y1 = $ymid[$n] + 1; -			$seps[] = $flip ? array($y1, $x1) : array($x1, $y1); -		} -		$seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); - -		return array($this->lcs, $seps); -	} - -	function _lcs_pos ($ypos) -	{ -		$end = $this->lcs; -		if ($end == 0 || $ypos > $this->seq[$end]) -		{ -			$this->seq[++$this->lcs] = $ypos; -			$this->in_seq[$ypos] = 1; -			return $this->lcs; -		} - -		$beg = 1; -		while ($beg < $end) -		{ -			$mid = (int)(($beg + $end) / 2); - -			if ($ypos > $this->seq[$mid]) -			{ -				$beg = $mid + 1; -			} -			else -			{ -				$end = $mid; -			} -		} - -		//if ($ypos == $this->seq[$end]) die("assertion failure"); - -		$this->in_seq[$this->seq[$end]] = false; -		$this->seq[$end] = $ypos; -		$this->in_seq[$ypos] = 1; -		return $end; -	} - -	/* Find LCS of two sequences. -	* -	* The results are recorded in the vectors $this->{x,y}changed[], by -	* storing a 1 in the element for each line that is an insertion -	* or deletion (ie. is not in the LCS). -	* -	* The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. -	* -	* Note that XLIM, YLIM are exclusive bounds. -	* All line numbers are origin-0 and discarded lines are not counted. -	*/ -	function _compareseq ($xoff, $xlim, $yoff, $ylim) -	{ -		// Slide down the bottom initial diagonal. -		while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) -		{ -			++$xoff; -			++$yoff; -		} - -		// Slide up the top initial diagonal. -		while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) -		{ -			--$xlim; -			--$ylim; -		} - -		if ($xoff == $xlim || $yoff == $ylim) -		{ -			$lcs = 0; -		} -		else -		{ -			// This is ad hoc but seems to work well. -			//$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); -			//$nchunks = max(2,min(8,(int)$nchunks)); -			$nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; -			list ($lcs, $seps) = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); -		} - -		if ($lcs == 0) -		{ -			// X and Y sequences have no common subsequence: -			// mark all changed. -			while ($yoff < $ylim) -			{ -				$this->ychanged[$this->yind[$yoff++]] = 1; -			} - -			while ($xoff < $xlim) -			{ -				$this->xchanged[$this->xind[$xoff++]] = 1; -			} -		} -		else -		{ -			// Use the partitions to split this problem into subproblems. -			reset($seps); - -			$pt1 = $seps[0]; - -			while ($pt2 = next($seps)) -			{ -				$this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); -				$pt1 = $pt2; -			} -		} -	} - -	/* Adjust inserts/deletes of identical lines to join changes -	* as much as possible. -	* -	* We do something when a run of changed lines include a -	* line at one end and have an excluded, identical line at the other. -	* We are free to choose which identical line is included. -	* `compareseq' usually chooses the one at the beginning, -	* but usually it is cleaner to consider the following identical line -	* to be the "change". -	* -	* This is extracted verbatim from analyze.c (GNU diffutils-2.7). -	*/ -	function _shift_boundaries ($lines, &$changed, $other_changed) -	{ -		$i = 0; -		$j = 0; -		$len = sizeof($lines); - -		while (1) -		{ -			/* -			* Scan forwards to find beginning of another run of changes. -			* Also keep track of the corresponding point in the other file. -			*/ - -			while ($i < $len && $changed[$i] == 0) -			{ -				while ($other_changed[$j++]) -				{ -					continue; -				} -				$i++; -			} - -			if ($i == $len) -			{ -				break; -			} - -			$start = $i; - -			// Find the end of this run of changes. -			while (isset($changed[++$i])) -			{ -				continue; -			} - -			while (isset($other_changed[$j]) && $other_changed[$j]) -			{ -				$j++; -			} - -			do -			{ -				/* -				* Record the length of this run of changes, so that -				* we can later determine whether the run has grown. -				*/ -				$runlength = $i - $start; - -				/* -				* Move the changed region back, so long as the -				* previous unchanged line matches the last changed one. -				* This merges with previous changed regions. -				*/ -				while ($start && $lines[$start - 1] == $lines[$i - 1]) -				{ -					$changed[--$start] = 1; -					$changed[--$i] = false; - -					while ($changed[$start - 1]) -					{ -						$start--; -					} - -					while ($other_changed[--$j]) -					{ -						continue; -					} -				} - -				/* -				* Set CORRESPONDING to the end of the changed run, at the last -				* point where it corresponds to a changed run in the other file. -				* CORRESPONDING == LEN means no such point has been found. -				*/ -				$corresponding = empty($other_changed[$j - 1]) ? $len : $i; - -				/* -				* Move the changed region forward, so long as the -				* first changed line matches the following unchanged one. -				* This merges with following changed regions. -				* Do this second, so that if there are no merges, -				* the changed region is moved forward as far as possible. -				*/ -				while ($i != $len && $lines[$start] == $lines[$i]) -				{ -					$changed[$start++] = false; -					$changed[$i++] = 1; - -					while ($changed[$i]) -					{ -						$i++; -					} - -					while ($other_changed[++$j]) -					{ -						$corresponding = $i; -					} -				} -			} while ($runlength != $i - $start); - -			/* -			* If possible, move the fully-merged run of changes -			* back to a corresponding run in the other file. -			*/ -			while ($corresponding < $i) -			{ -				$changed[--$start] = 1; -				$changed[--$i] = 0; - -				while ($other_changed[--$j]) -				{ -					continue; -				} -			} -		} -	} -} - -/** -* Class representing a diff between two files. -*/ -class Diff -{ -	var $edits; - -	/** -	* Compute diff between files (or deserialize serialized WikiDiff.) -	*/ -	function Diff($from_lines = false, $to_lines = false) -	{ -		if ($from_lines && $to_lines) -		{ -			$compute = new _WikiDiffEngine($from_lines, $to_lines); -			$this->edits = $compute->edits; -		} -		else if ($from_lines) -		{ -			// $from_lines is not really from_lines, but rather -			// a serialized Diff. -			$this->edits = unserialize($from_lines); -		} -		else -		{ -			$this->edits = array(); -		} -	} - -	/** -	* Compute reversed Diff. -	* -	* SYNOPSIS: -	* -	*  $diff = new Diff($lines1, $lines2); -	*  $rev = $diff->reverse($lines1); -	* -	*  // reconstruct $lines1 from $lines2: -	*  $out = $rev->apply($lines2); -	*/ -	function reverse ($from_lines) -	{ -		$x = 0; -		$rev = new Diff; - -		for (reset($this->edits); $edit = current($this->edits); next($this->edits)) -		{ -			if (is_array($edit)) -			{ // Was an add, turn it into a delete. -				$nadd = sizeof($edit); -				if ($nadd == 0) -				{ -					die("assertion error"); -				} -				$edit = -$nadd; -			} -			else if ($edit > 0) -			{ -				// Was a copy --- just pass it through.	      } -				$x += $edit; -			} -			else if ($edit < 0) -			{ // Was a delete, turn it into an add. -				$ndelete = -$edit; -				$edit = array(); - -				while ($ndelete-- > 0) -				{ -					$edit[] = $from_lines[$x++]; -				} -			} -			else -			{ -				die("assertion error"); -			} - -			$rev->edits[] = $edit; -		} - -		return $rev; -	} - -	/** -	* Compose (concatenate) Diffs. -	* -	* SYNOPSIS: -	* -	*  $diff1 = new Diff($lines1, $lines2); -	*  $diff2 = new Diff($lines2, $lines3); -	*  $comp = $diff1->compose($diff2); -	* -	*  // reconstruct $lines3 from $lines1: -	*  $out = $comp->apply($lines1); -	*/ -	function compose ($that) -	{ -		reset($this->edits); -		reset($that->edits); - -		$comp = new Diff; -		$left = current($this->edits); -		$right = current($that->edits); - -		while ($left || $right) -		{ -			if (!is_array($left) && $left < 0) -			{ // Left op is a delete. -				$newop = $left; -				$left = next($this->edits); -			} -			else if (is_array($right)) -			{ // Right op is an add. -				$newop = $right; -				$right = next($that->edits); -			} -			else if (!$left || !$right) -			{ -				die ("assertion error"); -			} -			else if (!is_array($left) && $left > 0) -			{ // Left op is a copy. -				if ($left <= abs($right)) -				{ -					$newop = $right > 0 ? $left : -$left; -					$right -= $newop; - -					if ($right == 0) -					{ -						$right = next($that->edits); -					} -					$left = next($this->edits); -				} -				else -				{ -					$newop = $right; -					$left -= abs($right); -					$right = next($that->edits); -				} -			} -			else -			{ // Left op is an add. -				if (!is_array($left)) -				{ -					die('assertion error'); -				} -				$nleft = sizeof($left); - -				if ($nleft <= abs($right)) -				{ -					if ($right > 0) -					{ // Right op is copy -						$newop = $left; -						$right -= $nleft; -					} -					else // Right op is delete -					{ -						$newop = false; -						$right += $nleft; -					} - -					if ($right == 0) -					{ -						$right = next($that->edits); -					} -					$left = next($this->edits); -				} -				else -				{ -					unset($newop); - -					if ($right > 0) -					{ -						for ($i = 0; $i < $right; $i++) -						{ -							$newop[] = $left[$i]; -						} -					} - -					$tmp = array(); -					for ($i = abs($right); $i < $nleft; $i++) -					{ -						$tmp[] = $left[$i]; -					} -					$left = $tmp; -					$right = next($that->edits); -				} -			} - -			if (!$op) -			{ -				$op = $newop; -				continue; -			} - -			if (!$newop) -			{ -				continue; -			} - -			if (is_array($op) && is_array($newop)) -			{ -				// Both $op and $newop are adds. -				for ($i = 0; $i < sizeof($newop); $i++) -				{ -					$op[] = $newop[$i]; -				} -			} -			else if (($op > 0 && $newop > 0) || ($op < 0 && $newop < 0)) -			{ // $op and $newop are both either deletes or copies. -				$op += $newop; -			} -			else -			{ -				$comp->edits[] = $op; -				$op = $newop; -			} -		} - -		if ($op) -		{ -			$comp->edits[] = $op; -		} - -		return $comp; -	} - -	/* Debugging only: -	function _dump () -	{ -		echo "<ol>"; -		for (reset($this->edits); -		{ -			$edit = current($this->edits); -		} -		next($this->edits)) -	  { -	    echo "<li>"; -	    if ($edit > 0) -		echo "Copy $edit"; -	    else if ($edit < 0) -		echo "Delete " . -$edit; -	    else if (is_array($edit)) -	      { -		echo "Add " . sizeof($edit) . "<ul>"; -		for ($i = 0; $i < sizeof($edit); $i++) -		    echo "<li>" . htmlspecialchars($edit[$i]); -		echo "</ul>"; -	      } -	    else -		die("assertion error"); -	  } -	echo "</ol>"; -      } -  */ - -	/** -	* Apply a Diff to a set of lines. -	* -	* SYNOPSIS: -	* -	*  $diff = new Diff($lines1, $lines2); -	* -	*  // reconstruct $lines2 from $lines1: -	*  $out = $diff->apply($lines1); -	*/ -	function apply ($from_lines) -	{ -		$x = 0; -		$xlim = sizeof($from_lines); - -		for (reset($this->edits); $edit = current($this->edits); next($this->edits)) -		{ -			if (is_array($edit)) -			{ -				reset($edit); -				while (list ($junk, $line) = each($edit)) -				{ -					$output[] = $line; -				} -			} -			else if ($edit > 0) -			{ -				while ($edit--) -				{ -					$output[] = $from_lines[$x++]; -				} -			} -			else -			{ -				$x += -$edit; -			} -		} - -		if ($x != $xlim) -		{ -			die(sprintf("Diff::apply: line count mismatch: %s != %s", $x, $xlim)); -		} - -		return $output; -	} - -	/** -	* Serialize a Diff. -	* -	* SYNOPSIS: -	* -	*  $diff = new Diff($lines1, $lines2); -	*  $string = $diff->serialize; -	* -	*  // recover Diff from serialized version: -	*  $diff2 = new Diff($string); -	*/ -	function serialize () -	{ -		return serialize($this->edits); -	} - -	/** -	* Return true if two files were equal. -	*/ -	function isEmpty() -	{ -		if (sizeof($this->edits) > 1) -		{ -			return false; -		} - -		if (sizeof($this->edits) == 0) -		{ -			return true; -		} - -		// Test for: only edit is a copy. -		return !is_array($this->edits[0]) && $this->edits[0] > 0; -	} - -	/** -	* Compute the length of the Longest Common Subsequence (LCS). -	* -	* This is mostly for diagnostic purposed. -	*/ -	function lcs() -	{ -		$lcs = 0; -		for (reset($this->edits); $edit = current($this->edits); next($this->edits)) -		{ -			if (!is_array($edit) && $edit > 0) -			{ -				$lcs += $edit; -			} -		} - -		return $lcs; -	} - -	/** -	* Check a Diff for validity. -	* -	* This is here only for debugging purposes. -	*/ -	function _check ($from_lines, $to_lines) -	{ -		$test = $this->apply($from_lines); -		if (serialize($test) != serialize($to_lines)) -		{ -			die("Diff::_check: failed"); -		} - -		reset($this->edits); -		$prev = current($this->edits); -		$prevtype = is_array($prev) ? 'a' : ($prev > 0 ? 'c' : 'd'); - -		while ($edit = next($this->edits)) -		{ -			$type = is_array($edit) ? 'a' : ($edit > 0 ? 'c' : 'd'); -			if ($prevtype == $type) -			{ -				die("Diff::_check: edit sequence is non-optimal"); -			} -			$prevtype = $type; -		} -		$lcs = $this->lcs(); -		echo "<strong>Diff Okay: LCS = $lcs</strong>\n"; -	} -} - -/** -* A class to format a Diff as HTML. -* -* Usage: -* -*	$diff = new Diff($lines1, $lines2); // compute diff. -* -*	$fmt = new DiffFormatter; -*	echo $fmt->format($diff, $lines1); // Output HTMLified standard diff. -* -*	or to output reverse diff (diff's that would take $lines2 to $lines1): -* -*	$fmt = new DiffFormatter(true); -*	echo $fmt->format($diff, $lines1); -*/ -class DiffFormatter -{ -	var $context_lines; -	var $do_reverse_diff; -	var $context_prefix, $deletes_prefix, $adds_prefix; - -	function DiffFormatter ($reverse = false) -	{ -		$this->do_reverse_diff = $reverse; -		$this->context_lines = 0; -		$this->context_prefix = '  '; -		$this->deletes_prefix = '< '; -		$this->adds_prefix = '> '; -	} - -	function format ($diff, $from_lines) -	{ -		$html = '<table width="100%" bgcolor="black" cellspacing=2 cellpadding=2 border=0>' . "\n"; -		$html .= $this->_format($diff->edits, $from_lines); -		$html .= "</table>\n"; - -		return $html; -	} - -	function _format ($edits, $from_lines) -	{ -		$html = ''; -		$x = 0; $y = 0; -		$xlim = sizeof($from_lines); - -		reset($edits); -		while ($edit = current($edits)) -		{ -			if (!is_array($edit) && $edit >= 0) -			{ // Edit op is a copy. -				$ncopy = $edit; -			} -			else -			{ -				$ncopy = 0; - -				if (empty($hunk)) -				{ -					// Start of an output hunk. -					$xoff = max(0, $x - $this->context_lines); -					$yoff = $xoff + $y - $x; - -					if ($xoff < $x) -					{ -						// Get leading context. -						$context = array(); - -						for ($i = $xoff; $i < $x; $i++) -						{ -							$context[] = $from_lines[$i]; -						} -						$hunk['c'] = $context; -					} -				} - -				if (is_array($edit)) -				{ -					// Edit op is an add. -					$y += sizeof($edit); -					$hunk[$this->do_reverse_diff ? 'd' : 'a'] = $edit; -				} -				else -				{ -					// Edit op is a delete -					$deletes = array(); - -					while ($edit++ < 0) -					{ -						$deletes[] = $from_lines[$x++]; -					} - -					$hunk[$this->do_reverse_diff ? 'a' : 'd'] = $deletes; -				} -			} - -			$next = next($edits); - -			if (!empty($hunk)) -			{ -				// Originally $ncopy > 2 * $this->context_lines, but we need to split as early as we can for creating MOD Text Templates. ;) -				if (!$next || $ncopy > $this->context_lines) -				{ -					// End of an output hunk. -					$hunks[] = $hunk; -					unset($hunk); - -					$xend = min($x + $this->context_lines, $xlim); - -					if ($x < $xend) -					{ -						// Get trailing context. -						$context = array(); -						for ($i = $x; $i < $xend; $i++) -						{ -							$context[] = $from_lines[$i]; -						} -						$hunks[] = array('c' => $context); -					} - -					$xlen = $xend - $xoff; -					$ylen = $xend + $y - $x - $yoff; -					$xbeg = $xlen ? $xoff + 1 : $xoff; -					$ybeg = $ylen ? $yoff + 1 : $yoff; - -					if ($this->do_reverse_diff) -					{ -						list ($xbeg, $xlen, $ybeg, $ylen) = array($ybeg, $ylen, $xbeg, $xlen); -					} - -					$html .= $this->_emit_diff($xbeg, $xlen, $ybeg, $ylen, $hunks); -					unset($hunks); -				} -				else if ($ncopy) -				{ -					$hunks[] = $hunk; - -					// Copy context. -					$context = array(); -					for ($i = $x; $i < $x + $ncopy; $i++) -					{ -						$context[] = $from_lines[$i]; -					} -					$hunk = array('c' => $context); -				} -			} - -			$x += $ncopy; -			$y += $ncopy; -		} - -		return $html; -	} - -	function _emit_lines($lines,  $prefix, $color) -	{ -		$html = ''; -		reset($lines); -		while (list ($junk, $line) = each($lines)) -		{ -			$html .= "<tr bgcolor=\"$color\"><td><tt>$prefix</tt>"; -			$html .= "<tt>" . htmlspecialchars($line) . "</tt></td></tr>\n"; -		} -		return $html; -	} - -	function _emit_diff ($xbeg,$xlen,$ybeg,$ylen,$hunks) -	{ -		// Save hunk... -		$this->diff_hunks[] = $hunks; - -		$html = '<tr><td><table width="100%" bgcolor="white" cellspacing="0" border="0" cellpadding="4"> -				<tr bgcolor="#cccccc"><td><tt>' . -				$this->_diff_header($xbeg, $xlen, $ybeg, $ylen) . ' -				</tt></td></tr>\n<tr><td> -				<table width="100%" cellspacing="0" border="0" cellpadding="2"> -		'; - -		$prefix = array('c' => $this->context_prefix, 'a' => $this->adds_prefix, 'd' => $this->deletes_prefix); -		$color = array('c' => '#ffffff', 'a' => '#ffcccc', 'd' => '#ccffcc'); - -		for (reset($hunks); $hunk = current($hunks); next($hunks)) -		{ -			if (!empty($hunk['c'])) -			{ -				$html .= $this->_emit_lines($hunk['c'], $this->context_prefix, '#ffffff'); -			} - -			if (!empty($hunk['d'])) -			{ -				$html .= $this->_emit_lines($hunk['d'], $this->deletes_prefix, '#ccffcc'); -			} - -			if (!empty($hunk['a'])) -			{ -				$html .= $this->_emit_lines($hunk['a'], $this->adds_prefix, '#ffcccc'); -			} -		} - -		$html .= "</table></td></tr></table></td></tr>\n"; -		return $html; -	} - -	function _diff_header ($xbeg,$xlen,$ybeg,$ylen) -	{ -		$what = $xlen ? ($ylen ? 'c' : 'd') : 'a'; -		$xlen = $xlen > 1 ? "," . ($xbeg + $xlen - 1) : ''; -		$ylen = $ylen > 1 ? "," . ($ybeg + $ylen - 1) : ''; - -		return "$xbeg$xlen$what$ybeg$ylen"; -	} -} - -/** -* A class to format a Diff as a pretty HTML unified diff. -* -* Usage: -* -*	$diff = new Diff($lines1, $lines2); // compute diff. -* -*	$fmt = new UnifiedDiffFormatter; -*	echo $fmt->format($diff, $lines1); // Output HTMLified unified diff. -*/ -class UnifiedDiffFormatter extends DiffFormatter -{ -	function UnifiedDiffFormatter ($reverse = false, $context_lines = 3) -	{ -		$this->do_reverse_diff = $reverse; -		$this->context_lines = $context_lines; -		$this->context_prefix = ' '; -		$this->deletes_prefix = '-'; -		$this->adds_prefix = '+'; -	} - -	function _diff_header ($xbeg,$xlen,$ybeg,$ylen) -	{ -		$xlen = $xlen == 1 ? '' : ",$xlen"; -		$ylen = $ylen == 1 ? '' : ",$ylen"; - -		return "@@ -$xbeg$xlen +$ybeg$ylen @@"; -	} -} - -/** -* A class to format a Diff as MOD Template instuctions. -* -* Usage: -* -*	$diff = new Diff($lines1, $lines2); // compute diff. -* -*	$fmt = new BBCodeDiffFormatter; -*	echo $fmt->format($diff, $lines1); // Output MOD Actions. -*/ -class BBCodeDiffFormatter extends DiffFormatter -{ -	function BBCodeDiffFormatter ($reverse = false, $context_lines = 3, $debug = false) -	{ -		$this->do_reverse_diff = $reverse; -		$this->context_lines = $context_lines; -		$this->context_prefix = ' '; -		$this->deletes_prefix = '-'; -		$this->adds_prefix = '+'; -		$this->debug = $debug; -	} - -	function format ($diff, $from_lines) -	{ -		$html = $this->_format($diff->edits, $from_lines); - -		return $html; -	} - -	function skip_lines(&$order_array, &$ordering) -	{ -		if (sizeof($order_array['find_c'])) -		{ -			$text = implode('', $order_array['find_c']); -			if ($text === "\n" || $text === "\t" || $text === '') -			{ -				if (isset($order_array['first_find_c'][0]) && -					is_array($order_array['first_find_c'][0]) && -					trim(implode('', $order_array['first_find_c'][0])) != '' && -					isset($order_array['replace'])) -				{ -					$order_array['add'] = $order_array['replace']; -					unset($order_array['replace']); -					// this is actually an after add -				} -				else -				{ -					return true; -				} -			} -		} - -		if (isset($order_array['add']) && sizeof($order_array['add'])) -		{ -			$text = implode('', $order_array['add']); -			if ($text === "\n" || $text === "\t" || $text === '') -			{ -				return true; -			} -		} - -		if (isset($order_array['replace']) && sizeof($order_array['replace'])) -		{ -			$text = implode('', $order_array['replace']); -			if ($text === "\n" || $text === "\t" || $text === '') -			{ -				return true; -			} -		} -	} - -	function _emit_lines_bb($ybeg, &$ordering) -	{ -		$html = ''; - -		// Now adjust for bbcode display... -		foreach ($ordering as $order_array) -		{ -			// Skip useless empty lines... -			if ($this->skip_lines($order_array, $ordering)) -			{ -				continue; -			} - -			// Only include double-finds if the last find has very few code location indications... -			if (isset($order_array['first_find_c']) && sizeof($order_array['first_find_c'])) -			{ -				$text = implode('', $order_array['find_c']); -				if ($text === "\n" || $text === "\t" || $text === '') -				{ -					// no real find, use first_find_c if possible! -					//var_dump($order_array); -					if (is_array($order_array['first_find_c'][0])) -					{ -						$order_array['find_c'] = $order_array['first_find_c'][0]; -					} -					else -					{ -						if (isset($order_array['replace']) || isset($order_array['add']) || isset($order_array['delete'])) -						{ -							echo "skipped an edit!\n"; -							var_dump($order_array); -						} -						continue; -					} -				} -				else -				{ -					if (strlen(implode('', $order_array['find_c'])) < 50 && is_array($order_array['first_find_c'][0])) -					{ -						$html .= "#\n#-----[ FIND ]---------------------------------------------\n# Around Line {$ybeg}\n"; -						$html .= implode("", $order_array['first_find_c'][0]); -						$html .= "\n"; -						$ybeg += sizeof($order_array['first_find_c'][0]); -					} -				} -			} - -			// still here but nothing to do? what the heck? -			if (!isset($order_array['replace']) && !isset($order_array['add']) && !isset($order_array['delete'])) -			{ -				echo "skipped an edit!\n"; -				var_dump($order_array); -				continue; -			} - -			if (sizeof($order_array['find_c'])) -			{ -				$html .= "#\n#-----[ FIND ]---------------------------------------------\n# Around Line {$ybeg}\n"; -				$html .= implode("", $order_array['find_c']); -				$html .= "\n"; -			} - -			if (isset($order_array['replace'])) -			{ -				$html .= "#\n#-----[ REPLACE WITH ]---------------------------------------------\n#\n"; -				$html .= implode("", $order_array['replace']); -				$html .= "\n"; -			} - -			if (isset($order_array['add'])) -			{ -				$html .= "#\n#-----[ AFTER, ADD ]---------------------------------------------\n#\n"; -				$html .= implode("", $order_array['add']); -				$html .= "\n"; -			} - -			// There is no DELETE. :o -			// Let's try to adjust it then... -			if (isset($order_array['delete'])) -			{ -				$text = implode('', $order_array['delete']); -				if ($text === "\n" || $text === "\t" || $text === '') -				{ -					continue; -				} - -				$ybeg += sizeof($order_array['find_c']); - -				$html .= "#\n#-----[ FIND ]---------------------------------------------\n# Around Line {$ybeg}\n"; -				$html .= implode("", $order_array['delete']); -				$html .= "\n"; - -				$html .= "#\n#-----[ REPLACE WITH ]---------------------------------------------\n# Just remove/delete the lines (replacing with an empty line)\n"; -				$html .= "\n"; -				$html .= "\n"; -			} -		} - -		return $html; -	} - -	function format_open($filename) -	{ -		$html = ''; -		$html .= "#\n#-----[ OPEN ]--------------------------------------------- \n#\n{$filename}\n\n"; - -		return $html; -	} - -	function format_close($filename) -	{ -		return ''; -	} - -	function _emit_diff ($xbeg, $xlen, $ybeg, $ylen, $hunks) -	{ - -		// Go through the hunks to determine which method we are using (AFTER, ADD; REPLACE WITH or DELETE) - -		// Remove the header... -		if (sizeof($hunks) <= 2 && !isset($hunks[1]['a']) && !isset($hunks[1]['d'])) -		{ -			$reorder = false; -			$orig_hunks = $hunks; - -			foreach ($hunks as $key => $hunk) -			{ -				if (isset($hunk['a']) && isset($hunk['d'])) -				{ -	/**/				if (sizeof($hunk['a']) == 1 && sizeof($hunk['d']) == 1) -					{ -						if (preg_match('/\* @version \$Id:.+\$$/', $hunk['a'][0]) && preg_match('/\* @version \$Id:.+\$$/', $hunk['d'][0])) -						{ -							// Only remove this sole hunk... -							unset($hunks[$key]); -							$reorder = true; -							continue; -						} -					}/**/ - -					// Compare the add and replace one... -					$string_1 = rtrim(trim(implode('', $hunk['a']))); -					$string_2 = rtrim(trim(implode('', $hunk['d']))); - -					if (strcmp($string_1, $string_2) === 0) -					{ -						// Only remove this sole hunk... -						unset($hunks[$key]); -						$reorder = true; -						continue; -					} -				} -			} - -			if ($reorder) -			{ -				// Now check if we have no more 'a' and 'd's -				$hunks = array_merge($hunks, array()); -			} -		} -		else -		{ -			$reorder = false; -			$orig_hunks = $hunks; - -			foreach ($hunks as $key => $hunk) -			{ -				if (isset($hunk['a']) && isset($hunk['d'])) -				{ -	/**/				if (sizeof($hunk['a']) == 1 && sizeof($hunk['d']) == 1) -					{ -						if (preg_match('/\* @version \$Id:.+\$$/', $hunk['a'][0]) && preg_match('/\* @version \$Id:.+\$$/', $hunk['d'][0])) -						{ -							// Only remove this sole hunk... -							unset($hunks[$key]); -							$reorder = true; -							continue; -						} -					}/**/ - -					// Compare the add and replace one... -					$string_1 = rtrim(trim(implode('', $hunk['a']))); -					$string_2 = rtrim(trim(implode('', $hunk['d']))); - -					if (strcmp($string_1, $string_2) === 0) -					{ -						// Only remove this sole hunk... -						unset($hunks[$key]); -						$reorder = true; -						continue; -					} -				} -			} - -			if ($reorder) -			{ -				$hunks = array_merge($hunks, array()); - -				if (sizeof($hunks) == 1 && sizeof($hunks[0]) == 1 && isset($hunks[0]['c'])) -				{ -					return; -				} -				else -				{ -					$hunks = $orig_hunks; -				} -			} -		} - -		if (sizeof($hunks) == 1 && sizeof($hunks[0]) == 1 && isset($hunks[0]['c'])) -		{ -			return; -		} - -		$replace = false; -		foreach ($hunks as $key => $hunk) -		{ -			if (isset($hunk['d']) && isset($hunk['a'])) -			{ -				$replace = true; -				break; -			} -		} - -		$ordering = array(); -		$cur_pos = 0; - -		// Replace-block -		if ($replace) -		{ -			foreach ($hunks as $hunk) -			{ -				if (!isset($hunk['a']) && !isset($hunk['d'])) -				{ -					continue; -				} - -				if (!empty($hunk['c'])) -				{ -					if (!isset($ordering[$cur_pos]['find_c'])) -					{ -						$ordering[$cur_pos]['find_c'] = $hunk['c']; -					} -					else -					{ -						$ordering[$cur_pos]['end_c'] = $hunk['c']; -					} -				} - -				// Make sure we begin fresh... -				if (!isset($ordering[$cur_pos]['replace'])) -				{ -					$ordering[$cur_pos]['first_find_c'][] = $ordering[$cur_pos]['find_c']; -					$ordering[$cur_pos]['find_c'] = array(); -					$ordering[$cur_pos]['replace'] = array(); -				} - -				// Add the middle part if one exist... -				if (isset($ordering[$cur_pos]['end_c'])) -				{ -					$ordering[$cur_pos]['find_c'] = array_merge($ordering[$cur_pos]['find_c'], $ordering[$cur_pos]['end_c']); -					$ordering[$cur_pos]['replace'] = array_merge($ordering[$cur_pos]['replace'], $ordering[$cur_pos]['end_c']); -					unset($ordering[$cur_pos]['end_c']); -				} - -				if (isset($hunk['d'])) -				{ -					$ordering[$cur_pos]['find_c'] = array_merge($ordering[$cur_pos]['find_c'], $hunk['d']); -				} - -				if (isset($hunk['a'])) -				{ -					$ordering[$cur_pos]['replace'] = array_merge($ordering[$cur_pos]['replace'], $hunk['a']); -				} -			} -		} -		else -		{ -			foreach ($hunks as $hunk) -			{ -				if (!empty($hunk['c'])) -				{ -					if (!isset($ordering[$cur_pos]['find_c'])) -					{ -						$ordering[$cur_pos]['find_c'] = $hunk['c']; -					} -					else -					{ -						$ordering[$cur_pos]['end_c'] = $hunk['c']; -					} -				} - -				if (!empty($hunk['a'])) -				{ -					if (isset($ordering[$cur_pos]['delete'])) -					{ -						// If ordering is set with an delete entry, we will actually begin a new ordering array (to seperate delete from add) -						$cur_pos++; -						$ordering[$cur_pos]['find_c'] = $ordering[($cur_pos - 1)]['end_c']; -						$ordering[$cur_pos]['add'] = $hunk['a']; -					} -					else -					{ -						if (isset($ordering[$cur_pos]['add'])) -						{ -							// Now, we really need to be quite careful here... -							if (isset($ordering[$cur_pos]['end_c']) && isset($hunk['c']) && isset($hunk['a']) && sizeof($hunk) == 2) -							{ -								// There is a new find/add entry we did not catch... let's try to add a new entry then... but first check the hunk[a] contents... -								$text = trim(implode("\n", $hunk['c'])); -								if ($text == "\n" || !$text) -								{ -									$ordering[$cur_pos]['add'] = array_merge($ordering[$cur_pos]['add'], array("\n"), $hunk['a']); -								} -								else if (sizeof($hunk['c']) > 2 || strlen(implode('', $hunk['c'])) > 20) -								{ -									$cur_pos++; -									$ordering[$cur_pos]['find_c'] = $ordering[($cur_pos - 1)]['end_c']; -									$ordering[$cur_pos]['add'] = $hunk['a']; -								} -								else -								{ -									$cur_pos++; -									$ordering[$cur_pos]['find_c'] = $ordering[($cur_pos - 1)]['end_c']; -									$ordering[$cur_pos]['add'] = $hunk['a']; -/*									echo 'FIND STATEMENT TOO TINY'; -									echo ";".rawurlencode($text).";"; -									var_dump($hunk); -									exit;*/ -								} -							} -							else -							{ -								echo 'UNCATCHED ENTRY'; -								var_dump($hunks); -								exit; -							} -						} -						else -						{ -							$ordering[$cur_pos]['add'] = $hunk['a']; -						} -					} -				} -				else if (!empty($hunk['d'])) -				{ -					if (isset($ordering[$cur_pos]['add'])) -					{ -						// If ordering is set with an add entry, we will actually begin a new ordering array (to seperate delete from add) -						$cur_pos++; -						$ordering[$cur_pos]['find_c'] = $ordering[($cur_pos - 1)]['end_c']; -						$ordering[$cur_pos]['delete'] = $hunk['d']; -					} -					else -					{ -						$ordering[$cur_pos]['delete'] = $hunk['d']; -					} -				} -			} -		} - -		$html = ''; - -		return $this->_emit_lines_bb($ybeg, $ordering); -	} - -	function _diff_header($xbeg, $xlen, $ybeg, $ylen) -	{ -	} -} - - -/** -* A class to format a Diff as MOD Template instuctions. -* -* Usage: -* -*	$diff = new Diff($lines1, $lines2); // compute diff. -* -*	$fmt = new BBCodeDiffFormatter; -*	echo $fmt->format($diff, $lines1); // Output MOD Actions. -*/ -class MODXDiffFormatter extends BBCodeDiffFormatter -{ -	function MODXDiffFormatter ($reverse = false, $context_lines = 3, $debug = false) -	{ -		$this->do_reverse_diff = $reverse; -		$this->context_lines = $context_lines; -		$this->context_prefix = ' '; -		$this->deletes_prefix = '-'; -		$this->adds_prefix = '+'; -		$this->debug = $debug; -	} - -	function _emit_lines_bb($ybeg, &$ordering) -	{ -		$html = ''; - -		// Now adjust for bbcode display... -		foreach ($ordering as $order_array) -		{ -			// Skip useless empty lines... -			if ($this->skip_lines($order_array, $ordering)) -			{ -				continue; -			} - -			// Only include double-finds if the last find has very few code location indications... -			if (sizeof($order_array['first_find_c'])) -			{ -				$text = implode('', $order_array['find_c']); -				if ($text === "\n" || $text === "\t" || $text === '') -				{ -					continue; -				} - -				if (strlen(implode('', $order_array['find_c'])) < 50) -				{ -					if (substr($html, -8) !== '</find>' . "\n") -					{ -						$html .= '	<edit>' . "\n"; -					} - -					$html .= '		<comment lang="en">Around Line ' . $ybeg . '</comment>' . "\n"; -					$html .= '		<find>' . htmlspecialchars(implode('', $order_array['first_find_c'][0])) . '</find>' . "\n"; -					$ybeg += sizeof($order_array['first_find_c'][0]); -				} -			} - -			if (sizeof($order_array['find_c'])) -			{ -				if (substr($html, -8) !== '</find>' . "\n") -				{ -					$html .= '	<edit>' . "\n"; -				} - -//				$html .= '	<edit>' . "\n"; -				$html .= '		<comment lang="en">Around Line ' . $ybeg . '</comment>' . "\n"; -				$html .= '		<find>' . htmlspecialchars(implode('', $order_array['find_c'])) . '</find>' . "\n"; -			} - -			if (isset($order_array['replace'])) -			{ -				$html .= '		<action type="replace-with">' . htmlspecialchars(implode('', $order_array['replace'])) . '</action>' . "\n"; -				$html .= '	</edit>' . "\n"; -			} - -			if (isset($order_array['add'])) -			{ -				$html .= '		<action type="after-add">' . htmlspecialchars(implode('', $order_array['add'])) . '</action>' . "\n"; -				$html .= '	</edit>' . "\n"; -			} - -			// There is no DELETE. :o -			// Let's try to adjust it then... -			if (isset($order_array['delete'])) -			{ -				$text = implode('', $order_array['delete']); -				if ($text === "\n" || $text === "\t" || $text === '') -				{ -					continue; -				} - -				$ybeg += sizeof($order_array['find_c']); - -				if (substr($html, -8) !== '</find>' . "\n") -				{ -					$html .= '	<edit>' . "\n"; -				} -				$html .= '		<comment lang="en">Around Line ' . $ybeg . ' / Just remove/delete the lines (replacing with an empty line)</comment>' . "\n"; -				$html .= '		<find>' . htmlspecialchars(implode('', $order_array['delete'])) . '</find>' . "\n"; -				$html .= '		<action type="replace-with"></action>' . "\n"; -				$html .= '	</edit>'; -			} -		} - -		return $html; -	} - -	function format_open($filename) -	{ -		return '<open src="' . $filename . '">' . "\n"; -	} - -	function format_close($filename) -	{ -		return '</open>' . "\n"; -	} - -	function _diff_header($xbeg, $xlen, $ybeg, $ylen) -	{ -	} -} diff --git a/build/package.php b/build/package.php index d05448dfb4..97bfc4ea5c 100755 --- a/build/package.php +++ b/build/package.php @@ -174,6 +174,27 @@ if (sizeof($package->old_packages))  			$package->run_command('cp ' . $source_filename . ' ' . $dest_filename);  		} +		/** +		* We try to keep the update packages as small as possible while creating them. +		* However, we sometimes need to include additional files that are not included +		* in the diff in order to be able to correctly include the relatively +		* referenced files from the same or subsequent directories. +		*/ +		$copy_relative_directories = array( +			'adm/style/admin.css'	=> array( +				'copied'	=> false, +				'copy'		=> array( +					'adm/images/*' => 'adm/images', +				), +			), +			'config/'	=> array( +				'copied'	=> false, +				'copy'		=> array( +					'config/*.yml' => 'config', +				), +			), +		); +  		// Then fill the 'new' directory  		foreach ($file_contents['all'] as $file)  		{ @@ -185,6 +206,8 @@ if (sizeof($package->old_packages))  				continue;  			} +			$filename = $file; +  			// Create Directories along the way?  			$file = explode('/', $file);  			// Remove filename portion @@ -205,6 +228,37 @@ if (sizeof($package->old_packages))  			}  			$package->run_command('cp ' . $source_filename . ' ' . $dest_filename); + +			foreach ($copy_relative_directories as $reference => $data) +			{ +				// Copy all relative referenced files if needed +				if (strpos($filename, $reference) === 0 && !$data['copied']) +				{ +					foreach ($data['copy'] as $source_dir_files => $destination_dir) +					{ +						// Create directories along the way? +						$directories = explode('/', $destination_dir); + +						chdir($dest_filename_dir . '/install/update/new'); +						foreach ($directories as $dir) +						{ +							$dir = trim($dir); +							if ($dir) +							{ +								if (!file_exists('./' . $dir)) +								{ +									$package->run_command('mkdir ' . $dir); +								} +								chdir('./' . $dir); +							} +						} +						$source_dir_files = $package->locations['old_versions'] . $package->get('simple_name') . '/' . $source_dir_files; +						$destination_dir = $dest_filename_dir . '/install/update/new/' . $destination_dir; +						$package->run_command('cp ' . $source_dir_files . ' ' . $destination_dir); +					} +					$copy_relative_directories[$reference]['copied'] = true; +				} +			}  		}  		// Build index.php file for holding the file structure diff --git a/git-tools/commit-msg-hook-range.sh b/git-tools/commit-msg-hook-range.sh new file mode 100755 index 0000000000..66628c1d17 --- /dev/null +++ b/git-tools/commit-msg-hook-range.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# +# @copyright (c) 2014 phpBB Group +# @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +# +# Calls the git commit-msg hook on all non-merge commits in a given commit range. +# +set -e + +if [ "$#" -ne 1 ]; +then +	echo "Expected one argument (commit range, e.g. eef1b586...1666476b)." +	exit +fi + +DIR=$(dirname "$0") +COMMIT_MSG_HOOK_PATH="$DIR/hooks/commit-msg" + +COMMIT_RANGE="$1" + +for COMMIT_HASH in $(git rev-list --no-merges "$COMMIT_RANGE") +do +	# The git commit-msg hook takes a path to a file containing a commit +	# message. So we have to extract the commit message into a file first, +	# which then also needs to be deleted after our work is done. +	COMMIT_MESSAGE_PATH="$DIR/commit_msg.$COMMIT_HASH" +	git log -n 1 --pretty=format:%B "$COMMIT_HASH" > "$COMMIT_MESSAGE_PATH" +	"$COMMIT_MSG_HOOK_PATH" "$COMMIT_MESSAGE_PATH" +	rm "$COMMIT_MESSAGE_PATH" +done diff --git a/phpBB/adm/style/acp_attachments.html b/phpBB/adm/style/acp_attachments.html index 9ce9fb5f13..9db0fc6521 100644 --- a/phpBB/adm/style/acp_attachments.html +++ b/phpBB/adm/style/acp_attachments.html @@ -331,7 +331,7 @@  	<fieldset class="tabulated">  	<legend>{L_TITLE}</legend> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_FILENAME}</th> @@ -344,7 +344,7 @@  	</thead>  	<tbody>  	<!-- BEGIN orphan --> -		<!-- IF orphan.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td><a href="{orphan.U_FILE}">{orphan.REAL_FILENAME}</a></td>  			<td>{orphan.FILETIME}</td>  			<td>{orphan.FILESIZE}</td> @@ -389,7 +389,7 @@  	<!-- ENDIF -->  	</div> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_FILENAME}</th> @@ -400,7 +400,7 @@  	</thead>  	<tbody>  	<!-- BEGIN attachments --> -		<!-- IF attachments.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td>  				<!-- IF attachments.S_IN_MESSAGE -->{L_EXTENSION_GROUP}{L_COLON} <strong><!-- IF attachments.EXT_GROUP_NAME -->{attachments.EXT_GROUP_NAME}<!-- ELSE -->{L_NO_EXT_GROUP}<!-- ENDIF --></strong><br />{attachments.L_DOWNLOAD_COUNT}<br />{L_IN} {L_PRIVATE_MESSAGE}  				<!-- ELSE --><a href="{attachments.U_FILE}" style="font-weight: bold;">{attachments.REAL_FILENAME}</a><br /><!-- IF attachments.COMMENT -->{attachments.COMMENT}<br /><!-- ENDIF -->{attachments.L_DOWNLOAD_COUNT}<br />{L_TOPIC}{L_COLON} <a href="{attachments.U_VIEW_TOPIC}">{attachments.TOPIC_TITLE}</a><!-- ENDIF --> diff --git a/phpBB/adm/style/acp_bbcodes.html b/phpBB/adm/style/acp_bbcodes.html index d4ed5cfd10..8c8b805975 100644 --- a/phpBB/adm/style/acp_bbcodes.html +++ b/phpBB/adm/style/acp_bbcodes.html @@ -92,7 +92,7 @@  	<fieldset class="tabulated">  	<legend>{L_ACP_BBCODES}</legend> -	<table class="table1" id="down"> +	<table class="table1 zebra-table" id="down">  	<thead>  	<tr>  		<th>{L_BBCODE_TAG}</th> @@ -101,7 +101,7 @@  	</thead>  	<tbody>  	<!-- BEGIN bbcodes --> -		<!-- IF bbcodes.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td style="text-align: center;">{bbcodes.BBCODE_TAG}</td>  			<td class="actions"><!-- EVENT acp_bbcodes_actions_prepend --> <a href="{bbcodes.U_EDIT}">{ICON_EDIT}</a> <a href="{bbcodes.U_DELETE}" data-ajax="row_delete">{ICON_DELETE}</a> <!-- EVENT acp_bbcodes_actions_append --></td>  		</tr> diff --git a/phpBB/adm/style/acp_bots.html b/phpBB/adm/style/acp_bots.html index eeda13303d..b0e61015b6 100644 --- a/phpBB/adm/style/acp_bots.html +++ b/phpBB/adm/style/acp_bots.html @@ -62,7 +62,7 @@  	<form id="acp_bots" method="post" action="{U_ACTION}"> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_BOT_NAME}</th> @@ -73,7 +73,7 @@  	</thead>  	<tbody>  	<!-- BEGIN bots --> -		<!-- IF bots.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td style="width: 50%;">{bots.BOT_NAME}</td>  			<td style="width: 15%; white-space: nowrap;" align="center"> {bots.LAST_VISIT} </td>  			<td style="text-align: center;"> <a href="{bots.U_ACTIVATE_DEACTIVATE}" data-ajax="activate_deactivate">{bots.L_ACTIVATE_DEACTIVATE}</a> </td> diff --git a/phpBB/adm/style/acp_groups.html b/phpBB/adm/style/acp_groups.html index 925d24f3a3..72f4f2b239 100644 --- a/phpBB/adm/style/acp_groups.html +++ b/phpBB/adm/style/acp_groups.html @@ -155,7 +155,7 @@  		<a href="{U_DEFAULT_ALL}">» {L_MAKE_DEFAULT_FOR_ALL}</a>  	</fieldset> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_USERNAME}</th> @@ -170,7 +170,7 @@  		<td class="row3" colspan="5"><strong>{L_GROUP_LEAD}</strong></td>  	</tr>  	<!-- BEGIN leader --> -		<!-- IF leader.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td><!-- IF leader.USERNAME_COLOUR --><a href="{leader.U_USER_EDIT}" style="color: #{leader.USERNAME_COLOUR};" class="username-coloured">{leader.USERNAME}</a><!-- ELSE --><a href="{leader.U_USER_EDIT}">{leader.USERNAME}</a><!-- ENDIF --></td>  			<td style="text-align: center;"><!-- IF leader.S_GROUP_DEFAULT -->{L_YES}<!-- ELSE -->{L_NO}<!-- ENDIF --></td>  			<td style="text-align: center;">{leader.JOINED}</td> @@ -179,7 +179,7 @@  		</tr>  	<!-- BEGINELSE -->  		<tr> -			<td class="row1" colspan="5" style="text-align: center;">{L_GROUPS_NO_MODS}</td> +			<td colspan="5" style="text-align: center;">{L_GROUPS_NO_MODS}</td>  		</tr>  	<!-- END leader -->  	<tr> @@ -191,7 +191,7 @@  			<td class="row3" colspan="5"><strong>{L_GROUP_PENDING}</strong></td>  		</tr>  		<!-- ELSE --> -			<!-- IF member.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td><!-- IF member.USERNAME_COLOUR --><a href="{member.U_USER_EDIT}" style="color: #{member.USERNAME_COLOUR};" class="username-coloured">{member.USERNAME}</a><!-- ELSE --><a href="{member.U_USER_EDIT}">{member.USERNAME}</a><!-- ENDIF --></td>  			<td style="text-align: center;"><!-- IF member.S_GROUP_DEFAULT -->{L_YES}<!-- ELSE -->{L_NO}<!-- ENDIF --></td>  			<td style="text-align: center;">{member.JOINED}</td> @@ -201,7 +201,7 @@  		<!-- ENDIF -->  	<!-- BEGINELSE -->  		<tr> -			<td class="row1" colspan="5" style="text-align: center;">{L_GROUPS_NO_MEMBERS}</td> +			<td colspan="5" style="text-align: center;">{L_GROUPS_NO_MEMBERS}</td>  		</tr>  	<!-- END member -->  	</tbody> diff --git a/phpBB/adm/style/acp_icons.html b/phpBB/adm/style/acp_icons.html index bfe4878ba5..9117052d87 100644 --- a/phpBB/adm/style/acp_icons.html +++ b/phpBB/adm/style/acp_icons.html @@ -74,7 +74,7 @@  	<fieldset class="tabulated">  	<legend>{L_TITLE}</legend> -	<table class="table1" id="smilies"> +	<table class="table1 zebra-table" id="smilies">  	<thead>  	<tr>  		<th colspan="{COLSPAN}">{L_CONFIG}</th> @@ -100,7 +100,7 @@  	</thead>  	<tbody>  	<!-- BEGIN items --> -		<!-- IF items.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  		<td style="text-align: center;"><img src="{items.IMG_SRC}" alt="" title="" /><input type="hidden" name="image[{items.IMG}]" value="1" /></td>  		<td style="vertical-align: top;">[{items.IMG}]</td> @@ -218,7 +218,7 @@  	<legend>{L_TITLE}</legend> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_TITLE}</th> @@ -236,16 +236,18 @@  				<td class="row3" colspan="{COLSPAN}" style="text-align: center;">{L_NOT_DISPLAYED}</td>  			</tr>  		<!-- ENDIF --> -		<!-- IF items.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td style="width: 85%; text-align: center;"><img src="{items.IMG_SRC}" width="{items.WIDTH}" height="{items.HEIGHT}" alt="{items.ALT_TEXT}" title="{items.ALT_TEXT}" /></td>  			<!-- IF S_SMILIES -->  				<td style="text-align: center;">{items.CODE}</td>  				<td style="text-align: center;">{items.EMOTION}</td>  			<!-- ENDIF --> -			<td style="text-align: right; white-space: nowrap;"> -				<!-- IF items.S_FIRST_ROW and not U_PREVIOUS_PAGE -->{ICON_MOVE_UP_DISABLED}<!-- ELSE --><a href="{items.U_MOVE_UP}">{ICON_MOVE_UP}</a><!-- ENDIF -->  -				<!-- IF items.S_LAST_ROW and not U_NEXT_PAGE -->{ICON_MOVE_DOWN_DISABLED}<!-- ELSE --><a href="{items.U_MOVE_DOWN}">{ICON_MOVE_DOWN}</a><!-- ENDIF --> -				 <a href="{items.U_EDIT}">{ICON_EDIT}</a> <a href="{items.U_DELETE}" data-ajax="row_delete">{ICON_DELETE}</a> +			<td class="actions" style="text-align: right;"> +				<span class="up-disabled" style="display:none;">{ICON_MOVE_UP_DISABLED}</span> +				<span class="up"><a href="{items.U_MOVE_UP}" data-ajax="row_up">{ICON_MOVE_UP}</a></span> +				<span class="down-disabled" style="display:none;">{ICON_MOVE_DOWN_DISABLED}</span> +				<span class="down"><a href="{items.U_MOVE_DOWN}" data-ajax="row_down">{ICON_MOVE_DOWN}</a></span> +				<a href="{items.U_EDIT}">{ICON_EDIT}</a> <a href="{items.U_DELETE}" data-ajax="row_delete">{ICON_DELETE}</a>  			</td>  		</tr>  	<!-- BEGINELSE --> diff --git a/phpBB/adm/style/acp_inactive.html b/phpBB/adm/style/acp_inactive.html index 1c42c93f76..1cdc1abe6b 100644 --- a/phpBB/adm/style/acp_inactive.html +++ b/phpBB/adm/style/acp_inactive.html @@ -16,7 +16,7 @@  </div>  <!-- ENDIF --> -<table class="table1"> +<table class="table1 zebra-table">  <thead>  <tr>  	<th>{L_USERNAME}</th> @@ -29,8 +29,7 @@  </thead>  <tbody>  <!-- BEGIN inactive --> -	<!-- IF inactive.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> - +	<tr>  		<td style="vertical-align: top;">  			{inactive.USERNAME_FULL}  			<!-- IF inactive.POSTS --><br />{L_POSTS}{L_COLON} <strong>{inactive.POSTS}</strong> [<a href="{inactive.U_SEARCH_USER}">{L_SEARCH_USER_POSTS}</a>]<!-- ENDIF --> diff --git a/phpBB/adm/style/acp_language.html b/phpBB/adm/style/acp_language.html index d4fd572376..d32f6b7eac 100644 --- a/phpBB/adm/style/acp_language.html +++ b/phpBB/adm/style/acp_language.html @@ -147,7 +147,7 @@  	</tr>  	<tr>  		<td class="row3" style="text-align: right;">	 -		<!-- IF ALLOW_UPLOAD -->  {L_UPLOAD_METHOD}{L_COLON} <!-- BEGIN buttons--><input type="radio" class="radio"<!-- IF buttons.S_FIRST_ROW --> id="method" checked="checked"<!-- ENDIF --> value="{buttons.VALUE}" name="method" /> {buttons.VALUE} <!-- END buttons --><input type="submit" name="upload_file" class="button2" value="{L_SUBMIT_AND_UPLOAD}" /><!-- ENDIF --></td> +		<!-- IF ALLOW_UPLOAD -->  {L_UPLOAD_METHOD}{L_COLON} <!-- BEGIN buttons --><input type="radio" class="radio"<!-- IF buttons.S_FIRST_ROW --> id="method" checked="checked"<!-- ENDIF --> value="{buttons.VALUE}" name="method" /> {buttons.VALUE} <!-- END buttons --><input type="submit" name="upload_file" class="button2" value="{L_SUBMIT_AND_UPLOAD}" /><!-- ENDIF --></td>  	</tr>  	</thead>  	<tbody> @@ -211,7 +211,7 @@  	<p>{L_ACP_LANGUAGE_PACKS_EXPLAIN}</p> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_LANGUAGE_PACK_NAME}</th> @@ -226,7 +226,7 @@  		<td class="row3" colspan="5"><strong>{L_INSTALLED_LANGUAGE_PACKS}</strong></td>  	</tr>  	<!-- BEGIN lang --> -		<!-- IF lang.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +	<tr>  		<td><a href="{lang.U_DETAILS}">{lang.ENGLISH_NAME}</a> {lang.TAG}</td>  		<td>{lang.LOCAL_NAME}</td>  		<td style="text-align: center;"><strong>{lang.ISO}</strong></td> @@ -240,7 +240,7 @@  	</tr>  	<!-- ENDIF -->  	<!-- BEGIN notinst --> -		<!-- IF notinst.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +	<tr>  		<td>{notinst.NAME}</td>  		<td>{notinst.LOCAL_NAME}</td>  		<td style="text-align: center;"><strong>{notinst.ISO}</strong></td> diff --git a/phpBB/adm/style/acp_logs.html b/phpBB/adm/style/acp_logs.html index f22eb67a72..592b5bbc16 100644 --- a/phpBB/adm/style/acp_logs.html +++ b/phpBB/adm/style/acp_logs.html @@ -22,7 +22,7 @@  <div><br style="clear: both;" /></div>  <!-- IF .log --> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_USERNAME}</th> @@ -36,7 +36,7 @@  	</thead>  	<tbody>  	<!-- BEGIN log --> -		<!-- IF log.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td>  				{log.USERNAME}  				<!-- IF log.REPORTEE_USERNAME --> diff --git a/phpBB/adm/style/acp_main.html b/phpBB/adm/style/acp_main.html index f5b030d954..6e28c7a0cc 100644 --- a/phpBB/adm/style/acp_main.html +++ b/phpBB/adm/style/acp_main.html @@ -222,7 +222,7 @@  		<div style="text-align: right;"><a href="{U_ADMIN_LOG}">» {L_VIEW_ADMIN_LOG}</a></div> -		<table class="table1"> +		<table class="table1 zebra-table">  		<thead>  		<tr>  			<th>{L_USERNAME}</th> @@ -233,8 +233,7 @@  		</thead>  		<tbody>  		<!-- BEGIN log --> -			<!-- IF log.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> - +			<tr>  				<td>{log.USERNAME}</td>  				<td style="text-align: center;">{log.IP}</td>  				<td style="text-align: center;">{log.DATE}</td> @@ -255,7 +254,7 @@  		<div style="text-align: right;"><a href="{U_INACTIVE_USERS}">» {L_VIEW_INACTIVE_USERS}</a></div> -		<table class="table1"> +		<table class="table1 zebra-table">  		<thead>  		<tr>  			<th>{L_USERNAME}</th> @@ -267,8 +266,7 @@  		</thead>  		<tbody>  		<!-- BEGIN inactive --> -			<!-- IF inactive.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> - +			<tr>  				<td style="vertical-align: top;">  					{inactive.USERNAME_FULL}  					<!-- IF inactive.POSTS --><br />{L_POSTS}{L_COLON} <strong>{inactive.POSTS}</strong> [<a href="{inactive.U_SEARCH_USER}">{L_SEARCH_USER_POSTS}</a>]<!-- ENDIF --> diff --git a/phpBB/adm/style/acp_modules.html b/phpBB/adm/style/acp_modules.html index 5024a950b9..c7688a610c 100644 --- a/phpBB/adm/style/acp_modules.html +++ b/phpBB/adm/style/acp_modules.html @@ -151,9 +151,9 @@  				<td style="width: 15%; white-space: nowrap; text-align: center; vertical-align: middle;"> <!-- IF modules.MODULE_ENABLED --><a href="{modules.U_DISABLE}">{L_DISABLE}</a><!-- ELSE --><a href="{modules.U_ENABLE}">{L_ENABLE}</a><!-- ENDIF --> </td>  				<td class="actions">  					<span class="up-disabled" style="display:none;">{ICON_MOVE_UP_DISABLED}</span> -					<span class="up"><a href="{modules.U_MOVE_UP}">{ICON_MOVE_UP}</a></span> +					<span class="up"><a href="{modules.U_MOVE_UP}" data-ajax="row_up">{ICON_MOVE_UP}</a></span>  					<span class="down-disabled" style="display:none;">{ICON_MOVE_DOWN_DISABLED}</span> -					<span class="down"><a href="{modules.U_MOVE_DOWN}">{ICON_MOVE_DOWN}</a></span> +					<span class="down"><a href="{modules.U_MOVE_DOWN}" data-ajax="row_down">{ICON_MOVE_DOWN}</a></span>  					<a href="{modules.U_EDIT}">{ICON_EDIT}</a>   					<a href="{modules.U_DELETE}" data-ajax="row_delete">{ICON_DELETE}</a>  				</td> diff --git a/phpBB/adm/style/acp_permission_roles.html b/phpBB/adm/style/acp_permission_roles.html index b8fdaeb837..b3137f134c 100644 --- a/phpBB/adm/style/acp_permission_roles.html +++ b/phpBB/adm/style/acp_permission_roles.html @@ -157,20 +157,11 @@  			<!-- IF roles.ROLE_DESCRIPTION --><br /><span>{roles.ROLE_DESCRIPTION}</span><!-- ENDIF -->  		</td>  		<td style="width: 30%; text-align: center; vertical-align: top; white-space: nowrap;"><!-- IF roles.U_DISPLAY_ITEMS --><a href="{roles.U_DISPLAY_ITEMS}">{L_VIEW_ASSIGNED_ITEMS}</a><!-- ELSE -->{L_VIEW_ASSIGNED_ITEMS}<!-- ENDIF --></td> -		<td style="width: 80px; text-align: right; vertical-align: top; white-space: nowrap;"> -			<!-- IF roles.S_FIRST_ROW && not roles.S_LAST_ROW --> -				{ICON_MOVE_UP_DISABLED} -				<a href="{roles.U_MOVE_DOWN}">{ICON_MOVE_DOWN}</a> -			<!-- ELSEIF not roles.S_FIRST_ROW && not roles.S_LAST_ROW--> -				<a href="{roles.U_MOVE_UP}">{ICON_MOVE_UP}</a>  -				<a href="{roles.U_MOVE_DOWN}">{ICON_MOVE_DOWN}</a>  -			<!-- ELSEIF roles.S_LAST_ROW && not roles.S_FIRST_ROW --> -				<a href="{roles.U_MOVE_UP}">{ICON_MOVE_UP}</a>	 -				{ICON_MOVE_DOWN_DISABLED} -			<!-- ELSE --> -				{ICON_MOVE_UP_DISABLED} -				{ICON_MOVE_DOWN_DISABLED} -			<!-- ENDIF -->	 +		<td class="actions"> +			<span class="up-disabled" style="display:none;">{ICON_MOVE_UP_DISABLED}</span> +			<span class="up"><a href="{roles.U_MOVE_UP}" data-ajax="row_up">{ICON_MOVE_UP}</a></span> +			<span class="down-disabled" style="display:none;">{ICON_MOVE_DOWN_DISABLED}</span> +			<span class="down"><a href="{roles.U_MOVE_DOWN}" data-ajax="row_down">{ICON_MOVE_DOWN}</a></span>  			<a href="{roles.U_EDIT}" title="{L_EDIT_ROLE}">{ICON_EDIT}</a>   			<a href="{roles.U_REMOVE}" title="{L_REMOVE_ROLE}" data-ajax="row_delete">{ICON_DELETE}</a>  		</td> diff --git a/phpBB/adm/style/acp_profile.html b/phpBB/adm/style/acp_profile.html index 3546873f6c..44c9cb6d49 100644 --- a/phpBB/adm/style/acp_profile.html +++ b/phpBB/adm/style/acp_profile.html @@ -185,7 +185,7 @@  		</div>  	<!-- ENDIF --> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_FIELD_IDENT}</th> @@ -195,23 +195,16 @@  	</thead>  	<tbody>  	<!-- BEGIN fields --> -		<!-- IF fields.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> - +	<tr>  		<td>{fields.FIELD_IDENT}</td>  		<td>{fields.FIELD_TYPE}</td>  		<td style="text-align: center;"><a href="{fields.U_ACTIVATE_DEACTIVATE}" data-ajax="activate_deactivate">{fields.L_ACTIVATE_DEACTIVATE}</a><!-- IF fields.S_NEED_EDIT --> | <a href="{fields.U_TRANSLATE}" style="color: red;">{L_TRANSLATE}</a><!-- ENDIF --></td> -		<td style="width: 80px; text-align: right; white-space: nowrap;"> -			<!-- IF fields.S_FIRST_ROW && not fields.S_LAST_ROW --> -				{ICON_MOVE_UP_DISABLED} -				<a href="{fields.U_MOVE_DOWN}">{ICON_MOVE_DOWN}</a> -			<!-- ELSEIF not fields.S_FIRST_ROW && not fields.S_LAST_ROW--> -				<a href="{fields.U_MOVE_UP}">{ICON_MOVE_UP}</a> -				<a href="{fields.U_MOVE_DOWN}">{ICON_MOVE_DOWN}</a> -			<!-- ELSEIF fields.S_LAST_ROW && not fields.S_FIRST_ROW --> -				<a href="{fields.U_MOVE_UP}">{ICON_MOVE_UP}</a> -				{ICON_MOVE_DOWN_DISABLED} -			<!-- ENDIF --> +		<td class="actions" style="width: 80px;"> +			<span class="up-disabled" style="display:none;">{ICON_MOVE_UP_DISABLED}</span> +			<span class="up"><a href="{fields.U_MOVE_UP}" data-ajax="row_up">{ICON_MOVE_UP}</a></span> +			<span class="down-disabled" style="display:none;">{ICON_MOVE_DOWN_DISABLED}</span> +			<span class="down"><a href="{fields.U_MOVE_DOWN}" data-ajax="row_down">{ICON_MOVE_DOWN}</a></span>  			<!-- IF not fields.S_NEED_EDIT -->  				<a href="{fields.U_EDIT}">{ICON_EDIT}</a>  			<!-- ELSE --> diff --git a/phpBB/adm/style/acp_prune_forums.html b/phpBB/adm/style/acp_prune_forums.html index 1be9689cc7..4d748f1cce 100644 --- a/phpBB/adm/style/acp_prune_forums.html +++ b/phpBB/adm/style/acp_prune_forums.html @@ -8,7 +8,7 @@  	<p>{L_PRUNE_SUCCESS}</p> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_FORUM}</th> @@ -18,7 +18,7 @@  	</thead>  	<tbody>  	<!-- BEGIN pruned --> -		<!-- IF pruned.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +	<tr>  		<td style="text-align: center;">{pruned.FORUM_NAME}</td>  		<td style="text-align: center;">{pruned.NUM_TOPICS}</td>  		<td style="text-align: center;">{pruned.NUM_POSTS}</td> diff --git a/phpBB/adm/style/acp_ranks.html b/phpBB/adm/style/acp_ranks.html index a1409b15ef..be68dda695 100644 --- a/phpBB/adm/style/acp_ranks.html +++ b/phpBB/adm/style/acp_ranks.html @@ -65,7 +65,7 @@  	<fieldset class="tabulated">  	<legend>{L_ACP_MANAGE_RANKS}</legend> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_RANK_IMAGE}</th> @@ -76,7 +76,7 @@  	</thead>  	<tbody>  	<!-- BEGIN ranks --> -		<!-- IF ranks.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +	<tr>  		<td style="text-align: center;"><!-- IF ranks.S_RANK_IMAGE --><img src="{ranks.RANK_IMAGE}" alt="{ranks.RANK_TITLE}" title="{ranks.RANK_TITLE}" /><!-- ELSE -->  -  <!-- ENDIF --></td>  		<td style="text-align: center;">{ranks.RANK_TITLE}</td>  		<td style="text-align: center;"><!-- IF ranks.S_SPECIAL_RANK -->  -  <!-- ELSE -->{ranks.MIN_POSTS}<!-- ENDIF --></td> diff --git a/phpBB/adm/style/acp_reasons.html b/phpBB/adm/style/acp_reasons.html index ca3aebb338..d629a9553f 100644 --- a/phpBB/adm/style/acp_reasons.html +++ b/phpBB/adm/style/acp_reasons.html @@ -86,17 +86,11 @@  					<br /><span>{reasons.REASON_DESCRIPTION}</span>  				</td>  				<td style="width: 100px;">{reasons.REASON_COUNT}</td> -				<td style="width: 80px; text-align: right; white-space: nowrap;"> -					<!-- IF reasons.S_FIRST_ROW && not reasons.S_LAST_ROW --> -						{ICON_MOVE_UP_DISABLED} -						<a href="{reasons.U_MOVE_DOWN}">{ICON_MOVE_DOWN}</a> -					<!-- ELSEIF not reasons.S_FIRST_ROW && not reasons.S_LAST_ROW--> -						<a href="{reasons.U_MOVE_UP}">{ICON_MOVE_UP}</a>  -						<a href="{reasons.U_MOVE_DOWN}">{ICON_MOVE_DOWN}</a>  -					<!-- ELSEIF reasons.S_LAST_ROW && not reasons.S_FIRST_ROW --> -						<a href="{reasons.U_MOVE_UP}">{ICON_MOVE_UP}</a>	 -						{ICON_MOVE_DOWN_DISABLED} -					<!-- ENDIF --> +				<td class="actions" style="width: 80px;"> +					<span class="up-disabled" style="display:none;">{ICON_MOVE_UP_DISABLED}</span> +					<span class="up"><a href="{reasons.U_MOVE_UP}" data-ajax="row_up">{ICON_MOVE_UP}</a></span> +					<span class="down-disabled" style="display:none;">{ICON_MOVE_DOWN_DISABLED}</span> +					<span class="down"><a href="{reasons.U_MOVE_DOWN}" data-ajax="row_down">{ICON_MOVE_DOWN}</a></span>  					<a href="{reasons.U_EDIT}">{ICON_EDIT}</a>   					<!-- IF reasons.U_DELETE -->  						<a href="{reasons.U_DELETE}" data-ajax="row_delete">{ICON_DELETE}</a> diff --git a/phpBB/adm/style/acp_users.html b/phpBB/adm/style/acp_users.html index b213587def..b84e32013f 100644 --- a/phpBB/adm/style/acp_users.html +++ b/phpBB/adm/style/acp_users.html @@ -125,7 +125,7 @@  	<form id="user_groups" method="post" action="{U_ACTION}"> -	<table class="table1"> +	<table class="table1 zebra-table">  	<tbody>  	<!-- BEGIN group -->  		<!-- IF group.S_NEW_GROUP_TYPE --> @@ -133,7 +133,7 @@  			<td class="row3" colspan="4"><strong>{group.GROUP_TYPE}</strong></td>  		</tr>  		<!-- ELSE --> -			<!-- IF group.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +			<tr>  				<td><a href="{group.U_EDIT_GROUP}">{group.GROUP_NAME}</a></td>  				<td><!-- IF group.S_IS_MEMBER --><!-- IF group.S_NO_DEFAULT --><a href="{group.U_DEFAULT}">{L_GROUP_DEFAULT}</a><!-- ELSE --><strong>{L_GROUP_DEFAULT}</strong><!-- ENDIF --><!-- ELSEIF not group.S_IS_MEMBER and group.U_APPROVE --><a href="{group.U_APPROVE}">{L_GROUP_APPROVE}</a><!-- ELSE --> <!-- ENDIF --></td>  				<td><!-- IF group.S_IS_MEMBER and not group.S_SPECIAL_GROUP --><a href="{group.U_DEMOTE_PROMOTE}">{group.L_DEMOTE_PROMOTE}</a><!-- ELSE --> <!-- ENDIF --></td> @@ -164,7 +164,7 @@  	</div>  	<!-- IF .attach --> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_FILENAME}</th> @@ -176,7 +176,7 @@  	</thead>  	<tbody>  	<!-- BEGIN attach --> -		<!-- IF attach.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +	<tr>  		<td><a href="{attach.U_DOWNLOAD}">{attach.REAL_FILENAME}</a><br /><span class="small"><!-- IF attach.S_IN_MESSAGE --><strong>{L_PM}{L_COLON} </strong><!-- ELSE --><strong>{L_POST}{L_COLON} </strong><!-- ENDIF --><a href="{attach.U_VIEW_TOPIC}">{attach.TOPIC_TITLE}</a></span></td>  		<td style="text-align: center">{attach.POST_TIME}</td>  		<td style="text-align: center">{attach.SIZE}</td> diff --git a/phpBB/adm/style/acp_users_feedback.html b/phpBB/adm/style/acp_users_feedback.html index 95892f2822..f251724cd2 100644 --- a/phpBB/adm/style/acp_users_feedback.html +++ b/phpBB/adm/style/acp_users_feedback.html @@ -7,7 +7,7 @@  	</div>  	<!-- IF .log --> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_REPORT_BY}</th> @@ -19,8 +19,7 @@  	</thead>  	<tbody>  	<!-- BEGIN log --> -		<!-- IF log.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> - +		<tr>  			<td>{log.USERNAME}</td>  			<td style="text-align: center;">{log.IP}</td>  			<td style="text-align: center;">{log.DATE}</td> diff --git a/phpBB/adm/style/acp_users_warnings.html b/phpBB/adm/style/acp_users_warnings.html index 9d082eda6d..6e7f521415 100644 --- a/phpBB/adm/style/acp_users_warnings.html +++ b/phpBB/adm/style/acp_users_warnings.html @@ -1,7 +1,7 @@  	<form id="list" method="post" action="{U_ACTION}">  	<!-- IF .warn --> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_REPORT_BY}</th> @@ -12,7 +12,7 @@  	</thead>  	<tbody>  	<!-- BEGIN warn --> -		<!-- IF warn.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +		<tr>  			<td>{warn.USERNAME}</td>  			<td style="text-align: center; nowrap: nowrap;">{warn.DATE}</td>  			<td>{warn.ACTION}</td> diff --git a/phpBB/adm/style/acp_words.html b/phpBB/adm/style/acp_words.html index 7e91bc4367..6038fc6161 100644 --- a/phpBB/adm/style/acp_words.html +++ b/phpBB/adm/style/acp_words.html @@ -47,7 +47,7 @@  		<input class="button2" name="add" type="submit" value="{L_ADD_WORD}" />  	</p> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th>{L_WORD}</th> @@ -57,7 +57,7 @@  	</thead>  	<tbody>  	<!-- BEGIN words --> -		<!-- IF words.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> +	<tr>  		<td style="text-align: center;">{words.WORD}</td>  		<td style="text-align: center;">{words.REPLACEMENT}</td>  		<td> <a href="{words.U_EDIT}">{ICON_EDIT}</a>  <a href="{words.U_DELETE}" data-ajax="row_delete">{ICON_DELETE}</a> </td> diff --git a/phpBB/adm/style/admin.css b/phpBB/adm/style/admin.css index c4f1caf722..aa8622ff61 100644 --- a/phpBB/adm/style/admin.css +++ b/phpBB/adm/style/admin.css @@ -220,7 +220,7 @@ li {  }  .rtl #main { -	float: right; +	float: left;  	margin: 0 -210px 0 0;  } @@ -352,7 +352,7 @@ li {  	position: relative;  } -.rtl #tabs a { +.rtl #tabs li {  	float: right;  } @@ -840,8 +840,22 @@ td.name {  	text-align: right;  } -.row1 { background-color: #F9F9F9; } -.row2 { background-color: #DCEBFE; } +.row1 { +	background-color: #F9F9F9; +} + +table.zebra-table tbody tr:nth-child(odd) { +	background-color: #F9F9F9; +} + +.row2 { +	background-color: #DCEBFE; +} + +table.zebra-table tbody tr:nth-child(even) { +	background-color: #DCEBFE; +} +  .row3 { background-color: #DBDFE2; }  .row4 { background-color: #E4E8EB; }  .col1 { background-color: #DCEBFE; } diff --git a/phpBB/adm/style/ajax.js b/phpBB/adm/style/ajax.js index 78fcbd88fd..959580d6c2 100644 --- a/phpBB/adm/style/ajax.js +++ b/phpBB/adm/style/ajax.js @@ -8,7 +8,11 @@   * an item is moved up. It moves the row up or down, and deactivates /   * activates any up / down icons that require it (the ones at the top or bottom).   */ -phpbb.addAjaxCallback('row_down', function() { +phpbb.addAjaxCallback('row_down', function(res) { +	if (typeof res.success === 'undefined' || !res.success) { +		return; +	} +  	var el = $(this),  		tr = el.parents('tr'),  		trSwap = tr.next(); @@ -16,7 +20,11 @@ phpbb.addAjaxCallback('row_down', function() {  	tr.insertAfter(trSwap);  }); -phpbb.addAjaxCallback('row_up', function() { +phpbb.addAjaxCallback('row_up', function(res) { +	if (typeof res.success === 'undefined' || !res.success) { +		return; +	} +  	var el = $(this),  		tr = el.parents('tr'),  		trSwap = tr.prev(); diff --git a/phpBB/adm/style/captcha_qa_acp.html b/phpBB/adm/style/captcha_qa_acp.html index 4ff7c328fc..6235f9a104 100644 --- a/phpBB/adm/style/captcha_qa_acp.html +++ b/phpBB/adm/style/captcha_qa_acp.html @@ -14,7 +14,7 @@  	<fieldset class="tabulated">  	<legend>{L_QUESTIONS}</legend> -	<table class="table1"> +	<table class="table1 zebra-table">  	<thead>  	<tr>  		<th colspan="3">{L_QUESTIONS}</th> @@ -27,14 +27,12 @@  	</thead>  	<tbody>  	<!-- BEGIN questions --> - -		<!-- IF questions.S_ROW_COUNT is even --><tr class="row1"><!-- ELSE --><tr class="row2"><!-- ENDIF --> - +	<tr>  		<td style="text-align: left;">{questions.QUESTION_TEXT}</td>  		<td style="text-align: center;">{questions.QUESTION_LANG}</td>  		<td style="text-align: center;"><a href="{questions.U_EDIT}">{ICON_EDIT}</a> <a href="{questions.U_DELETE}">{ICON_DELETE}</a></td> -		</tr> -		<!-- END questions --> +	</tr> +	<!-- END questions -->  	</tbody>  	</table>  	<fieldset class="quick"> diff --git a/phpBB/assets/javascript/editor.js b/phpBB/assets/javascript/editor.js index 5222de9fee..be3d868f13 100644 --- a/phpBB/assets/javascript/editor.js +++ b/phpBB/assets/javascript/editor.js @@ -78,14 +78,13 @@ function bbfontstyle(bbopen, bbclose) {  		if (theSelection) {  			// Add tags around selection  			document.selection.createRange().text = bbopen + theSelection + bbclose; -			document.forms[form_name].elements[text_name].focus(); +			textarea.focus();  			theSelection = '';  			return;  		} -	} else if (document.forms[form_name].elements[text_name].selectionEnd -			&& (document.forms[form_name].elements[text_name].selectionEnd - document.forms[form_name].elements[text_name].selectionStart > 0)) { -		mozWrap(document.forms[form_name].elements[text_name], bbopen, bbclose); -		document.forms[form_name].elements[text_name].focus(); +	} else if (textarea.selectionEnd && (textarea.selectionEnd - textarea.selectionStart > 0)) { +		mozWrap(textarea, bbopen, bbclose); +		textarea.focus();  		theSelection = '';  		return;  	} diff --git a/phpBB/assets/javascript/plupload.js b/phpBB/assets/javascript/plupload.js index 1befb88eb6..a90757d487 100644 --- a/phpBB/assets/javascript/plupload.js +++ b/phpBB/assets/javascript/plupload.js @@ -1,38 +1,82 @@  plupload.addI18n(phpbb.plupload.i18n); -plupload.attachment_data = []; +phpbb.plupload.ids = []; + +(function($) {  // Avoid conflicts with other libraries + +"use strict";  /** - * Returns the index of the plupload.attachment_data array where the given - * attach id appears + * Set up the uploader.   * - * @param int id The attachment id of the file + * @return undefined + */ +phpbb.plupload.initialize = function() { +	// Initialize the Plupload uploader. +	uploader.init(); + +	// Set attachment data. +	phpbb.plupload.setData(phpbb.plupload.data); +	phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData()); + +	// Only execute if Plupload initialized successfully. +	uploader.bind('Init', function() { +		phpbb.plupload.form = $(phpbb.plupload.config.form_hook)[0], +		phpbb.plupload.rowTpl = $('#attach-row-tpl')[0].outerHTML; + +		// Hide the basic upload panel and remove the attach row template. +		$('#attach-row-tpl, #attach-panel-basic').remove(); +		// Show multi-file upload options. +		$('#attach-panel-multi').show(); +	}); + +	uploader.bind('PostInit', function() { +		// Point out the drag-and-drop zone if it's supported. +		if (uploader.features.dragdrop) { +			$('#drag-n-drop-message').show(); +		} +	}); +}; + +/** + * Unsets all elements in the object uploader.settings.multipart_params whose keys + * begin with 'attachment_data['   * - * @return bool	Returns false if the id cannot be found - * @return int	Returns the index in the main array where the attachment id - * 	was found + * @return undefined   */ -function phpbb_plupload_find_attachment_idx(id) { -	var data = plupload.attachment_data; -	for (var i = 0; i < data.length; i++) { -		if (data[i].attach_id == id) { -			return i; +phpbb.plupload.clearParams = function() { +	var obj = uploader.settings.multipart_params; +	for (var key in obj) { +		if (!obj.hasOwnProperty(key) || key.indexOf('attachment_data[') !== 0) { +			continue;  		} + +		delete uploader.settings.multipart_params[key];  	} +}; -	return false; -} +/** + * Update uploader.settings.multipart_params object with new data. + * + * @param object obj + * @return undefined + */ +phpbb.plupload.updateMultipartParams = function(obj) { +	uploader.settings.multipart_params = $.extend( +		uploader.settings.multipart_params, +		obj +	); +};  /** - * Converts an array of objects into an object that PHP would expect as POST - * data + * Convert the array of attachment objects into an object that PHP would expect as POST data.   *   * @return object An object in the form 'attachment_data[i][key]': value as   * 	expected by the server   */ -function phpbb_plupload_attachment_data_serialize() { +phpbb.plupload.getSerializedData = function() {  	var obj = {}; -	for (var i = 0; i < plupload.attachment_data.length; i++) { -		var datum = plupload.attachment_data[i]; +	for (var i = 0; i < phpbb.plupload.data.length; i++) { +		var datum = phpbb.plupload.data[i];  		for (var key in datum) {  			if (!datum.hasOwnProperty(key)) {  				continue; @@ -41,264 +85,596 @@ function phpbb_plupload_attachment_data_serialize() {  			obj['attachment_data[' + i + '][' + key + ']'] = datum[key];  		}  	} -  	return obj; -} +};  /** - * Unsets all elements in an object whose keys begin with 'attachment_data[' + * Get the index from the phpbb.plupload.data array where the given + * attachment id appears.   * - * @param object The object to be cleared + * @param int attach_id The attachment id of the file. + * @return bool	Returns false if the id cannot be found. + * @return int	Returns the index of the file if it exists. + */ +phpbb.plupload.getIndex = function(attach_id) { +	var index = $.inArray(Number(attach_id), phpbb.plupload.ids); +	return (index !== -1) ? index : false; +}; + +/** + * Set the data in phpbb.plupload.data and phpbb.plupload.ids arrays. + *  + * @param array data	Array containing the new data to use. In the form of  + * array(index => object(property: value). Requires attach_id to be one of the object properties.   *   * @return undefined   */ -function phpbb_plupload_clear_params(obj) { -	for (var key in obj) { -		if (!obj.hasOwnProperty(key) || key.indexOf('attachment_data[') !== 0) { -			continue; -		} +phpbb.plupload.setData = function(data) { +	// Make sure that the array keys are reset. +	phpbb.plupload.ids = phpbb.plupload.data = []; +	phpbb.plupload.data = data; -		delete obj[key]; +	for (var i = 0; i < data.length; i++) { +		phpbb.plupload.ids.push(Number(data[i].attach_id));  	} -} +};  /** - * Update hidden attachment inputs in posting form - * Pre-existing hidden inputs will be removed by comparing the old attachment - * data (old_data) to the new attachment data (data) that has been sent back - * by plupload. - * - * @param object form Posting form - * @param object data Current attachment_data - * @param object old_date Previous attachment_data (before submission) - * - * @return void + * Update the attachment data in the HTML and the phpbb & phpbb.plupload objects. + *  + * @param array data		Array containing the new data to use. + * @param string action		The action that required the update. Used to update the inline attachment bbcodes. + * @param int index			The index from phpbb.plupload_ids that was affected by the action. + * @param array downloadUrl	Optional array of download urls to update. + * @return undefined   */ -phpbb.update_hidden_attachment_inputs = function(form, data, old_data) { -	// Update already existing hidden inputs -	for (var i = 0; i < form.length; i++) { -		if (data.hasOwnProperty(form[i].name)) { -			form[i].value = data[form[i].name]; -			delete data[form[i].name]; -		} else if (typeof old_data !== 'undefined' && old_data.hasOwnProperty(form[i].name)) { -			var inputRegex = /\b^[a-z_]+[+[0-9]+]/; -			var inputName = inputRegex.exec(form[i].name); -			if (typeof inputName !== 'undefined' && inputName[0] !== '') { -				$("input[type='hidden'][name^='" + inputName[0] + "']").remove(); -			} -		} +phpbb.plupload.update = function(data, action, index, downloadUrl) { + +	phpbb.plupload.updateBbcode(action, index); +	phpbb.plupload.setData(data); +	phpbb.plupload.updateRows(downloadUrl); +	phpbb.plupload.clearParams(); +	phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData()); +}; + +/** + * Update the relevant elements and hidden data for all attachments. + *  + * @param array downloadUrl	Optional array of download urls to update. + * @return undefined + */ +phpbb.plupload.updateRows = function(downloadUrl) { +	for (var i = 0; i < phpbb.plupload.ids.length; i++) { +		phpbb.plupload.updateRow(i, downloadUrl);  	} +}; -	// Append new inputs -	for (var key in data) { -		if (!data.hasOwnProperty(key)) { -			continue; -		} +/** + * Insert a row for a new attachment. This expects an HTML snippet in the HTML + * using the id "attach-row-tpl" to be present. This snippet is cloned and the + * data for the file inserted into it. The row is then appended or prepended to + * #file-list based on the attach_order setting. + *  + * @param object file	Plupload file object for the new attachment. + * @return undefined + */ +phpbb.plupload.insertRow = function(file) { +	var row = $(phpbb.plupload.rowTpl); + +	row.attr('id', file.id); +	row.find('.file-name').html(file.name); +	row.find('.file-size').html(plupload.formatSize(file.size)); +	if (phpbb.plupload.order == 'desc') { +		$('#file-list').prepend(row); +	} else { +		$('#file-list').append(row); +	} +}; + +/** + * Update the relevant elements and hidden data for an attachment. + *  + * @param int index	The index from phpbb.plupload.ids of the attachment to edit. + * @param array downloadUrl	Optional array of download urls to update.  + * @return undefined + */ +phpbb.plupload.updateRow = function(index, downloadUrl) { +	var attach = phpbb.plupload.data[index], +		row = $('[data-attach-id="' + attach.attach_id + '"]'); + +	// Add the link to the file +	if (typeof downloadUrl !== 'undefined' && typeof downloadUrl[index] !== 'undefined') { +		var url = downloadUrl[index].replace('&', '&'), +			link = $('<a></a>'); + +		link.attr('href', url).html(attach.real_filename); +		row.find('.file-name').html(link)	 +	} + +	row.find('textarea').attr('name', 'comment_list[' + index + ']'); +	phpbb.plupload.updateHiddenData(row, attach, index); +}; + +/** + * Update hidden input data for an attachment. + * + * @param object row	jQuery object for the attachment row. + * @param object attach	Attachment data object from phpbb.plupload.data + * @param int index		Attachment index from phpbb.plupload.ids + * @return undefined + */ +phpbb.plupload.updateHiddenData = function(row, attach, index) { +	row.find('input[type="hidden"]').remove(); + +	for (var key in attach) {  		var input = $('<input />')  			.attr('type', 'hidden') -			.attr('name', key) -			.attr('value', data[key]); -		$(form).append(input); +			.attr('name', 'attachment_data[' + index + '][' + key +']') +			.attr('value', attach[key]); +		$('textarea', row).after(input);  	} -} - -jQuery(function($) { -	$(phpbb.plupload.config.element_hook).pluploadQueue(phpbb.plupload.config); -	var uploader = $(phpbb.plupload.config.element_hook).pluploadQueue(); +}; -	// Check the page for already-existing attachment data and add it to the -	// array -	var form = $(phpbb.plupload.config.form_hook)[0]; -	for (var i = 0; i < form.length; i++) { -		if (form[i].name.indexOf('attachment_data[') !== 0) { -			continue; -		} -		 -		var matches = form[i].name.match(/\[(\d+)\]\[([^\]]+)\]/); -		var index = matches[1]; -		var property = matches[2]; -		 -		if (!plupload.attachment_data[index]) { -			plupload.attachment_data[index] = {}; -		} -		 -		plupload.attachment_data[index][property] = form[i].value; -		uploader.settings.multipart_params[form[i].name] = form[i].value; +/** + * Deleting a file removes it from the queue and fires an AJAX event to the + * server to tell it to remove the temporary attachment. The server + * responds with the updated attachment data list so that any future + * uploads can maintain state with the server + * + * @param object row	jQuery object for the attachment row. + * @param int attachId	Attachment id of the file to be removed. + * + * @return undefined + */ +phpbb.plupload.deleteFile = function(row, attachId) { +	// If there's no attach id, then the file hasn't been uploaded. Simply delete the row. +	if (typeof attachId === 'undefined') { +		row.slideUp(100, function() { +			row.remove(); +			phpbb.plupload.hideEmptyList(); +		});  	} -	/** -	 * Fires before a given file is about to be uploaded. This allows us to -	 * send the real filename along with the chunk. This is necessary because -	 * for some reason the filename is set to 'blob' whenever a file is chunked -	 * -	 * @param object up		The plupload.Uploader object -	 * @param object file	The plupload.File object that is about to be -	 * 	uploaded -	 * -	 * @return undefined -	 */ -	uploader.bind('BeforeUpload', function(up, file) { -		up.settings.multipart_params = $.extend( -			up.settings.multipart_params, -			{'real_filename': file.name} -		); -	}); +	var index = phpbb.plupload.getIndex(attachId); +	row.find('.file-status').toggleClass('file-uploaded file-working'); -	/** -	 * Fired when a single chunk of any given file is uploaded. This parses the -	 * response from the server and checks for an error. If an error occurs it -	 * is reported to the user and the upload of this particular file is halted -	 * -	 * @param object up			The plupload.Uploader object -	 * @param object file		The plupload.File object whose chunk has just -	 * 	been uploaded -	 * @param object response	The response object from the server -	 * -	 * @return undefined -	 */ -	uploader.bind('ChunkUploaded', function(up, file, response) { -		if (response.chunk >= response.chunks - 1) { -			return; -		} +	if (index === false) { +		return; +	} +	var fields = {}; +	fields['delete_file[' + index + ']'] = 1; + +	var always = function() { +		row.find('.file-status').removeClass('file-working'); +	}; +	var done = function(response) {  		var json = {};  		try { -			json = $.parseJSON(response.response); +			json = $.parseJSON(response);  		} catch (e) { -			file.status = plupload.FAILED; -			up.trigger('FileUploaded', file, { -				response: JSON.stringify({ -					error: { -						message: 'Error parsing server response.' -					} -				}) -			}); +			return;  		} -		if (json.error) { -			file.status = plupload.FAILED; -			up.trigger('FileUploaded', file, { -				response: JSON.stringify({ -					error: { -						message: json.error.message -					} -				}) -			}); +		// trigger_error() was called which likely means a permission error was encountered. +		if (typeof response.title !== 'undefined') { +			uploader.trigger('Error', {message: response.message}); +			// We will have to assume that the deletion failed. So leave the file status as uploaded. +			row.find('.file-status').toggleClass('file-uploaded'); + +			return;  		} -	}); +		phpbb.plupload.update(response, 'removal', index); +		// Check if the user can upload files now if he had reached the max files limit. +		phpbb.plupload.handleMaxFilesReached(); -	/** -	 * Fires when an entire file has been uploaded. It checks for errors -	 * returned by the server otherwise parses the list of attachment data and -	 * appends it to the next file upload so that the server can maintain state -	 * with regards to the attachments in a given post -	 * -	 * @param object up			The plupload.Uploader object -	 * @param object file		The plupload.File object that has just been -	 * 	uploaded -	 * @param string response	The response string from the server -	 * -	 * @return undefined -	 */ -	uploader.bind('FileUploaded', function(up, file, response) { -		var json = {}; -		try { -			json = $.parseJSON(response.response); -		} catch (e) { -			file.status = plupload.FAILED; -			file.error = 'Error parsing server response.' +		if (row.attr('id')) { +			var file = uploader.getFile(row.attr('id')); +			uploader.removeFile(file); +		} +		row.slideUp(100, function() { +			row.remove(); +			// Hide the file list if it's empty now. +			phpbb.plupload.hideEmptyList(); +		}); +		uploader.trigger('FilesRemoved'); +	}; + +	$.ajax(phpbb.plupload.config.url, { +		type: 'POST', +		data: $.extend(fields, phpbb.plupload.getSerializedData()), +		headers: {'X-PHPBB-USING-PLUPLOAD': '1', 'X-Requested-With': 'XMLHttpRequest'} +	}) +	.always(always) +	.done(done); +}; + +/** + * Check the attachment list and hide its container if it's empty. + * + * @return undefined + */ +phpbb.plupload.hideEmptyList = function() { +	if (!$('#file-list').children().length) { +		$('#file-list-container').slideUp(100); +	} +} + +/** + * Update the indices used in inline attachment bbcodes. This ensures that the bbcodes + * correspond to the correct file after a file is added or removed. This should be called  + * before the phpbb.plupload,data and phpbb.plupload.ids arrays are updated, otherwise it will + * not work correctly. + * + * @param string action	The action that occurred -- either "addition" or "removal" + * @param int index		The index of the attachment from phpbb.plupload.ids that was affected. + * + * @return undefined + */ +phpbb.plupload.updateBbcode = function(action, index) { +	var	textarea = $('#message', phpbb.plupload.form), +		text = textarea.val(), +		removal = (action === 'removal'); + +	// Return if the bbcode isn't used at all. +	if (text.indexOf('[attachment=') === -1) { +		return; +	} + +	// Private function used to replace the bbcode. +	var updateBbcode = function(match, fileName) { +		// Remove the bbcode if the file was removed. +		if (removal && index === i) { +			return '';  		} +		var newIndex = i + ((removal) ? -1 : 1); +		return '[attachment=' + newIndex +']' + fileName + '[/attachment]'; +	}; -		if (json.error) { -			file.status = plupload.FAILED; -			file.error = json.error.message; -		} else if (file.status === plupload.DONE) { -			plupload.attachment_data = json; -			file.attachment_data = json[0]; -			up.settings.multipart_params = $.extend( -				up.settings.multipart_params, -				phpbb_plupload_attachment_data_serialize() -			); +	// Private function used to generate search regexp +	var searchRegexp = function(index) { +		return new RegExp('\\[attachment=' + index + '\\](.*?)\\[\\/attachment\\]', 'g'); +	} +	// The update order of the indices is based on the action taken to ensure that we don't corrupt +	// the bbcode index by updating it several times as we move through the loop. +	// Removal loop starts at the removed index and moves to the end of the array. +	// Addition loop starts at the end of the array and moves to the added index at 0. +	var searchLoop = function() { +		if (typeof i === 'undefined') { +			i = (removal) ? index : phpbb.plupload.ids.length - 1; +		} +		return (removal) ? (i < phpbb.plupload.ids.length): (i >= index); +	} +	var i; + +	while (searchLoop()) { +		text = text.replace(searchRegexp(i), updateBbcode); +		(removal) ? i++ : i--; +	} +	textarea.val(text); +}; + +/** + * Get Plupload file objects based on their upload status. + * + * @param int status Plupload status - plupload.DONE, plupload.FAILED, plupload.QUEUED, + * plupload.STARTED, plupload.STOPPED + * + * @return Returns an array of the Plupload file objects matching the status. + */ +phpbb.plupload.getFilesByStatus = function(status) { +	var files = []; + +	$.each(uploader.files, function(i, file) { +		if (file.status === status) { +			files.push(file);  		}  	}); +	return files; +} -	/** -	 * Fires when the entire queue of files have been uploaded. It resets the -	 * 'add files' button to allow more files to be uploaded and also attaches -	 * several events to each row of the currently-uploaded files to facilitate -	 * deleting any one of the files. -	 * -	 * Deleting a file removes it from the queue and fires an ajax event to the -	 * server to tell it to remove the temporary attachment. The server -	 * responds with the updated attachment data list so that any future -	 * uploads can maintain state with the server -	 * -	 * @param object up		The plupload.Uploader object -	 * @param array files	An array of plupload.File objects that have just -	 * 	been uploaded as part of a queue -	 * -	 * @return undefined -	 */ -	uploader.bind('UploadComplete', function(up, files) { -		$('.plupload_upload_status').css('display', 'none'); -		$('.plupload_buttons').css('display', 'block'); - -		// Insert a bunch of hidden input elements containing the attachment -		// data so that the save/preview/submit buttons work as expected. -		var form = $(phpbb.plupload.config.form_hook)[0]; -		var data = phpbb_plupload_attachment_data_serialize(); - -		phpbb.update_hidden_attachment_inputs(form, data); - -		files.forEach(function(file) { -			if (file.status !== plupload.DONE) { -				var click = function(evt) { -					alert(file.error); -				} +/** + * Check whether the user has reached the maximun number of files that he's allowed + * to upload. If so, disables the uploader and marks the queued files as failed. Otherwise + * makes sure that the uploader is enabled. + * + * @return bool Returns true if the limit has been reached. False if otherwise. + */ +phpbb.plupload.handleMaxFilesReached = function() { +	// If there is no limit, the user is an admin or moderator. +	if (!phpbb.plupload.maxFiles) { +		return false; +	} -				$('#' + file.id).attr('title', file.error); -				$('#' + file.id).click(click); +	if (phpbb.plupload.maxFiles <= phpbb.plupload.ids.length) { +		// Fail the rest of the queue. +		phpbb.plupload.markQueuedFailed(phpbb.plupload.lang.TOO_MANY_ATTACHMENTS); +		// Disable the uploader. +		phpbb.plupload.disableUploader(); +		uploader.trigger('Error', {message: phpbb.plupload.lang.TOO_MANY_ATTACHMENTS}); -				return; -			} +		return true; +	} else if(phpbb.plupload.maxFiles > phpbb.plupload.ids.length) { +		// Enable the uploader if the user is under the limit +		phpbb.plupload.enableUploader(); +	} +	return false; +} + +/** + * Disable the uploader + * + * @return undefined + */ +phpbb.plupload.disableUploader = function() { +	$('#add_files').addClass('disabled'); +	uploader.disableBrowse(); +} + +/** + * Enable the uploader + * + * @return undefined + */ +phpbb.plupload.enableUploader = function() { +	$('#add_files').removeClass('disabled'); +	uploader.disableBrowse(false); +} + +/** + * Mark all queued files as failed. + * + * @param string error Error message to present to the user. + * @return undefined + */ +phpbb.plupload.markQueuedFailed = function(error) { +	var files = phpbb.plupload.getFilesByStatus(plupload.QUEUED); + +	$.each(files, function(i, file) { +		$('#' + file.id).find('.file-progress').hide(); +		phpbb.plupload.fileError(file, error); +	}); +} + +/** + * Marks a file as failed and sets the error message for it. + * + * @param object file	Plupload file object that failed. + * @param string error	Error message to present to the user. + * @return undefined + */ +phpbb.plupload.fileError = function(file, error) { +	file.status = plupload.FAILED; +	file.error = error; +	$('#' + file.id).find('.file-status').addClass('file-error').attr({'data-error-title': phpbb.plupload.lang.ERROR, 'data-error-message': error}); +} + + + + +/** + * Set up the Plupload object and get some basic data. + */ +var	uploader = new plupload.Uploader(phpbb.plupload.config); +phpbb.plupload.initialize(); + + + + +/** + * Insert inline attachment bbcode. + */ + $('#file-list').on('click', '.file-inline-bbcode', function(e) { +	var attachId = $(this).parents('.attach-row').attr('data-attach-id'), +		index = phpbb.plupload.getIndex(attachId); + +	attach_inline(index, phpbb.plupload.data[index].real_filename);	 +	e.preventDefault(); +}); + +/** + * Delete a file. + */ +$('#file-list').on('click', '.file-delete', function(e) { +	var row = $(this).parents('.attach-row'), +		attachId = row.attr('data-attach-id'); + +	phpbb.plupload.deleteFile(row, attachId); +	e.preventDefault(); +}); + +/** + * Display the error message for a particular file when the error icon is clicked. + */ +$('#file-list').on('click', '.file-error', function(e) { +	phpbb.alert($(this).attr('data-error-title'), $(this).attr('data-error-message')); +	e.preventDefault(); +}); + +/** + * Fires when an error occurs. + */ +uploader.bind('Error', function(up, error) { +	// The error message that Plupload provides for these is vague, so we'll be more specific. +	if (error.code === plupload.FILE_EXTENSION_ERROR) { +		error.message = plupload.translate('Invalid file extension:') + ' ' + error.file.name; +	} else if (error.code === plupload.FILE_SIZE_ERROR) { +		error.message = plupload.translate('File too large:') + ' ' + error.file.name; +	} +	phpbb.alert(phpbb.plupload.lang.ERROR, error.message); +}); + +/** + * Fires before a given file is about to be uploaded. This allows us to + * send the real filename along with the chunk. This is necessary because + * for some reason the filename is set to 'blob' whenever a file is chunked + * + * @param object up		The plupload.Uploader object + * @param object file	The plupload.File object that is about to be + * 	uploaded + * + * @return undefined + */ +uploader.bind('BeforeUpload', function(up, file) { +	if (phpbb.plupload.handleMaxFilesReached()) { +		return; +	} + +	phpbb.plupload.updateMultipartParams({'real_filename': file.name}); +}); + +/** + * Fired when a single chunk of any given file is uploaded. This parses the + * response from the server and checks for an error. If an error occurs it + * is reported to the user and the upload of this particular file is halted + * + * @param object up			The plupload.Uploader object + * @param object file		The plupload.File object whose chunk has just + * 	been uploaded + * @param object response	The response object from the server + * + * @return undefined + */ +uploader.bind('ChunkUploaded', function(up, file, response) { +	if (response.chunk >= response.chunks - 1) { +		return; +	} -			var click = function(evt) { -				$(evt.target).find('a').addClass('working'); - -				// The index is always found because file.attachment_data is -				// just an element of plupload.attachment_data -				var idx = phpbb_plupload_find_attachment_idx(file.attachment_data.attach_id); -				var fields = {}; -				fields['delete_file[' + idx + ']'] = 1; - -				var always = function() { -					$(evt.target).find('a').removeClass('working'); -				}; - -				var done = function(response) { -					up.removeFile(file); -					plupload.attachment_data = response; -					phpbb.update_hidden_attachment_inputs(form, phpbb_plupload_attachment_data_serialize(), data); -					phpbb_plupload_clear_params(up.settings.multipart_params); -					up.settings.multipart_params = $.extend( -						up.settings.multipart_params, -						phpbb_plupload_attachment_data_serialize() -					); -				}; -				 -				$.ajax(phpbb.plupload.config.url, { -					type: 'POST', -					data: $.extend(fields, phpbb_plupload_attachment_data_serialize()), -					headers: {'X-PHPBB-USING-PLUPLOAD': '1'} -				}) -				.always(always) -				.done(done); -			}; -			 -			$('#' + file.id) -			.addClass('can_delete') -			.click(click); +	var json = {}; +	try { +		json = $.parseJSON(response.response); +	} catch (e) { +		file.status = plupload.FAILED; +		up.trigger('FileUploaded', file, { +			response: JSON.stringify({ +				error: { +					message: 'Error parsing server response.' +				} +			})  		}); +	} + +	// If trigger_error() was called, then a permission error likely occurred. +	if (typeof json.title !== 'undefined') { +		json.error = {message: json.message}; +	} + +	if (json.error) { +		file.status = plupload.FAILED; +		up.trigger('FileUploaded', file, { +			response: JSON.stringify({ +				error: { +					message: json.error.message +				} +			}) +		}); +	} +}); + +/** + * Fires when files are added to the queue. + * + * @return undefined + */ +uploader.bind('FilesAdded', function(up, files) { +	// Prevent unnecessary requests to the server if the user already uploaded +	// the maximum number of files allowed. +	if (phpbb.plupload.handleMaxFilesReached()) { +		return; +	} + +	// Show the file list if there aren't any files currently. +	if (!$('#file-list-container').is(':visible')) { +		$('#file-list-container').show(100); +	} + +	$.each(files, function(i, file) { +		phpbb.plupload.insertRow(file);  	}); + +	up.bind('UploadProgress', function(up, file) { +		$('#' + file.id + " .file-progress-bar").css('width', file.percent + '%'); +		$('#file-total-progress-bar').css('width', up.total.percent + '%'); +	}); + +	// Do not allow more files to be added to the running queue. +	phpbb.plupload.disableUploader(); + +	// Start uploading the files once the user has selected them. +	up.start();  }); + + +/** + * Fires when an entire file has been uploaded. It checks for errors + * returned by the server otherwise parses the list of attachment data and + * appends it to the next file upload so that the server can maintain state + * with regards to the attachments in a given post + * + * @param object up			The plupload.Uploader object + * @param object file		The plupload.File object that has just been + * 	uploaded + * @param string response	The response string from the server + * + * @return undefined + */ +uploader.bind('FileUploaded', function(up, file, response) { +	var json = {}, +		row = $('#' + file.id), +		error; + +	// Hide the progress indicator. +	row.find('.file-progress').hide(); + +	try { +		json = $.parseJSON(response.response); +	} catch (e) { +		error = 'Error parsing server response.'; +	} + +	// If trigger_error() was called, then a permission error likely occurred. +	if (typeof json.title !== 'undefined') { +		error = json.message; +		up.trigger('Error', {message: error}); + +		// The rest of the queue will fail. +		phpbb.plupload.markQueuedFailed(error); +	} else if (json.error) { +		error = json.error.message; +	} + +	if (typeof error !== 'undefined') { +		phpbb.plupload.fileError(file, error); +	} else if (file.status === plupload.DONE) { +		file.attachment_data = json['data'][0]; + +		row.attr('data-attach-id', file.attachment_data.attach_id); +		row.find('.file-inline-bbcode').show(); +		row.find('.file-status').addClass('file-uploaded'); +		phpbb.plupload.update(json['data'], 'addition', 0, [json['download_url']]); +	} +}); + +/** + * Fires when the entire queue of files have been uploaded.  + * + * @param object up		The plupload.Uploader object + * @param array files	An array of plupload.File objects that have just + * 	been uploaded as part of a queue + * + * @return undefined + */ +uploader.bind('UploadComplete', function(up, files) { +	// Hide the progress bar +	setTimeout(function() { +		$('#file-total-progress-bar').fadeOut(500, function() { +			$(this).css('width', 0).show(); +		}); +	}, 2000); + +	// Re-enable the uploader +	phpbb.plupload.enableUploader(); +}); + +})(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/assets/plupload/jquery.plupload.queue/img/done.gif b/phpBB/assets/plupload/img/done.gif Binary files differindex 29f3ed7c97..29f3ed7c97 100644 --- a/phpBB/assets/plupload/jquery.plupload.queue/img/done.gif +++ b/phpBB/assets/plupload/img/done.gif diff --git a/phpBB/assets/plupload/jquery.plupload.queue/img/error.gif b/phpBB/assets/plupload/img/error.gif Binary files differindex 4682b63007..4682b63007 100644 --- a/phpBB/assets/plupload/jquery.plupload.queue/img/error.gif +++ b/phpBB/assets/plupload/img/error.gif diff --git a/phpBB/assets/plupload/jquery.plupload.queue/img/throbber.gif b/phpBB/assets/plupload/img/throbber.gif Binary files differindex 4ae8b16a5a..4ae8b16a5a 100644 --- a/phpBB/assets/plupload/jquery.plupload.queue/img/throbber.gif +++ b/phpBB/assets/plupload/img/throbber.gif diff --git a/phpBB/assets/plupload/jquery.plupload.queue/css/jquery.plupload.queue.css b/phpBB/assets/plupload/jquery.plupload.queue/css/jquery.plupload.queue.css deleted file mode 100644 index da60fefe2b..0000000000 --- a/phpBB/assets/plupload/jquery.plupload.queue/css/jquery.plupload.queue.css +++ /dev/null @@ -1,177 +0,0 @@ -/* -   Plupload -------------------------------------------------------------------- */ - -.plupload_button { -	display: -moz-inline-box; /* FF < 3*/ -	display: inline-block; -	font: normal 12px sans-serif; -	text-decoration: none; -	color: #42454a; -	border: 1px solid #bababa; -	padding: 2px 8px 3px 20px; -	margin-right: 4px; -	background: #f3f3f3 url('../img/buttons.png') no-repeat 0 center; -	outline: 0; - -	/* Optional rounded corners for browsers that support it */ -	-moz-border-radius: 3px; -	-khtml-border-radius: 3px; -	-webkit-border-radius: 3px; -	border-radius: 3px; -} - -.plupload_button:hover { -	color: #000; -	text-decoration: none; -} - -.plupload_disabled, a.plupload_disabled:hover { -	color: #737373; -	border-color: #c5c5c5; -	background: #ededed url('../img/buttons-disabled.png') no-repeat 0 center; -	cursor: default; -} - -.plupload_add { -	background-position: -181px center; -} - -.plupload_wrapper { -	font: normal 11px Verdana, sans-serif; -	width: 100%; -} - -.plupload_container { -	padding: 8px; -	background: url('../img/transp50.png'); -	/*-moz-border-radius: 5px;*/ -} - -.plupload_container input { -	border: 1px solid #DDD; -	font: normal 11px Verdana,sans-serif; -	width: 98%; -} - -.plupload_header {background: #2A2C2E url('../img/backgrounds.gif') repeat-x;} -.plupload_header_content { -	background: url('../img/backgrounds.gif') no-repeat 0 -317px; -	min-height: 56px; -	padding-left: 60px; -	color: #FFF; -} -.plupload_header_title { -	font: normal 18px sans-serif; -	padding: 6px 0 3px; -} -.plupload_header_text { -	font: normal 12px sans-serif; -} - -.plupload_filelist { -	margin: 0; -	padding: 0; -	list-style: none; -} - -.plupload_scroll .plupload_filelist { -	height: 185px; -	background: #F5F5F5; -	overflow-y: scroll; -} - -.plupload_filelist li { -	padding: 10px 8px; -	background: #F5F5F5 url('../img/backgrounds.gif') repeat-x 0 -156px; -	border-bottom: 1px solid #DDD; -} - -.plupload_filelist_header, .plupload_filelist_footer { -	background: #DFDFDF; -	padding: 8px 8px; -	color: #42454A; -} -.plupload_filelist_header { -	border-top: 1px solid #EEE; -	border-bottom: 1px solid #CDCDCD; -} - -.plupload_filelist_footer {border-top: 1px solid #FFF; height: 22px; line-height: 20px; vertical-align: middle;} -.plupload_file_name {float: left; overflow: hidden} -.plupload_file_status {color: #777;} -.plupload_file_status span {color: #42454A;} -.plupload_file_size, .plupload_file_status, .plupload_progress { -	float: right; -	width: 80px; -} -.plupload_file_size, .plupload_file_status, .plupload_file_action {text-align: right;} - -.plupload_filelist .plupload_file_name {width: 205px} - -.plupload_file_action { -	float: right; -	width: 16px; -	height: 16px; -	margin-left: 15px; -} - -.plupload_file_action * { -	display: none; -	width: 16px; -	height: 16px; -} - -li.plupload_uploading {background: #ECF3DC url('../img/backgrounds.gif') repeat-x 0 -238px;} -li.plupload_done {color:#AAA} - -li.plupload_delete a { -	background: url('../img/delete.gif'); -} - -li.plupload_failed a { -	background: url('../img/error.gif'); -	cursor: default; -} - -li.plupload_done a { -	background: url('../img/done.gif'); -	cursor: default; -} - -.plupload_progress, .plupload_upload_status { -	display: none; -} - -.plupload_progress_container { -	margin-top: 3px; -	border: 1px solid #CCC; -	background: #FFF; -	padding: 1px; -} -.plupload_progress_bar { -	width: 0px; -	height: 7px; -	background: #CDEB8B; -} - -.plupload_scroll .plupload_filelist_header .plupload_file_action, .plupload_scroll .plupload_filelist_footer .plupload_file_action { -	margin-right: 17px; -} - -/* Floats */ - -.plupload_clear,.plupload_clearer {clear: both;} -.plupload_clearer, .plupload_progress_bar { -	display: block; -	font-size: 0; -	line-height: 0; -} - -li.plupload_droptext { -	background: transparent; -	text-align: center; -	vertical-align: middle; -	border: 0; -	line-height: 165px; -} diff --git a/phpBB/assets/plupload/jquery.plupload.queue/img/backgrounds.gif b/phpBB/assets/plupload/jquery.plupload.queue/img/backgrounds.gif Binary files differdeleted file mode 100644 index 39e33ebc02..0000000000 --- a/phpBB/assets/plupload/jquery.plupload.queue/img/backgrounds.gif +++ /dev/null diff --git a/phpBB/assets/plupload/jquery.plupload.queue/img/buttons-disabled.png b/phpBB/assets/plupload/jquery.plupload.queue/img/buttons-disabled.png Binary files differdeleted file mode 100644 index afa11af9b9..0000000000 --- a/phpBB/assets/plupload/jquery.plupload.queue/img/buttons-disabled.png +++ /dev/null diff --git a/phpBB/assets/plupload/jquery.plupload.queue/img/buttons.png b/phpBB/assets/plupload/jquery.plupload.queue/img/buttons.png Binary files differdeleted file mode 100644 index 153e73885a..0000000000 --- a/phpBB/assets/plupload/jquery.plupload.queue/img/buttons.png +++ /dev/null diff --git a/phpBB/assets/plupload/jquery.plupload.queue/img/delete.gif b/phpBB/assets/plupload/jquery.plupload.queue/img/delete.gif Binary files differdeleted file mode 100644 index 78ca8b3b49..0000000000 --- a/phpBB/assets/plupload/jquery.plupload.queue/img/delete.gif +++ /dev/null diff --git a/phpBB/assets/plupload/jquery.plupload.queue/img/transp50.png b/phpBB/assets/plupload/jquery.plupload.queue/img/transp50.png Binary files differdeleted file mode 100644 index eb0efe104b..0000000000 --- a/phpBB/assets/plupload/jquery.plupload.queue/img/transp50.png +++ /dev/null diff --git a/phpBB/assets/plupload/jquery.plupload.queue/jquery.plupload.queue.min.js b/phpBB/assets/plupload/jquery.plupload.queue/jquery.plupload.queue.min.js deleted file mode 100644 index c20be880bc..0000000000 --- a/phpBB/assets/plupload/jquery.plupload.queue/jquery.plupload.queue.min.js +++ /dev/null @@ -1 +0,0 @@ -;(function(e){function n(e){return plupload.translate(e)||e}function r(t,r){r.contents().each(function(t,n){n=e(n),n.is(".plupload")||n.remove()}),r.prepend('<div class="plupload_wrapper plupload_scroll"><div id="'+t+'_container" class="plupload_container">'+'<div class="plupload">'+'<div class="plupload_header">'+'<div class="plupload_header_content">'+'<div class="plupload_header_title">'+n("Select files")+"</div>"+'<div class="plupload_header_text">'+n("Add files to the upload queue and click the start button.")+"</div>"+"</div>"+"</div>"+'<div class="plupload_content">'+'<div class="plupload_filelist_header">'+'<div class="plupload_file_name">'+n("Filename")+"</div>"+'<div class="plupload_file_action"> </div>'+'<div class="plupload_file_status"><span>'+n("Status")+"</span></div>"+'<div class="plupload_file_size">'+n("Size")+"</div>"+'<div class="plupload_clearer"> </div>'+"</div>"+'<ul id="'+t+'_filelist" class="plupload_filelist"></ul>'+'<div class="plupload_filelist_footer">'+'<div class="plupload_file_name">'+'<div class="plupload_buttons">'+'<a href="#" class="plupload_button plupload_add" id="'+t+'_browse">'+n("Add Files")+"</a>"+'<a href="#" class="plupload_button plupload_start">'+n("Start Upload")+"</a>"+"</div>"+'<span class="plupload_upload_status"></span>'+"</div>"+'<div class="plupload_file_action"></div>'+'<div class="plupload_file_status"><span class="plupload_total_status">0%</span></div>'+'<div class="plupload_file_size"><span class="plupload_total_file_size">0 b</span></div>'+'<div class="plupload_progress">'+'<div class="plupload_progress_container">'+'<div class="plupload_progress_bar"></div>'+"</div>"+"</div>"+'<div class="plupload_clearer"> </div>'+"</div>"+"</div>"+"</div>"+"</div>"+'<input type="hidden" id="'+t+'_count" name="'+t+'_count" value="0" />'+"</div>")}var t={};e.fn.pluploadQueue=function(i){return i?(this.each(function(){function f(t){var n;t.status==plupload.DONE&&(n="plupload_done"),t.status==plupload.FAILED&&(n="plupload_failed"),t.status==plupload.QUEUED&&(n="plupload_delete"),t.status==plupload.UPLOADING&&(n="plupload_uploading");var r=e("#"+t.id).attr("class",n).find("a").css("display","block");t.hint&&r.attr("title",t.hint)}function l(){e("span.plupload_total_status",o).html(s.total.percent+"%"),e("div.plupload_progress_bar",o).css("width",s.total.percent+"%"),e("span.plupload_upload_status",o).html(n("Uploaded %d/%d files").replace(/%d\/%d/,s.total.uploaded+"/"+s.files.length))}function c(){var t=e("ul.plupload_filelist",o).html(""),r=0,i;e.each(s.files,function(n,o){i="",o.status==plupload.DONE&&(o.target_name&&(i+='<input type="hidden" name="'+u+"_"+r+'_tmpname" value="'+plupload.xmlEncode(o.target_name)+'" />'),i+='<input type="hidden" name="'+u+"_"+r+'_name" value="'+plupload.xmlEncode(o.name)+'" />',i+='<input type="hidden" name="'+u+"_"+r+'_status" value="'+(o.status==plupload.DONE?"done":"failed")+'" />',r++,e("#"+u+"_count").val(r)),t.append('<li id="'+o.id+'">'+'<div class="plupload_file_name"><span>'+o.name+"</span></div>"+'<div class="plupload_file_action"><a href="#"></a></div>'+'<div class="plupload_file_status">'+o.percent+"%</div>"+'<div class="plupload_file_size">'+plupload.formatSize(o.size)+"</div>"+'<div class="plupload_clearer"> </div>'+i+"</li>"),f(o),e("#"+o.id+".plupload_delete a").click(function(t){e("#"+o.id).remove(),s.removeFile(o),t.preventDefault()})}),e("span.plupload_total_file_size",o).html(plupload.formatSize(s.total.size)),s.total.queued===0?e("span.plupload_add_text",o).html(n("Add Files")):e("span.plupload_add_text",o).html(n("%d files queued").replace(/%d/,s.total.queued)),e("a.plupload_start",o).toggleClass("plupload_disabled",s.files.length==s.total.uploaded+s.total.failed),t[0].scrollTop=t[0].scrollHeight,l(),!s.files.length&&s.features.dragdrop&&s.settings.dragdrop&&e("#"+u+"_filelist").append('<li class="plupload_droptext">'+n("Drag files here.")+"</li>")}function h(){delete t[u],s.destroy(),o.html(a),s=o=a=null}var s,o,u,a;o=e(this),u=o.attr("id"),u||(u=plupload.guid(),o.attr("id",u)),a=o.html(),r(u,o),s=new plupload.Uploader(e.extend({dragdrop:!0,browse_button:u+"_browse",container:u},i)),t[u]=s,s.bind("UploadFile",function(t,n){e("#"+n.id).addClass("plupload_current_file")}),s.bind("Init",function(t,n){!i.unique_names&&i.rename&&o.on("click","#"+u+"_filelist div.plupload_file_name span",function(n){var r=e(n.target),i,s,o,u="";i=t.getFile(r.parents("li")[0].id),o=i.name,s=/^(.+)(\.[^.]+)$/.exec(o),s&&(o=s[1],u=s[2]),r.hide().after('<input type="text" />'),r.next().val(o).focus().blur(function(){r.show().next().remove()}).keydown(function(t){var n=e(this);t.keyCode==13&&(t.preventDefault(),i.name=n.val()+u,r.html(i.name),n.blur())})}),t.settings.dragdrop&&(t.settings.drop_element=u+"_filelist"),e("#"+u+"_container").attr("title","Using runtime: "+n.runtime),e("a.plupload_start",o).click(function(t){e(this).hasClass("plupload_disabled")||s.start(),t.preventDefault()}),e("a.plupload_stop",o).click(function(e){e.preventDefault(),s.stop()}),e("a.plupload_start",o).addClass("plupload_disabled")}),s.bind("Error",function(t,r){var i=r.file,s;i&&(s=r.message,r.details&&(s+=" ("+r.details+")"),r.code==plupload.FILE_SIZE_ERROR&&alert(n("Error: File too large:")+" "+i.name),r.code==plupload.FILE_EXTENSION_ERROR&&alert(n("Error: Invalid file extension:")+" "+i.name),i.hint=s,e("#"+i.id).attr("class","plupload_failed").find("a").css("display","block").attr("title",s)),r.code===plupload.INIT_ERROR&&setTimeout(function(){h()},1)}),s.bind("PostInit",function(t){t.settings.dragdrop&&t.features.dragdrop&&e("#"+u+"_filelist").append('<li class="plupload_droptext">'+n("Drag files here.")+"</li>")}),s.init(),s.bind("StateChanged",function(){s.state===plupload.STARTED?(e("li.plupload_delete a,div.plupload_buttons",o).hide(),e("span.plupload_upload_status,div.plupload_progress,a.plupload_stop",o).css("display","block"),e("span.plupload_upload_status",o).html("Uploaded "+s.total.uploaded+"/"+s.files.length+" files"),i.multiple_queues&&e("span.plupload_total_status,span.plupload_total_file_size",o).show()):(c(),e("a.plupload_stop,div.plupload_progress",o).hide(),e("a.plupload_delete",o).css("display","block"),i.multiple_queues&&s.total.uploaded+s.total.failed==s.files.length&&(e(".plupload_buttons,.plupload_upload_status",o).css("display","inline"),e(".plupload_start",o).addClass("plupload_disabled"),e("span.plupload_total_status,span.plupload_total_file_size",o).hide()))}),s.bind("QueueChanged",c),s.bind("FileUploaded",function(e,t){f(t)}),s.bind("UploadProgress",function(t,n){e("#"+n.id+" div.plupload_file_status",o).html(n.percent+"%"),f(n),l()}),i.setup&&i.setup(s)}),this):t[e(this[0]).attr("id")]}})(jQuery);
\ No newline at end of file diff --git a/phpBB/assets/plupload/plupload.full.min.js b/phpBB/assets/plupload/plupload.full.min.js index 12e72ccfcb..69d6ad120c 100644 --- a/phpBB/assets/plupload/plupload.full.min.js +++ b/phpBB/assets/plupload/plupload.full.min.js @@ -1,6 +1,6 @@  /**   * mOxie - multi-runtime File API & XMLHttpRequest L2 Polyfill - * v1.0.0 + * v1.2.0   *   * Copyright 2013, Moxiecode Systems AB   * Released under GPL License. @@ -8,14 +8,14 @@   * License: http://www.plupload.com/license   * Contributing: http://www.plupload.com/contributing   * - * Date: 2013-09-23 + * Date: 2014-01-16   */ -!function(e,t){"use strict";function n(e,t){for(var n,i=[],r=0;r<e.length;++r){if(n=s[e[r]]||o(e[r]),!n)throw"module definition dependecy not found: "+e[r];i.push(n)}t.apply(null,i)}function i(e,i,r){if("string"!=typeof e)throw"invalid module definition, module id must be defined and be a string";if(i===t)throw"invalid module definition, dependencies must be specified";if(r===t)throw"invalid module definition, definition function must be specified";n(i,function(){s[e]=r.apply(null,arguments)})}function r(e){return!!s[e]}function o(t){for(var n=e,i=t.split(/[.\/]/),r=0;r<i.length;++r){if(!n[i[r]])return;n=n[i[r]]}return n}function a(n){for(var i=0;i<n.length;i++){for(var r=e,o=n[i],a=o.split(/[.\/]/),u=0;u<a.length-1;++u)r[a[u]]===t&&(r[a[u]]={}),r=r[a[u]];r[a[a.length-1]]=s[o]}}var s={},u="moxie/core/utils/Basic",c="moxie/core/I18n",l="moxie/core/utils/Mime",d="moxie/core/utils/Env",f="moxie/core/utils/Dom",p="moxie/core/Exceptions",h="moxie/core/EventTarget",m="moxie/core/utils/Encode",g="moxie/runtime/Runtime",v="moxie/runtime/RuntimeClient",y="moxie/file/Blob",E="moxie/file/File",_="moxie/file/FileInput",R="moxie/file/FileDrop",w="moxie/runtime/RuntimeTarget",x="moxie/file/FileReader",b="moxie/core/utils/Url",A="moxie/file/FileReaderSync",T="moxie/xhr/FormData",S="moxie/xhr/XMLHttpRequest",O="moxie/runtime/Transporter",I="moxie/core/JSON",D="moxie/image/Image",L="moxie/runtime/html5/Runtime",N="moxie/runtime/html5/file/Blob",M="moxie/core/utils/Events",C="moxie/runtime/html5/file/FileInput",F="moxie/runtime/html5/file/FileDrop",P="moxie/runtime/html5/file/FileReader",H="moxie/runtime/html5/xhr/XMLHttpRequest",B="moxie/runtime/html5/utils/BinaryReader",U="moxie/runtime/html5/image/JPEGHeaders",G="moxie/runtime/html5/image/ExifParser",z="moxie/runtime/html5/image/JPEG",q="moxie/runtime/html5/image/PNG",k="moxie/runtime/html5/image/ImageInfo",X="moxie/runtime/html5/image/MegaPixel",j="moxie/runtime/html5/image/Image",V="moxie/runtime/flash/Runtime",W="moxie/runtime/flash/file/Blob",Y="moxie/runtime/flash/file/FileInput",$="moxie/runtime/flash/file/FileReader",K="moxie/runtime/flash/file/FileReaderSync",Z="moxie/runtime/flash/xhr/XMLHttpRequest",J="moxie/runtime/flash/runtime/Transporter",Q="moxie/runtime/flash/image/Image",et="moxie/runtime/silverlight/Runtime",tt="moxie/runtime/silverlight/file/Blob",nt="moxie/runtime/silverlight/file/FileInput",it="moxie/runtime/silverlight/file/FileDrop",rt="moxie/runtime/silverlight/file/FileReader",ot="moxie/runtime/silverlight/file/FileReaderSync",at="moxie/runtime/silverlight/xhr/XMLHttpRequest",st="moxie/runtime/silverlight/runtime/Transporter",ut="moxie/runtime/silverlight/image/Image",ct="moxie/runtime/html4/Runtime",lt="moxie/runtime/html4/file/FileInput",dt="moxie/runtime/html4/file/FileReader",ft="moxie/runtime/html4/xhr/XMLHttpRequest",pt="moxie/runtime/html4/image/Image";i(u,[],function(){var e=function(e){var t;return e===t?"undefined":null===e?"null":e.nodeType?"node":{}.toString.call(e).match(/\s([a-z|A-Z]+)/)[1].toLowerCase()},t=function(i){var r;return n(arguments,function(a,s){s>0&&n(a,function(n,a){n!==r&&(e(i[a])===e(n)&&~o(e(n),["array","object"])?t(i[a],n):i[a]=n)})}),i},n=function(e,t){var n,i,r,o;if(e){try{n=e.length}catch(a){n=o}if(n===o){for(i in e)if(e.hasOwnProperty(i)&&t(e[i],i)===!1)return}else for(r=0;n>r;r++)if(t(e[r],r)===!1)return}},i=function(t){var n;if(!t||"object"!==e(t))return!0;for(n in t)return!1;return!0},r=function(t,n){function i(r){"function"===e(t[r])&&t[r](function(e){++r<o&&!e?i(r):n(e)})}var r=0,o=t.length;"function"!==e(n)&&(n=function(){}),t&&t.length||n(),i(r)},o=function(e,t){if(t){if(Array.prototype.indexOf)return Array.prototype.indexOf.call(t,e);for(var n=0,i=t.length;i>n;n++)if(t[n]===e)return n}return-1},a=function(t,n){var i=[];"array"!==e(t)&&(t=[t]),"array"!==e(n)&&(n=[n]);for(var r in t)-1===o(t[r],n)&&i.push(t[r]);return i.length?i:!1},s=function(e,t){var i=[];return n(e,function(e){-1!==o(e,t)&&i.push(e)}),i.length?i:null},u=function(e){var t,n=[];for(t=0;t<e.length;t++)n[t]=e[t];return n},c=function(){var e=0;return function(t){var n=(new Date).getTime().toString(32),i;for(i=0;5>i;i++)n+=Math.floor(65535*Math.random()).toString(32);return(t||"o_")+n+(e++).toString(32)}}(),l=function(e){return e?String.prototype.trim?String.prototype.trim.call(e):e.toString().replace(/^\s*/,"").replace(/\s*$/,""):e},d=function(e){if("string"!=typeof e)return e;var t={t:1099511627776,g:1073741824,m:1048576,k:1024},n;return e=/^([0-9]+)([mgk]?)$/.exec(e.toLowerCase().replace(/[^0-9mkg]/g,"")),n=e[2],e=+e[1],t.hasOwnProperty(n)&&(e*=t[n]),e};return{guid:c,typeOf:e,extend:t,each:n,isEmptyObj:i,inSeries:r,inArray:o,arrayDiff:a,arrayIntersect:s,toArray:u,trim:l,parseSizeStr:d}}),i(c,[u],function(e){var t={};return{addI18n:function(n){return e.extend(t,n)},translate:function(e){return t[e]||e},_:function(e){return this.translate(e)},sprintf:function(t){var n=[].slice.call(arguments,1),i="";return e.each(t.split(/%[a-z]/),function(e){i+=e,n.length&&(i+=n.shift())}),i}}}),i(l,[u,c],function(e,t){var n="application/msword,doc dot,application/pdf,pdf,application/pgp-signature,pgp,application/postscript,ps ai eps,application/rtf,rtf,application/vnd.ms-excel,xls xlb,application/vnd.ms-powerpoint,ppt pps pot,application/zip,zip,application/x-shockwave-flash,swf swfl,application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx,application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx,application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx,application/vnd.openxmlformats-officedocument.presentationml.template,potx,application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx,application/x-javascript,js,application/json,json,audio/mpeg,mp3 mpga mpega mp2,audio/x-wav,wav,audio/mp4,m4a,audio/ogg,oga ogg,audio/aiff,aiff aif,audio/flac,flac,audio/aac,aac,audio/ac3,ac3,audio/x-ms-wma,wma,image/bmp,bmp,image/gif,gif,image/jpeg,jpg jpeg jpe,image/photoshop,psd,image/png,png,image/svg+xml,svg svgz,image/tiff,tiff tif,text/plain,asc txt text diff log,text/html,htm html xhtml,text/css,css,text/csv,csv,text/rtf,rtf,video/mpeg,mpeg mpg mpe m2v,video/quicktime,qt mov,video/mp4,mp4,video/x-m4v,m4v,video/x-flv,flv,video/x-ms-wmv,wmv,video/avi,avi,video/webm,webm,video/3gpp,3gpp 3gp,video/3gpp2,3g2,video/vnd.rn-realvideo,rv,video/ogg,ogv,video/x-matroska,mkv,application/vnd.oasis.opendocument.formula-template,otf,application/octet-stream,exe",i={mimes:{},extensions:{},addMimeType:function(e){var t=e.split(/,/),n,i,r;for(n=0;n<t.length;n+=2){for(r=t[n+1].split(/ /),i=0;i<r.length;i++)this.mimes[r[i]]=t[n];this.extensions[t[n]]=r}},extList2mimes:function(t,n){var i=this,r,o,a,s,u=[];for(o=0;o<t.length;o++)for(r=t[o].extensions.split(/\s*,\s*/),a=0;a<r.length;a++){if("*"===r[a])return[];if(s=i.mimes[r[a]])-1===e.inArray(s,u)&&u.push(s);else{if(!n||!/^\w+$/.test(r[a]))return[];u.push("."+r[a])}}return u},mimes2exts:function(t){var n=this,i=[];return e.each(t,function(t){if("*"===t)return i=[],!1;var r=t.match(/^(\w+)\/(\*|\w+)$/);r&&("*"===r[2]?e.each(n.extensions,function(e,t){new RegExp("^"+r[1]+"/").test(t)&&[].push.apply(i,n.extensions[t])}):n.extensions[t]&&[].push.apply(i,n.extensions[t]))}),i},mimes2extList:function(n){var i=[],r=[];return"string"===e.typeOf(n)&&(n=e.trim(n).split(/\s*,\s*/)),r=this.mimes2exts(n),i.push({title:t.translate("Files"),extensions:r.length?r.join(","):"*"}),i.mimes=n,i},getFileExtension:function(e){var t=e&&e.match(/\.([^.]+)$/);return t?t[1].toLowerCase():""},getFileMime:function(e){return this.mimes[this.getFileExtension(e)]||""}};return i.addMimeType(n),i}),i(d,[u],function(e){function t(e){for(var t,n,i=0;i<e.length;i++)if(t=e[i].s1,n=e[i].prop,o=e[i].sv||e[i].id,t){if(-1!=t.indexOf(e[i].s2))return e[i].id}else if(n)return e[i].id}function n(e){var t=e.indexOf(o);if(-1!=t)return parseFloat(e.substring(t+o.length+1))}var i=[{s1:navigator.userAgent,s2:"Android",id:"Android Browser",sv:"Version"},{s1:navigator.userAgent,s2:"Chrome",id:"Chrome"},{s1:navigator.vendor,s2:"Apple",id:"Safari",sv:"Version"},{prop:window.opera&&window.opera.buildNumber,id:"Opera",sv:"Version"},{s1:navigator.vendor,s2:"KDE",id:"Konqueror"},{s1:navigator.userAgent,s2:"Firefox",id:"Firefox"},{s1:navigator.vendor,s2:"Camino",id:"Camino"},{s1:navigator.userAgent,s2:"Netscape",id:"Netscape"},{s1:navigator.userAgent,s2:"MSIE",id:"IE",sv:"MSIE"},{s1:navigator.userAgent,s2:"Gecko",id:"Mozilla",sv:"rv"}],r=[{s1:navigator.platform,s2:"Win",id:"Windows"},{s1:navigator.platform,s2:"Mac",id:"Mac"},{s1:navigator.userAgent,s2:"iPhone",id:"iOS"},{s1:navigator.userAgent,s2:"iPad",id:"iOS"},{s1:navigator.userAgent,s2:"Android",id:"Android"},{s1:navigator.platform,s2:"Linux",id:"Linux"}],o,a=function(){var t={define_property:function(){return!1}(),create_canvas:function(){var e=document.createElement("canvas");return!(!e.getContext||!e.getContext("2d"))}(),return_response_type:function(t){try{if(-1!==e.inArray(t,["","text","document"]))return!0;if(window.XMLHttpRequest){var n=new XMLHttpRequest;if(n.open("get","/"),"responseType"in n)return n.responseType=t,n.responseType!==t?!1:!0}}catch(i){}return!1},use_data_uri:function(){var e=new Image;return e.onload=function(){t.use_data_uri=1===e.width&&1===e.height},setTimeout(function(){e.src="data:image/gif;base64,R0lGODlhAQABAIAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="},1),!1}(),use_data_uri_over32kb:function(){return t.use_data_uri&&("IE"!==s.browser||s.version>=9)},use_data_uri_of:function(e){return t.use_data_uri&&33e3>e||t.use_data_uri_over32kb()},use_fileinput:function(){var e=document.createElement("input");return e.setAttribute("type","file"),!e.disabled}};return function(n){var i=[].slice.call(arguments);return i.shift(),"function"===e.typeOf(t[n])?t[n].apply(this,i):!!t[n]}}(),s={can:a,browser:t(i),version:n(navigator.userAgent)||n(navigator.appVersion),OS:t(r),swf_url:"../flash/Moxie.swf",xap_url:"../silverlight/Moxie.xap",global_event_dispatcher:"moxie.core.EventTarget.instance.dispatchEvent"};return s}),i(f,[d],function(e){var t=function(e){return"string"!=typeof e?e:document.getElementById(e)},n=function(e,t){var n;return""===e.className?!1:(n=new RegExp("(^|\\s+)"+t+"(\\s+|$)"),n.test(e.className))},i=function(e,t){n(e,t)||(e.className=""===e.className?t:e.className.replace(/\s+$/,"")+" "+t)},r=function(e,t){var n=new RegExp("(^|\\s+)"+t+"(\\s+|$)");e.className=e.className.replace(n,function(e,t,n){return" "===t&&" "===n?" ":""})},o=function(e,t){return e.currentStyle?e.currentStyle[t]:window.getComputedStyle?window.getComputedStyle(e,null)[t]:void 0},a=function(t,n){function i(e){var t,n,i=0,r=0;return e&&(n=e.getBoundingClientRect(),t="CSS1Compat"===s.compatMode?s.documentElement:s.body,i=n.left+t.scrollLeft,r=n.top+t.scrollTop),{x:i,y:r}}var r=0,o=0,a,s=document,u,c;if(t=t,n=n||s.body,t&&t.getBoundingClientRect&&"IE"===e.browser&&(!s.documentMode||s.documentMode<8))return u=i(t),c=i(n),{x:u.x-c.x,y:u.y-c.y};for(a=t;a&&a!=n&&a.nodeType;)r+=a.offsetLeft||0,o+=a.offsetTop||0,a=a.offsetParent;for(a=t.parentNode;a&&a!=n&&a.nodeType;)r-=a.scrollLeft||0,o-=a.scrollTop||0,a=a.parentNode;return{x:r,y:o}},s=function(e){return{w:e.offsetWidth||e.clientWidth,h:e.offsetHeight||e.clientHeight}};return{get:t,hasClass:n,addClass:i,removeClass:r,getStyle:o,getPos:a,getSize:s}}),i(p,[u],function(e){function t(e,t){var n;for(n in e)if(e[n]===t)return n;return null}return{RuntimeError:function(){function n(e){this.code=e,this.name=t(i,e),this.message=this.name+": RuntimeError "+this.code}var i={NOT_INIT_ERR:1,NOT_SUPPORTED_ERR:9,JS_ERR:4};return e.extend(n,i),n.prototype=Error.prototype,n}(),OperationNotAllowedException:function(){function t(e){this.code=e,this.name="OperationNotAllowedException"}return e.extend(t,{NOT_ALLOWED_ERR:1}),t.prototype=Error.prototype,t}(),ImageError:function(){function n(e){this.code=e,this.name=t(i,e),this.message=this.name+": ImageError "+this.code}var i={WRONG_FORMAT:1,MAX_RESOLUTION_ERR:2};return e.extend(n,i),n.prototype=Error.prototype,n}(),FileException:function(){function n(e){this.code=e,this.name=t(i,e),this.message=this.name+": FileException "+this.code}var i={NOT_FOUND_ERR:1,SECURITY_ERR:2,ABORT_ERR:3,NOT_READABLE_ERR:4,ENCODING_ERR:5,NO_MODIFICATION_ALLOWED_ERR:6,INVALID_STATE_ERR:7,SYNTAX_ERR:8};return e.extend(n,i),n.prototype=Error.prototype,n}(),DOMException:function(){function n(e){this.code=e,this.name=t(i,e),this.message=this.name+": DOMException "+this.code}var i={INDEX_SIZE_ERR:1,DOMSTRING_SIZE_ERR:2,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,INVALID_CHARACTER_ERR:5,NO_DATA_ALLOWED_ERR:6,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INUSE_ATTRIBUTE_ERR:10,INVALID_STATE_ERR:11,SYNTAX_ERR:12,INVALID_MODIFICATION_ERR:13,NAMESPACE_ERR:14,INVALID_ACCESS_ERR:15,VALIDATION_ERR:16,TYPE_MISMATCH_ERR:17,SECURITY_ERR:18,NETWORK_ERR:19,ABORT_ERR:20,URL_MISMATCH_ERR:21,QUOTA_EXCEEDED_ERR:22,TIMEOUT_ERR:23,INVALID_NODE_TYPE_ERR:24,DATA_CLONE_ERR:25};return e.extend(n,i),n.prototype=Error.prototype,n}(),EventException:function(){function t(e){this.code=e,this.name="EventException"}return e.extend(t,{UNSPECIFIED_EVENT_TYPE_ERR:0}),t.prototype=Error.prototype,t}()}}),i(h,[p,u],function(e,t){function n(){var n={};t.extend(this,{uid:null,init:function(){this.uid||(this.uid=t.guid("uid_"))},addEventListener:function(e,i,r,o){var a=this,s;return e=t.trim(e),/\s/.test(e)?(t.each(e.split(/\s+/),function(e){a.addEventListener(e,i,r,o)}),void 0):(e=e.toLowerCase(),r=parseInt(r,10)||0,s=n[this.uid]&&n[this.uid][e]||[],s.push({fn:i,priority:r,scope:o||this}),n[this.uid]||(n[this.uid]={}),n[this.uid][e]=s,void 0)},hasEventListener:function(e){return e?!(!n[this.uid]||!n[this.uid][e]):!!n[this.uid]},removeEventListener:function(e,i){e=e.toLowerCase();var r=n[this.uid]&&n[this.uid][e],o;if(r){if(i){for(o=r.length-1;o>=0;o--)if(r[o].fn===i){r.splice(o,1);break}}else r=[];r.length||(delete n[this.uid][e],t.isEmptyObj(n[this.uid])&&delete n[this.uid])}},removeAllEventListeners:function(){n[this.uid]&&delete n[this.uid]},dispatchEvent:function(i){var r,o,a,s,u={};if("string"!==t.typeOf(i)){if(s=i,"string"!==t.typeOf(s.type))throw new e.EventException(e.EventException.UNSPECIFIED_EVENT_TYPE_ERR);i=s.type,s.total&&s.loaded&&(u.total=s.total,u.loaded=s.loaded),u.async=s.async||!1}if(-1!==i.indexOf("::")?function(e){r=e[0],i=e[1]}(i.split("::")):r=this.uid,i=i.toLowerCase(),o=n[r]&&n[r][i]){o.sort(function(e,t){return t.priority-e.priority}),a=[].slice.call(arguments),a.shift(),u.type=i,a.unshift(u);var c=[];t.each(o,function(e){a[0].target=e.scope,u.async?c.push(function(t){setTimeout(function(){t(e.fn.apply(e.scope,a)===!1)},1)}):c.push(function(t){t(e.fn.apply(e.scope,a)===!1)})}),c.length&&t.inSeries(c)}return!0},bind:function(){this.addEventListener.apply(this,arguments)},unbind:function(){this.removeEventListener.apply(this,arguments)},unbindAll:function(){this.removeAllEventListeners.apply(this,arguments)},trigger:function(){this.dispatchEvent.apply(this,arguments)},convertEventPropsToHandlers:function(e){var n;"array"!==t.typeOf(e)&&(e=[e]);for(var i=0;i<e.length;i++)n="on"+e[i],"function"===t.typeOf(this[n])?this.addEventListener(e[i],this[n]):"undefined"===t.typeOf(this[n])&&(this[n]=null)}})}return n.instance=new n,n}),i(m,[],function(){var e=function(e){return unescape(encodeURIComponent(e))},t=function(e){return decodeURIComponent(escape(e))},n=function(e,n){if("function"==typeof window.atob)return n?t(window.atob(e)):window.atob(e);var i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r,o,a,s,u,c,l,d,f=0,p=0,h="",m=[];if(!e)return e;e+="";do s=i.indexOf(e.charAt(f++)),u=i.indexOf(e.charAt(f++)),c=i.indexOf(e.charAt(f++)),l=i.indexOf(e.charAt(f++)),d=s<<18|u<<12|c<<6|l,r=255&d>>16,o=255&d>>8,a=255&d,m[p++]=64==c?String.fromCharCode(r):64==l?String.fromCharCode(r,o):String.fromCharCode(r,o,a);while(f<e.length);return h=m.join(""),n?t(h):h},i=function(t,n){if(n&&e(t),"function"==typeof window.btoa)return window.btoa(t);var i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r,o,a,s,u,c,l,d,f=0,p=0,h="",m=[];if(!t)return t;do r=t.charCodeAt(f++),o=t.charCodeAt(f++),a=t.charCodeAt(f++),d=r<<16|o<<8|a,s=63&d>>18,u=63&d>>12,c=63&d>>6,l=63&d,m[p++]=i.charAt(s)+i.charAt(u)+i.charAt(c)+i.charAt(l);while(f<t.length);h=m.join("");var g=t.length%3;return(g?h.slice(0,g-3):h)+"===".slice(g||3)};return{utf8_encode:e,utf8_decode:t,atob:n,btoa:i}}),i(g,[u,f,h],function(e,t,n){function i(n,r,a,s,u){function c(t,i){var r=null,o=n&&n.required_caps;return i=i||"browser",null!==this.mode?this.mode:(o&&!e.isEmptyObj(t)&&(e.each(o,function(n,i){if(t.hasOwnProperty(i)){var o=t[i](n);if("string"==typeof o&&(o=[o]),r){if(!(r=e.arrayIntersect(r,o)))return r=!1}else r=o}}),r?this.mode=-1!==e.inArray(i,r)?i:r[0]:r===!1&&(this.mode=!1)),null===this.mode&&(this.mode=i),this.mode&&o&&!this.can(o)&&(this.mode=!1),void 0)}var l=this,d,f=e.guid(r+"_");o[f]=this,a=e.extend({access_binary:!1,access_image_binary:!1,display_media:!1,do_cors:!1,drag_and_drop:!1,filter_by_extension:!0,resize_image:!1,report_upload_progress:!1,return_response_headers:!1,return_response_type:!1,return_status_code:!0,send_custom_headers:!1,select_file:!1,select_folder:!1,select_multiple:!0,send_binary_string:!1,send_browser_cookies:!0,send_multipart:!0,slice_blob:!1,stream_upload:!1,summon_file_dialog:!1,upload_filesize:!0,use_http_method:!0},a),d=function(){var t={};return{exec:function(e,n,i,r){return d[n]&&(t[e]||(t[e]={context:this,instance:new d[n]}),t[e].instance[i])?t[e].instance[i].apply(this,r):void 0},removeInstance:function(e){delete t[e]},removeAllInstances:function(){var n=this;e.each(t,function(t,i){"function"===e.typeOf(t.instance.destroy)&&t.instance.destroy.call(t.context),n.removeInstance(i)})}}}(),e.extend(this,{initialized:!1,uid:f,type:r,mode:null,shimid:f+"_container",clients:0,options:n,can:function(t,n){var r=arguments[2]||a;if("string"===e.typeOf(t)&&"undefined"===e.typeOf(n)&&(t=i.parseCaps(t)),"object"===e.typeOf(t)){for(var o in t)if(!this.can(o,t[o],r))return!1;return!0}return"function"===e.typeOf(r[t])?r[t].call(this,n):n===r[t]},getShimContainer:function(){var n,i=t.get(this.shimid);return i||(n=this.options.container?t.get(this.options.container):document.body,i=document.createElement("div"),i.id=this.shimid,i.className="moxie-shim moxie-shim-"+this.type,e.extend(i.style,{position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),n.appendChild(i),n=null),i},getShim:function(){return d},shimExec:function(e,t){var n=[].slice.call(arguments,2);return l.getShim().exec.call(this,this.uid,e,t,n)},exec:function(e,t){var n=[].slice.call(arguments,2);return l[e]&&l[e][t]?l[e][t].apply(this,n):l.shimExec.apply(this,arguments)},destroy:function(){if(l){var e=t.get(this.shimid);e&&e.parentNode.removeChild(e),d&&d.removeAllInstances(),this.unbindAll(),delete o[this.uid],this.uid=null,f=l=d=e=null}}}),c.call(this,s,u)}var r={},o={};return i.order="html5,flash,silverlight,html4",i.getRuntime=function(e){return o[e]?o[e]:!1},i.addConstructor=function(e,t){t.prototype=n.instance,r[e]=t},i.getConstructor=function(e){return r[e]||null},i.getInfo=function(e){var t=i.getRuntime(e);return t?{uid:t.uid,type:t.type,can:function(){return t.can.apply(t,arguments)}}:null},i.parseCaps=function(t){var n={};return"string"!==e.typeOf(t)?t||{}:(e.each(t.split(","),function(e){n[e]=!0}),n)},i.can=function(e,t){var n,r=i.getConstructor(e),o;return r?(n=new r({required_caps:t}),o=n.mode,n.destroy(),!!o):!1},i.thatCan=function(e,t){var n=(t||i.order).split(/\s*,\s*/);for(var r in n)if(i.can(n[r],e))return n[r];return null},i.capTrue=function(){return!0},i.capFalse=function(){return!1},i.capTest=function(e){return function(){return!!e}},i}),i(v,[p,u,g],function(e,t,n){return function i(){var i;t.extend(this,{connectRuntime:function(r){function o(t){var s,u;return t.length?(s=t.shift(),(u=n.getConstructor(s))?(i=new u(r),i.bind("Init",function(){i.initialized=!0,setTimeout(function(){i.clients++,a.trigger("RuntimeInit",i)},1)}),i.bind("Error",function(){i.destroy(),o(t)}),i.mode?(i.init(),void 0):(i.trigger("Error"),void 0)):(o(t),void 0)):(a.trigger("RuntimeError",new e.RuntimeError(e.RuntimeError.NOT_INIT_ERR)),i=null,void 0)}var a=this,s;if("string"===t.typeOf(r)?s=r:"string"===t.typeOf(r.ruid)&&(s=r.ruid),s){if(i=n.getRuntime(s))return i.clients++,i;throw new e.RuntimeError(e.RuntimeError.NOT_INIT_ERR)}o((r.runtime_order||n.order).split(/\s*,\s*/))},getRuntime:function(){return i&&i.uid?i:(i=null,null)},disconnectRuntime:function(){i&&--i.clients<=0&&(i.destroy(),i=null)}})}}),i(y,[u,m,v],function(e,t,n){function i(o,a){function s(t,n,o){var a,s=r[this.uid];return"string"===e.typeOf(s)&&s.length?(a=new i(null,{type:o,size:n-t}),a.detach(s.substr(t,a.size)),a):null}n.call(this),o&&this.connectRuntime(o),a?"string"===e.typeOf(a)&&(a={data:a}):a={},e.extend(this,{uid:a.uid||e.guid("uid_"),ruid:o,size:a.size||0,type:a.type||"",slice:function(e,t,n){return this.isDetached()?s.apply(this,arguments):this.getRuntime().exec.call(this,"Blob","slice",this.getSource(),e,t,n)},getSource:function(){return r[this.uid]?r[this.uid]:null},detach:function(e){this.ruid&&(this.getRuntime().exec.call(this,"Blob","destroy",r[this.uid]),this.disconnectRuntime(),this.ruid=null),e=e||"";var n=e.match(/^data:([^;]*);base64,/);n&&(this.type=n[1],e=t.atob(e.substring(e.indexOf("base64,")+7))),this.size=e.length,r[this.uid]=e},isDetached:function(){return!this.ruid&&"string"===e.typeOf(r[this.uid])},destroy:function(){this.detach(),delete r[this.uid]}}),a.data?this.detach(a.data):r[this.uid]=a}var r={};return i}),i(E,[u,l,y],function(e,t,n){function i(i,r){var o,a;if(r||(r={}),a=r.type&&""!==r.type?r.type:t.getFileMime(r.name),r.name)o=r.name.replace(/\\/g,"/"),o=o.substr(o.lastIndexOf("/")+1);else{var s=a.split("/")[0];o=e.guid((""!==s?s:"file")+"_"),t.extensions[a]&&(o+="."+t.extensions[a][0])}n.apply(this,arguments),e.extend(this,{type:a||"",name:o||e.guid("file_"),lastModifiedDate:r.lastModifiedDate||(new Date).toLocaleString()})}return i.prototype=n.prototype,i}),i(_,[u,l,f,p,h,c,E,g,v],function(e,t,n,i,r,o,a,s,u){function c(r){var c=this,d,f,p;if(-1!==e.inArray(e.typeOf(r),["string","node"])&&(r={browse_button:r}),f=n.get(r.browse_button),!f)throw new i.DOMException(i.DOMException.NOT_FOUND_ERR);p={accept:[{title:o.translate("All Files"),extensions:"*"}],name:"file",multiple:!1,required_caps:!1,container:f.parentNode||document.body},r=e.extend({},p,r),"string"==typeof r.required_caps&&(r.required_caps=s.parseCaps(r.required_caps)),"string"==typeof r.accept&&(r.accept=t.mimes2extList(r.accept)),d=n.get(r.container),d||(d=document.body),"static"===n.getStyle(d,"position")&&(d.style.position="relative"),d=f=null,u.call(c),e.extend(c,{uid:e.guid("uid_"),ruid:null,files:null,init:function(){c.convertEventPropsToHandlers(l),c.bind("RuntimeInit",function(t,i){c.ruid=i.uid,c.bind("Ready",function(){c.trigger("Refresh")},999),c.bind("Change",function(){var t=i.exec.call(c,"FileInput","getFiles");c.files=[],e.each(t,function(e){return 0===e.size?!0:(c.files.push(new a(c.ruid,e)),void 0)})},999),c.bind("Refresh",function(){var t,o,a,s;a=n.get(r.browse_button),s=n.get(i.shimid),a&&(t=n.getPos(a,n.get(r.container)),o=n.getSize(a),s&&e.extend(s.style,{top:t.y+"px",left:t.x+"px",width:o.w+"px",height:o.h+"px"})),s=a=null}),i.exec.call(c,"FileInput","init",r)}),c.connectRuntime(e.extend({},r,{required_caps:{select_file:!0}}))},disable:function(t){var n=this.getRuntime();n&&n.exec.call(this,"FileInput","disable","undefined"===e.typeOf(t)?!0:t)},refresh:function(){c.trigger("Refresh")},destroy:function(){var t=this.getRuntime();t&&(t.exec.call(this,"FileInput","destroy"),this.disconnectRuntime()),"array"===e.typeOf(this.files)&&e.each(this.files,function(e){e.destroy()}),this.files=null}})}var l=["ready","change","cancel","mouseenter","mouseleave","mousedown","mouseup"];return c.prototype=r.instance,c}),i(R,[c,f,p,u,E,v,h,l],function(e,t,n,i,r,o,a,s){function u(n){var a=this,u;"string"==typeof n&&(n={drop_zone:n}),u={accept:[{title:e.translate("All Files"),extensions:"*"}],required_caps:{drag_and_drop:!0}},n="object"==typeof n?i.extend({},u,n):u,n.container=t.get(n.drop_zone)||document.body,"static"===t.getStyle(n.container,"position")&&(n.container.style.position="relative"),"string"==typeof n.accept&&(n.accept=s.mimes2extList(n.accept)),o.call(a),i.extend(a,{uid:i.guid("uid_"),ruid:null,files:null,init:function(){a.convertEventPropsToHandlers(c),a.bind("RuntimeInit",function(e,t){a.ruid=t.uid,a.bind("Drop",function(){var e=t.exec.call(a,"FileDrop","getFiles");a.files=[],i.each(e,function(e){a.files.push(new r(a.ruid,e))})},999),t.exec.call(a,"FileDrop","init",n),a.dispatchEvent("ready")}),a.connectRuntime(n)},destroy:function(){var e=this.getRuntime();e&&(e.exec.call(this,"FileDrop","destroy"),this.disconnectRuntime()),this.files=null}})}var c=["ready","dragenter","dragleave","drop","error"];return u.prototype=a.instance,u}),i(w,[u,v,h],function(e,t,n){function i(){this.uid=e.guid("uid_"),t.call(this),this.destroy=function(){this.disconnectRuntime(),this.unbindAll()}}return i.prototype=n.instance,i}),i(x,[u,m,p,h,y,E,w],function(e,t,n,i,r,o,a){function s(){function i(e,i){function l(e){o.readyState=s.DONE,o.error=e,o.trigger("error"),d()}function d(){c.destroy(),c=null,o.trigger("loadend")}function f(t){c.bind("Error",function(e,t){l(t)}),c.bind("Progress",function(e){o.result=t.exec.call(c,"FileReader","getResult"),o.trigger(e)}),c.bind("Load",function(e){o.readyState=s.DONE,o.result=t.exec.call(c,"FileReader","getResult"),o.trigger(e),d()}),t.exec.call(c,"FileReader","read",e,i)}if(c=new a,this.convertEventPropsToHandlers(u),this.readyState===s.LOADING)return l(new n.DOMException(n.DOMException.INVALID_STATE_ERR));if(this.readyState=s.LOADING,this.trigger("loadstart"),i instanceof r)if(i.isDetached()){var p=i.getSource();switch(e){case"readAsText":case"readAsBinaryString":this.result=p;break;case"readAsDataURL":this.result="data:"+i.type+";base64,"+t.btoa(p)}this.readyState=s.DONE,this.trigger("load"),d()}else f(c.connectRuntime(i.ruid));else l(new n.DOMException(n.DOMException.NOT_FOUND_ERR))}var o=this,c;e.extend(this,{uid:e.guid("uid_"),readyState:s.EMPTY,result:null,error:null,readAsBinaryString:function(e){i.call(this,"readAsBinaryString",e)},readAsDataURL:function(e){i.call(this,"readAsDataURL",e)},readAsText:function(e){i.call(this,"readAsText",e)},abort:function(){this.result=null,-1===e.inArray(this.readyState,[s.EMPTY,s.DONE])&&(this.readyState===s.LOADING&&(this.readyState=s.DONE),c&&c.getRuntime().exec.call(this,"FileReader","abort"),this.trigger("abort"),this.trigger("loadend"))},destroy:function(){this.abort(),c&&(c.getRuntime().exec.call(this,"FileReader","destroy"),c.disconnectRuntime()),o=c=null}})}var u=["loadstart","progress","load","abort","error","loadend"];return s.EMPTY=0,s.LOADING=1,s.DONE=2,s.prototype=i.instance,s}),i(b,[],function(){var e=function(e){for(var t=["source","scheme","authority","userInfo","user","pass","host","port","relative","path","directory","file","query","fragment"],n=t.length,i={http:80,https:443},r={},o=/^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/,a=o.exec(e||"");n--;)a[n]&&(r[t[n]]=a[n]);if(/^[^\/]/.test(r.path)&&!r.scheme){var s=document.location.pathname;/(\/|\/[^\.]+)$/.test(s)||(s=s.replace(/[^\/]+$/,"")),r.host=document.location.hostname,r.path=s+(r.path||"")}return r.scheme||(r.scheme=document.location.protocol.replace(/:$/,"")),r.host||(r.host=document.location.hostname),r.port||(r.port=document.location.port||i[r.scheme]||80),r.port=parseInt(r.port,10),r.path||(r.path="/"),delete r.source,r},t=function(t){var n={http:80,https:443},i=e(t);return i.scheme+"://"+i.host+(i.port!==n[i.scheme]?":"+i.port:"")+i.path+(i.query?i.query:"")},n=function(t){function n(e){return[e.scheme,e.host,e.port].join("/")}return"string"==typeof t&&(t=e(t)),n(e())===n(t)};return{parseUrl:e,resolveUrl:t,hasSameOrigin:n}}),i(A,[u,v,m],function(e,t,n){return function(){function i(e,t){if(!t.isDetached()){var i=this.connectRuntime(t.ruid).exec.call(this,"FileReaderSync","read",e,t);return this.disconnectRuntime(),i}var r=t.getSource();switch(e){case"readAsBinaryString":return r;case"readAsDataURL":return"data:"+t.type+";base64,"+n.btoa(r);case"readAsText":for(var o="",a=0,s=r.length;s>a;a++)o+=String.fromCharCode(r[a]);return o}}t.call(this),e.extend(this,{uid:e.guid("uid_"),readAsBinaryString:function(e){return i.call(this,"readAsBinaryString",e)},readAsDataURL:function(e){return i.call(this,"readAsDataURL",e)},readAsText:function(e){return i.call(this,"readAsText",e)}})}}),i(T,[p,u,y],function(e,t,n){function i(){var e,i={},r="";t.extend(this,{append:function(r,o){var a=this,s=t.typeOf(o);o instanceof n?(e&&delete i[e],e=r,i[r]=[o]):"array"===s?(r+="[]",t.each(o,function(e){a.append.call(a,r,e)})):"object"===s?t.each(o,function(e,t){a.append.call(a,r+"["+t+"]",e)}):(o=o.toString(),i[r]||(i[r]=[]),i[r].push(o))},hasBlob:function(){return!!e},getBlob:function(){return i[e]&&i[e][0]||null},getBlobName:function(){return e||null},each:function(e){t.each(i,function(n,i){t.each(n,function(t){e(t,i)})})},destroy:function(){e=null,r="",i={}}})}return i}),i(S,[u,p,h,m,b,g,w,y,A,T,d,l],function(e,t,n,i,r,o,a,s,u,c,l,d){function f(){this.uid=e.guid("uid_")}function p(){function n(e,t){return y.hasOwnProperty(e)?1===arguments.length?l.can("define_property")?y[e]:v[e]:(l.can("define_property")?y[e]=t:v[e]=t,void 0):void 0}function u(t){function i(){B.destroy(),B=null,s.dispatchEvent("loadend"),s=null}function r(r){B.bind("LoadStart",function(e){n("readyState",p.LOADING),s.dispatchEvent("readystatechange"),s.dispatchEvent(e),I&&s.upload.dispatchEvent(e)}),B.bind("Progress",function(e){n("readyState")!==p.LOADING&&(n("readyState",p.LOADING),s.dispatchEvent("readystatechange")),s.dispatchEvent(e)}),B.bind("UploadProgress",function(e){I&&s.upload.dispatchEvent({type:"progress",lengthComputable:!1,total:e.total,loaded:e.loaded})}),B.bind("Load",function(t){n("readyState",p.DONE),n("status",Number(r.exec.call(B,"XMLHttpRequest","getStatus")||0)),n("statusText",h[n("status")]||""),n("response",r.exec.call(B,"XMLHttpRequest","getResponse",n("responseType"))),~e.inArray(n("responseType"),["text",""])?n("responseText",n("response")):"document"===n("responseType")&&n("responseXML",n("response")),U=r.exec.call(B,"XMLHttpRequest","getAllResponseHeaders"),s.dispatchEvent("readystatechange"),n("status")>0?(I&&s.upload.dispatchEvent(t),s.dispatchEvent(t)):(L=!0,s.dispatchEvent("error")),i()}),B.bind("Abort",function(e){s.dispatchEvent(e),i()}),B.bind("Error",function(e){L=!0,n("readyState",p.DONE),s.dispatchEvent("readystatechange"),D=!0,s.dispatchEvent(e),i()}),r.exec.call(B,"XMLHttpRequest","send",{url:_,method:R,async:E,user:x,password:b,headers:w,mimeType:T,encoding:A,responseType:s.responseType,withCredentials:s.withCredentials,options:H},t)}var s=this;M=(new Date).getTime(),B=new a,"string"==typeof H.required_caps&&(H.required_caps=o.parseCaps(H.required_caps)),H.required_caps=e.extend({},H.required_caps,{return_response_type:s.responseType}),t instanceof c&&(H.required_caps.send_multipart=!0),N||(H.required_caps.do_cors=!0),H.ruid?r(B.connectRuntime(H)):(B.bind("RuntimeInit",function(e,t){r(t)}),B.bind("RuntimeError",function(e,t){s.dispatchEvent("RuntimeError",t)}),B.connectRuntime(H))}function g(){n("responseText",""),n("responseXML",null),n("response",null),n("status",0),n("statusText",""),M=C=null}var v=this,y={timeout:0,readyState:p.UNSENT,withCredentials:!1,status:0,statusText:"",responseType:"",responseXML:null,responseText:null,response:null},E=!0,_,R,w={},x,b,A=null,T=null,S=!1,O=!1,I=!1,D=!1,L=!1,N=!1,M,C,F=null,P=null,H={},B,U="",G; -e.extend(this,y,{uid:e.guid("uid_"),upload:new f,open:function(o,a,s,u,c){var l;if(!o||!a)throw new t.DOMException(t.DOMException.SYNTAX_ERR);if(/[\u0100-\uffff]/.test(o)||i.utf8_encode(o)!==o)throw new t.DOMException(t.DOMException.SYNTAX_ERR);if(~e.inArray(o.toUpperCase(),["CONNECT","DELETE","GET","HEAD","OPTIONS","POST","PUT","TRACE","TRACK"])&&(R=o.toUpperCase()),~e.inArray(R,["CONNECT","TRACE","TRACK"]))throw new t.DOMException(t.DOMException.SECURITY_ERR);if(a=i.utf8_encode(a),l=r.parseUrl(a),N=r.hasSameOrigin(l),_=r.resolveUrl(a),(u||c)&&!N)throw new t.DOMException(t.DOMException.INVALID_ACCESS_ERR);if(x=u||l.user,b=c||l.pass,E=s||!0,E===!1&&(n("timeout")||n("withCredentials")||""!==n("responseType")))throw new t.DOMException(t.DOMException.INVALID_ACCESS_ERR);S=!E,O=!1,w={},g.call(this),n("readyState",p.OPENED),this.convertEventPropsToHandlers(["readystatechange"]),this.dispatchEvent("readystatechange")},setRequestHeader:function(r,o){var a=["accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","cookie","cookie2","content-transfer-encoding","date","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","user-agent","via"];if(n("readyState")!==p.OPENED||O)throw new t.DOMException(t.DOMException.INVALID_STATE_ERR);if(/[\u0100-\uffff]/.test(r)||i.utf8_encode(r)!==r)throw new t.DOMException(t.DOMException.SYNTAX_ERR);return r=e.trim(r).toLowerCase(),~e.inArray(r,a)||/^(proxy\-|sec\-)/.test(r)?!1:(w[r]?w[r]+=", "+o:w[r]=o,!0)},getAllResponseHeaders:function(){return U||""},getResponseHeader:function(t){return t=t.toLowerCase(),L||~e.inArray(t,["set-cookie","set-cookie2"])?null:U&&""!==U&&(G||(G={},e.each(U.split(/\r\n/),function(t){var n=t.split(/:\s+/);2===n.length&&(n[0]=e.trim(n[0]),G[n[0].toLowerCase()]={header:n[0],value:e.trim(n[1])})})),G.hasOwnProperty(t))?G[t].header+": "+G[t].value:null},overrideMimeType:function(i){var r,o;if(~e.inArray(n("readyState"),[p.LOADING,p.DONE]))throw new t.DOMException(t.DOMException.INVALID_STATE_ERR);if(i=e.trim(i.toLowerCase()),/;/.test(i)&&(r=i.match(/^([^;]+)(?:;\scharset\=)?(.*)$/))&&(i=r[1],r[2]&&(o=r[2])),!d.mimes[i])throw new t.DOMException(t.DOMException.SYNTAX_ERR);F=i,P=o},send:function(n,r){if(H="string"===e.typeOf(r)?{ruid:r}:r?r:{},this.convertEventPropsToHandlers(m),this.upload.convertEventPropsToHandlers(m),this.readyState!==p.OPENED||O)throw new t.DOMException(t.DOMException.INVALID_STATE_ERR);if(n instanceof s)H.ruid=n.ruid,T=n.type||"application/octet-stream";else if(n instanceof c){if(n.hasBlob()){var o=n.getBlob();H.ruid=o.ruid,T=o.type||"application/octet-stream"}}else"string"==typeof n&&(A="UTF-8",T="text/plain;charset=UTF-8",n=i.utf8_encode(n));this.withCredentials||(this.withCredentials=H.required_caps&&H.required_caps.send_browser_cookies&&!N),I=!S&&this.upload.hasEventListener(),L=!1,D=!n,S||(O=!0),u.call(this,n)},abort:function(){if(L=!0,S=!1,~e.inArray(n("readyState"),[p.UNSENT,p.OPENED,p.DONE]))n("readyState",p.UNSENT);else{if(n("readyState",p.DONE),O=!1,!B)throw new t.DOMException(t.DOMException.INVALID_STATE_ERR);B.getRuntime().exec.call(B,"XMLHttpRequest","abort",D),D=!0}},destroy:function(){B&&("function"===e.typeOf(B.destroy)&&B.destroy(),B=null),this.unbindAll(),this.upload&&(this.upload.unbindAll(),this.upload=null)}})}var h={100:"Continue",101:"Switching Protocols",102:"Processing",200:"OK",201:"Created",202:"Accepted",203:"Non-Authoritative Information",204:"No Content",205:"Reset Content",206:"Partial Content",207:"Multi-Status",226:"IM Used",300:"Multiple Choices",301:"Moved Permanently",302:"Found",303:"See Other",304:"Not Modified",305:"Use Proxy",306:"Reserved",307:"Temporary Redirect",400:"Bad Request",401:"Unauthorized",402:"Payment Required",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",406:"Not Acceptable",407:"Proxy Authentication Required",408:"Request Timeout",409:"Conflict",410:"Gone",411:"Length Required",412:"Precondition Failed",413:"Request Entity Too Large",414:"Request-URI Too Long",415:"Unsupported Media Type",416:"Requested Range Not Satisfiable",417:"Expectation Failed",422:"Unprocessable Entity",423:"Locked",424:"Failed Dependency",426:"Upgrade Required",500:"Internal Server Error",501:"Not Implemented",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout",505:"HTTP Version Not Supported",506:"Variant Also Negotiates",507:"Insufficient Storage",510:"Not Extended"};f.prototype=n.instance;var m=["loadstart","progress","abort","error","load","timeout","loadend"],g=1,v=2;return p.UNSENT=0,p.OPENED=1,p.HEADERS_RECEIVED=2,p.LOADING=3,p.DONE=4,p.prototype=n.instance,p}),i(O,[u,m,v,h],function(e,t,n,i){function r(){function i(){l=d=0,c=this.result=null}function o(t,n){var i=this;u=n,i.bind("TransportingProgress",function(t){d=t.loaded,l>d&&-1===e.inArray(i.state,[r.IDLE,r.DONE])&&a.call(i)},999),i.bind("TransportingComplete",function(){d=l,i.state=r.DONE,c=null,i.result=u.exec.call(i,"Transporter","getAsBlob",t||"")},999),i.state=r.BUSY,i.trigger("TransportingStarted"),a.call(i)}function a(){var e=this,n,i=l-d;f>i&&(f=i),n=t.btoa(c.substr(d,f)),u.exec.call(e,"Transporter","receive",n,l)}var s,u,c,l,d,f;n.call(this),e.extend(this,{uid:e.guid("uid_"),state:r.IDLE,result:null,transport:function(t,n,r){var a=this;if(r=e.extend({chunk_size:204798},r),(s=r.chunk_size%3)&&(r.chunk_size+=3-s),f=r.chunk_size,i.call(this),c=t,l=t.length,"string"===e.typeOf(r)||r.ruid)o.call(a,n,this.connectRuntime(r));else{var u=function(e,t){a.unbind("RuntimeInit",u),o.call(a,n,t)};this.bind("RuntimeInit",u),this.connectRuntime(r)}},abort:function(){var e=this;e.state=r.IDLE,u&&(u.exec.call(e,"Transporter","clear"),e.trigger("TransportingAborted")),i.call(e)},destroy:function(){this.unbindAll(),u=null,this.disconnectRuntime(),i.call(this)}})}return r.IDLE=0,r.BUSY=1,r.DONE=2,r.prototype=i.instance,r}),i(I,[],function(){return!!window.JSON&&JSON.parse||function(){var e,n,i={'"':'"',"\\":"\\","/":"/",b:"\b",f:"\f",n:"\n",r:"\r",t:"	"},r,o=function(t){throw{name:"SyntaxError",message:t,at:e,text:r}},a=function(t){return t&&t!==n&&o("Expected '"+t+"' instead of '"+n+"'"),n=r.charAt(e),e+=1,n},s=function(){var e,t="";for("-"===n&&(t="-",a("-"));n>="0"&&"9">=n;)t+=n,a();if("."===n)for(t+=".";a()&&n>="0"&&"9">=n;)t+=n;if("e"===n||"E"===n)for(t+=n,a(),("-"===n||"+"===n)&&(t+=n,a());n>="0"&&"9">=n;)t+=n,a();return e=+t,isFinite(e)?e:(o("Bad number"),void 0)},u=function(){var e,t,r="",s;if('"'===n)for(;a();){if('"'===n)return a(),r;if("\\"===n)if(a(),"u"===n){for(s=0,t=0;4>t&&(e=parseInt(a(),16),isFinite(e));t+=1)s=16*s+e;r+=String.fromCharCode(s)}else{if("string"!=typeof i[n])break;r+=i[n]}else r+=n}o("Bad string")},c=function(){for(;n&&" ">=n;)a()},l=function(){switch(n){case"t":return a("t"),a("r"),a("u"),a("e"),!0;case"f":return a("f"),a("a"),a("l"),a("s"),a("e"),!1;case"n":return a("n"),a("u"),a("l"),a("l"),null}o("Unexpected '"+n+"'")},d,f=function(){var e=[];if("["===n){if(a("["),c(),"]"===n)return a("]"),e;for(;n;){if(e.push(d()),c(),"]"===n)return a("]"),e;a(","),c()}}o("Bad array")},p=function(){var e,t={};if("{"===n){if(a("{"),c(),"}"===n)return a("}"),t;for(;n;){if(e=u(),c(),a(":"),Object.hasOwnProperty.call(t,e)&&o('Duplicate key "'+e+'"'),t[e]=d(),c(),"}"===n)return a("}"),t;a(","),c()}}o("Bad object")};return d=function(){switch(c(),n){case"{":return p();case"[":return f();case'"':return u();case"-":return s();default:return n>="0"&&"9">=n?s():l()}},function(i,a){var s;return r=i,e=0,n=" ",s=d(),c(),n&&o("Syntax error"),"function"==typeof a?function u(e,n){var i,r,o=e[n];if(o&&"object"==typeof o)for(i in o)Object.prototype.hasOwnProperty.call(o,i)&&(r=u(o,i),r!==t?o[i]=r:delete o[i]);return a.call(e,n,o)}({"":s},""):s}}()}),i(D,[u,f,p,A,S,g,v,O,d,h,y,E,m,I],function(e,t,n,i,r,o,a,s,u,c,l,d,f,p){function h(){function i(t){if(t||(t=this.getRuntime().exec.call(this,"Image","getInfo")),t)if("string"===e.typeOf(t.meta))try{this.meta=p(t.meta)}catch(n){}else this.meta=t.meta;e.extend(this,{size:parseInt(t.size,10),width:parseInt(t.width,10),height:parseInt(t.height,10),type:t.type}),""===this.name&&(this.name=t.name)}function c(t){var i=e.typeOf(t);try{if(t instanceof h){if(!t.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);g.apply(this,arguments)}else if(t instanceof l){if(!~e.inArray(t.type,["image/jpeg","image/png"]))throw new n.ImageError(n.ImageError.WRONG_FORMAT);v.apply(this,arguments)}else if(-1!==e.inArray(i,["blob","file"]))c.call(this,new d(null,t),arguments[1]);else if("string"===i)/^data:[^;]*;base64,/.test(t)?c.call(this,new l(null,{data:t}),arguments[1]):y.apply(this,arguments);else{if("node"!==i||"img"!==t.nodeName.toLowerCase())throw new n.DOMException(n.DOMException.TYPE_MISMATCH_ERR);c.call(this,t.src,arguments[1])}}catch(r){this.trigger("error",r)}}function g(t,n){var i=this.connectRuntime(t.ruid);this.ruid=i.uid,i.exec.call(this,"Image","loadFromImage",t,"undefined"===e.typeOf(n)?!0:n)}function v(t,n){function i(e){r.ruid=e.uid,e.exec.call(r,"Image","loadFromBlob",t)}var r=this;r.name=t.name||"",t.isDetached()?(this.bind("RuntimeInit",function(e,t){i(t)}),n&&"string"==typeof n.required_caps&&(n.required_caps=o.parseCaps(n.required_caps)),this.connectRuntime(e.extend({required_caps:{access_image_binary:!0,resize_image:!0}},n))):i(this.connectRuntime(t.ruid))}function y(e,t){var n=this,i;i=new r,i.open("get",e),i.responseType="blob",i.onprogress=function(e){n.trigger(e)},i.onload=function(){v.call(n,i.response,!0)},i.onerror=function(e){n.trigger(e)},i.onloadend=function(){i.destroy()},i.bind("RuntimeError",function(e,t){n.trigger("RuntimeError",t)}),i.send(null,t)}a.call(this),e.extend(this,{uid:e.guid("uid_"),ruid:null,name:"",size:0,width:0,height:0,type:"",meta:{},clone:function(){this.load.apply(this,arguments)},load:function(){this.bind("Load Resize",function(){i.call(this)},999),this.convertEventPropsToHandlers(m),c.apply(this,arguments)},downsize:function(t,i,r,o){try{if(!this.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);if(this.width>h.MAX_RESIZE_WIDTH||this.height>h.MAX_RESIZE_HEIGHT)throw new n.ImageError(n.ImageError.MAX_RESOLUTION_ERR);(!t&&!i||"undefined"===e.typeOf(r))&&(r=!1),t=t||this.width,i=i||this.height,o="undefined"===e.typeOf(o)?!0:!!o,this.getRuntime().exec.call(this,"Image","downsize",t,i,r,o)}catch(a){this.trigger("error",a)}},crop:function(e,t,n){this.downsize(e,t,!0,n)},getAsCanvas:function(){if(!u.can("create_canvas"))throw new n.RuntimeError(n.RuntimeError.NOT_SUPPORTED_ERR);var e=this.connectRuntime(this.ruid);return e.exec.call(this,"Image","getAsCanvas")},getAsBlob:function(e,t){if(!this.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);return e||(e="image/jpeg"),"image/jpeg"!==e||t||(t=90),this.getRuntime().exec.call(this,"Image","getAsBlob",e,t)},getAsDataURL:function(e,t){if(!this.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);return this.getRuntime().exec.call(this,"Image","getAsDataURL",e,t)},getAsBinaryString:function(e,t){var n=this.getAsDataURL(e,t);return f.atob(n.substring(n.indexOf("base64,")+7))},embed:function(i){function r(){if(u.can("create_canvas")){var t=a.getAsCanvas();if(t)return i.appendChild(t),t=null,a.destroy(),o.trigger("embedded"),void 0}var r=a.getAsDataURL(c,l);if(!r)throw new n.ImageError(n.ImageError.WRONG_FORMAT);if(u.can("use_data_uri_of",r.length))i.innerHTML='<img src="'+r+'" width="'+a.width+'" height="'+a.height+'" />',a.destroy(),o.trigger("embedded");else{var d=new s;d.bind("TransportingComplete",function(){v=o.connectRuntime(this.result.ruid),o.bind("Embedded",function(){e.extend(v.getShimContainer().style,{top:"0px",left:"0px",width:a.width+"px",height:a.height+"px"}),v=null},999),v.exec.call(o,"ImageView","display",this.result.uid,m,g),a.destroy()}),d.transport(f.atob(r.substring(r.indexOf("base64,")+7)),c,e.extend({},p,{required_caps:{display_media:!0},runtime_order:"flash,silverlight",container:i}))}}var o=this,a,c,l,d,p=arguments[1]||{},m=this.width,g=this.height,v;try{if(!(i=t.get(i)))throw new n.DOMException(n.DOMException.INVALID_NODE_TYPE_ERR);if(!this.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);if(this.width>h.MAX_RESIZE_WIDTH||this.height>h.MAX_RESIZE_HEIGHT)throw new n.ImageError(n.ImageError.MAX_RESOLUTION_ERR);if(c=p.type||this.type||"image/jpeg",l=p.quality||90,d="undefined"!==e.typeOf(p.crop)?p.crop:!1,p.width)m=p.width,g=p.height||m;else{var y=t.getSize(i);y.w&&y.h&&(m=y.w,g=y.h)}return a=new h,a.bind("Resize",function(){r.call(o)}),a.bind("Load",function(){a.downsize(m,g,d,!1)}),a.clone(this,!1),a}catch(E){this.trigger("error",E)}},destroy:function(){this.ruid&&(this.getRuntime().exec.call(this,"Image","destroy"),this.disconnectRuntime()),this.unbindAll()}})}var m=["progress","load","error","resize","embedded"];return h.MAX_RESIZE_WIDTH=6500,h.MAX_RESIZE_HEIGHT=6500,h.prototype=c.instance,h}),i(L,[u,p,g,d],function(e,t,n,i){function r(t){var r=this,s=n.capTest,u=n.capTrue,c=e.extend({access_binary:s(window.FileReader||window.File&&window.File.getAsDataURL),access_image_binary:function(){return r.can("access_binary")&&!!a.Image},display_media:s(i.can("create_canvas")||i.can("use_data_uri_over32kb")),do_cors:s(window.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest),drag_and_drop:s(function(){var e=document.createElement("div");return("draggable"in e||"ondragstart"in e&&"ondrop"in e)&&("IE"!==i.browser||i.version>9)}()),filter_by_extension:s(function(){return"Chrome"===i.browser&&i.version>=28||"IE"===i.browser&&i.version>=10}()),return_response_headers:u,return_response_type:function(e){return"json"===e?!0:i.can("return_response_type",e)},return_status_code:u,report_upload_progress:s(window.XMLHttpRequest&&(new XMLHttpRequest).upload),resize_image:function(){return r.can("access_binary")&&i.can("create_canvas")},select_file:function(){return i.can("use_fileinput")&&window.File},select_folder:function(){return r.can("select_file")&&"Chrome"===i.browser&&i.version>=21},select_multiple:function(){return r.can("select_file")&&!("Safari"===i.browser&&"Windows"===i.OS)},send_binary_string:s(window.XMLHttpRequest&&((new XMLHttpRequest).sendAsBinary||window.Uint8Array&&window.ArrayBuffer)),send_custom_headers:s(window.XMLHttpRequest),send_multipart:function(){return!!(window.XMLHttpRequest&&(new XMLHttpRequest).upload&&window.FormData)||r.can("send_binary_string")},slice_blob:s(window.File&&(File.prototype.mozSlice||File.prototype.webkitSlice||File.prototype.slice)),stream_upload:function(){return r.can("slice_blob")&&r.can("send_multipart")},summon_file_dialog:s(function(){return"Firefox"===i.browser&&i.version>=4||"Opera"===i.browser&&i.version>=12||"IE"===i.browser&&i.version>=10||!!~e.inArray(i.browser,["Chrome","Safari"])}()),upload_filesize:u},arguments[2]);n.call(this,t,arguments[1]||o,c),e.extend(this,{init:function(){this.trigger("Init")},destroy:function(e){return function(){e.call(r),e=r=null}}(this.destroy)}),e.extend(this.getShim(),a)}var o="html5",a={};return n.addConstructor(o,r),a}),i(N,[L,y],function(e,t){function n(){function e(e,t,n){var i;if(!window.File.prototype.slice)return(i=window.File.prototype.webkitSlice||window.File.prototype.mozSlice)?i.call(e,t,n):null;try{return e.slice(),e.slice(t,n)}catch(r){return e.slice(t,n-t)}}this.slice=function(){return new t(this.getRuntime().uid,e.apply(this,arguments))}}return e.Blob=n}),i(M,[u],function(e){function t(){this.returnValue=!1}function n(){this.cancelBubble=!0}var i={},r="moxie_"+e.guid(),o=function(o,a,s,u){var c,l;a=a.toLowerCase(),o.addEventListener?(c=s,o.addEventListener(a,c,!1)):o.attachEvent&&(c=function(){var e=window.event;e.target||(e.target=e.srcElement),e.preventDefault=t,e.stopPropagation=n,s(e)},o.attachEvent("on"+a,c)),o[r]||(o[r]=e.guid()),i.hasOwnProperty(o[r])||(i[o[r]]={}),l=i[o[r]],l.hasOwnProperty(a)||(l[a]=[]),l[a].push({func:c,orig:s,key:u})},a=function(t,n,o){var a,s;if(n=n.toLowerCase(),t[r]&&i[t[r]]&&i[t[r]][n]){a=i[t[r]][n];for(var u=a.length-1;u>=0&&(a[u].orig!==o&&a[u].key!==o||(t.removeEventListener?t.removeEventListener(n,a[u].func,!1):t.detachEvent&&t.detachEvent("on"+n,a[u].func),a[u].orig=null,a[u].func=null,a.splice(u,1),o===s));u--);if(a.length||delete i[t[r]][n],e.isEmptyObj(i[t[r]])){delete i[t[r]];try{delete t[r]}catch(c){t[r]=s}}}},s=function(t,n){t&&t[r]&&e.each(i[t[r]],function(e,i){a(t,i,n)})};return{addEvent:o,removeEvent:a,removeAllEvents:s}}),i(C,[L,u,f,M,l,d],function(e,t,n,i,r,o){function a(){var e=[],a;t.extend(this,{init:function(s){var u=this,c=u.getRuntime(),l,d,f,p,h,m;a=s,e=[],f=a.accept.mimes||r.extList2mimes(a.accept,c.can("filter_by_extension")),d=c.getShimContainer(),d.innerHTML='<input id="'+c.uid+'" type="file" style="font-size:999px;opacity:0;"'+(a.multiple&&c.can("select_multiple")?"multiple":"")+(a.directory&&c.can("select_folder")?"webkitdirectory directory":"")+(f?' accept="'+f.join(",")+'"':"")+" />",l=n.get(c.uid),t.extend(l.style,{position:"absolute",top:0,left:0,width:"100%",height:"100%"}),p=n.get(a.browse_button),c.can("summon_file_dialog")&&("static"===n.getStyle(p,"position")&&(p.style.position="relative"),h=parseInt(n.getStyle(p,"z-index"),10)||1,p.style.zIndex=h,d.style.zIndex=h-1,i.addEvent(p,"click",function(e){var t=n.get(c.uid);t&&!t.disabled&&t.click(),e.preventDefault()},u.uid)),m=c.can("summon_file_dialog")?p:d,i.addEvent(m,"mouseover",function(){u.trigger("mouseenter")},u.uid),i.addEvent(m,"mouseout",function(){u.trigger("mouseleave")},u.uid),i.addEvent(m,"mousedown",function(){u.trigger("mousedown")},u.uid),i.addEvent(n.get(a.container),"mouseup",function(){u.trigger("mouseup")},u.uid),l.onchange=function g(){if(e=[],a.directory?t.each(this.files,function(t){"."!==t.name&&e.push(t)}):e=[].slice.call(this.files),"IE"!==o.browser)this.value="";else{var n=this.cloneNode(!0);this.parentNode.replaceChild(n,this),n.onchange=g}u.trigger("change")},u.trigger({type:"ready",async:!0}),d=null},getFiles:function(){return e},disable:function(e){var t=this.getRuntime(),i;(i=n.get(t.uid))&&(i.disabled=!!e)},destroy:function(){var t=this.getRuntime(),r=t.getShimContainer();i.removeAllEvents(r,this.uid),i.removeAllEvents(a&&n.get(a.container),this.uid),i.removeAllEvents(a&&n.get(a.browse_button),this.uid),r&&(r.innerHTML=""),e=a=null}})}return e.FileInput=a}),i(F,[L,u,f,M,l],function(e,t,n,i,r){function o(){function e(e){for(var n=[],i=0;i<e.length;i++)[].push.apply(n,e[i].extensions.split(/\s*,\s*/));return-1===t.inArray("*",n)?n:[]}function o(e){var n=r.getFileExtension(e.name);return!n||!l.length||-1!==t.inArray(n,l)}function a(e,n){var i=[];t.each(e,function(e){i.push(function(t){s(e,t)})}),t.inSeries(i,function(){n()})}function s(e,t){e.isFile?e.file(function(e){o(e)&&c.push(e),t()},function(){t()}):e.isDirectory?u(e,t):t()}function u(e,t){function n(e){r.readEntries(function(t){t.length?([].push.apply(i,t),n(e)):e()},e)}var i=[],r=e.createReader();n(function(){a(i,t)})}var c=[],l=[],d;t.extend(this,{init:function(n){var r=this,s;d=n,l=e(d.accept),s=d.container,i.addEvent(s,"dragover",function(e){e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="copy"},r.uid),i.addEvent(s,"drop",function(e){if(e.preventDefault(),e.stopPropagation(),c=[],e.dataTransfer.items&&e.dataTransfer.items[0].webkitGetAsEntry){var n=[];t.each(e.dataTransfer.items,function(e){n.push(e.webkitGetAsEntry())}),a(n,function(){r.trigger("drop")})}else t.each(e.dataTransfer.files,function(e){o(e)&&c.push(e)}),r.trigger("drop")},r.uid),i.addEvent(s,"dragenter",function(e){e.preventDefault(),e.stopPropagation(),r.trigger("dragenter")},r.uid),i.addEvent(s,"dragleave",function(e){e.preventDefault(),e.stopPropagation(),r.trigger("dragleave")},r.uid)},getFiles:function(){return c},destroy:function(){i.removeAllEvents(d&&n.get(d.container),this.uid),c=l=d=null}})}return e.FileDrop=o}),i(P,[L,m,u],function(e,t,n){function i(){function e(e){return t.atob(e.substring(e.indexOf("base64,")+7))}var i,r=!1;n.extend(this,{read:function(e,t){var o=this;i=new window.FileReader,i.addEventListener("progress",function(e){o.trigger(e)}),i.addEventListener("load",function(e){o.trigger(e)}),i.addEventListener("error",function(e){o.trigger(e,i.error)}),i.addEventListener("loadend",function(){i=null}),"function"===n.typeOf(i[e])?(r=!1,i[e](t.getSource())):"readAsBinaryString"===e&&(r=!0,i.readAsDataURL(t.getSource()))},getResult:function(){return i&&i.result?r?e(i.result):i.result:null},abort:function(){i&&i.abort()},destroy:function(){i=null}})}return e.FileReader=i}),i(H,[L,u,l,b,E,y,T,p,d,I],function(e,t,n,i,r,o,a,s,u,c){function l(){function e(e,t){var n=this,i,r;i=t.getBlob().getSource(),r=new window.FileReader,r.onload=function(){t.append(t.getBlobName(),new o(null,{type:i.type,data:r.result})),self.send.call(n,e,t)},r.readAsBinaryString(i)}function l(){return!window.XMLHttpRequest||"IE"===u.browser&&u.version<8?function(){for(var e=["Msxml2.XMLHTTP.6.0","Microsoft.XMLHTTP"],t=0;t<e.length;t++)try{return new ActiveXObject(e[t])}catch(n){}}():new window.XMLHttpRequest}function d(e){var t=e.responseXML,n=e.responseText;return"IE"===u.browser&&n&&t&&!t.documentElement&&/[^\/]+\/[^\+]+\+xml/.test(e.getResponseHeader("Content-Type"))&&(t=new window.ActiveXObject("Microsoft.XMLDOM"),t.async=!1,t.validateOnParse=!1,t.loadXML(n)),t&&("IE"===u.browser&&0!==t.parseError||!t.documentElement||"parsererror"===t.documentElement.tagName)?null:t}function f(e){var t="----moxieboundary"+(new Date).getTime(),n="--",i="\r\n",r="",a=this.getRuntime();if(!a.can("send_binary_string"))throw new s.RuntimeError(s.RuntimeError.NOT_SUPPORTED_ERR);return p.setRequestHeader("Content-Type","multipart/form-data; boundary="+t),e.each(function(e,a){r+=e instanceof o?n+t+i+'Content-Disposition: form-data; name="'+a+'"; filename="'+unescape(encodeURIComponent(e.name||"blob"))+'"'+i+"Content-Type: "+e.type+i+i+e.getSource()+i:n+t+i+'Content-Disposition: form-data; name="'+a+'"'+i+i+unescape(encodeURIComponent(e))+i}),r+=n+t+n+i}var p,h;t.extend(this,{send:function(n,r){var s=this,c="Mozilla"===u.browser&&u.version>=4&&u.version<7,d="Android Browser"===u.browser,m=!1;if(h=n.url.replace(/^.+?\/([\w\-\.]+)$/,"$1").toLowerCase(),p=l(),p.open(n.method,n.url,n.async,n.user,n.password),r instanceof o)r.isDetached()&&(m=!0),r=r.getSource();else if(r instanceof a){if(r.hasBlob())if(r.getBlob().isDetached())r=f.call(s,r),m=!0;else if((c||d)&&"blob"===t.typeOf(r.getBlob().getSource())&&window.FileReader)return e.call(s,n,r),void 0;if(r instanceof a){var g=new window.FormData;r.each(function(e,t){e instanceof o?g.append(t,e.getSource()):g.append(t,e)}),r=g}}p.upload?(n.withCredentials&&(p.withCredentials=!0),p.addEventListener("load",function(e){s.trigger(e)}),p.addEventListener("error",function(e){s.trigger(e)}),p.addEventListener("progress",function(e){s.trigger(e)}),p.upload.addEventListener("progress",function(e){s.trigger({type:"UploadProgress",loaded:e.loaded,total:e.total})})):p.onreadystatechange=function v(){switch(p.readyState){case 1:break;case 2:break;case 3:var e,t;try{i.hasSameOrigin(n.url)&&(e=p.getResponseHeader("Content-Length")||0),p.responseText&&(t=p.responseText.length)}catch(r){e=t=0}s.trigger({type:"progress",lengthComputable:!!e,total:parseInt(e,10),loaded:t});break;case 4:p.onreadystatechange=function(){},0===p.status?s.trigger("error"):s.trigger("load")}},t.isEmptyObj(n.headers)||t.each(n.headers,function(e,t){p.setRequestHeader(t,e)}),""!==n.responseType&&"responseType"in p&&(p.responseType="json"!==n.responseType||u.can("return_response_type","json")?n.responseType:"text"),m?p.sendAsBinary?p.sendAsBinary(r):function(){for(var e=new Uint8Array(r.length),t=0;t<r.length;t++)e[t]=255&r.charCodeAt(t);p.send(e.buffer)}():p.send(r),s.trigger("loadstart")},getStatus:function(){try{if(p)return p.status}catch(e){}return 0},getResponse:function(e){var t=this.getRuntime();try{switch(e){case"blob":var i=new r(t.uid,p.response),o=p.getResponseHeader("Content-Disposition");if(o){var a=o.match(/filename=([\'\"'])([^\1]+)\1/);a&&(h=a[2])}return i.name=h,i.type||(i.type=n.getFileMime(h)),i;case"json":return u.can("return_response_type","json")?p.response:200===p.status?c(p.responseText):null;case"document":return d(p);default:return""!==p.responseText?p.responseText:null}}catch(s){return null}},getAllResponseHeaders:function(){try{return p.getAllResponseHeaders()}catch(e){}return""},abort:function(){p&&p.abort()},destroy:function(){self=h=null}})}return e.XMLHttpRequest=l}),i(B,[],function(){return function(){function e(e,t){var n=r?0:-8*(t-1),i=0,a;for(a=0;t>a;a++)i|=o.charCodeAt(e+a)<<Math.abs(n+8*a);return i}function n(e,t,n){n=3===arguments.length?n:o.length-t-1,o=o.substr(0,t)+e+o.substr(n+t)}function i(e,t,i){var o="",a=r?0:-8*(i-1),s;for(s=0;i>s;s++)o+=String.fromCharCode(255&t>>Math.abs(a+8*s));n(o,e,i)}var r=!1,o;return{II:function(e){return e===t?r:(r=e,void 0)},init:function(e){r=!1,o=e},SEGMENT:function(e,t,i){switch(arguments.length){case 1:return o.substr(e,o.length-e-1);case 2:return o.substr(e,t);case 3:n(i,e,t);break;default:return o}},BYTE:function(t){return e(t,1)},SHORT:function(t){return e(t,2)},LONG:function(n,r){return r===t?e(n,4):(i(n,r,4),void 0)},SLONG:function(t){var n=e(t,4);return n>2147483647?n-4294967296:n},STRING:function(t,n){var i="";for(n+=t;n>t;t++)i+=String.fromCharCode(e(t,1));return i}}}}),i(U,[B],function(e){return function t(n){var i=[],r,o,a,s=0;if(r=new e,r.init(n),65496===r.SHORT(0)){for(o=2;o<=n.length;)if(a=r.SHORT(o),a>=65488&&65495>=a)o+=2;else{if(65498===a||65497===a)break;s=r.SHORT(o+2)+2,a>=65505&&65519>=a&&i.push({hex:a,name:"APP"+(15&a),start:o,length:s,segment:r.SEGMENT(o,s)}),o+=s}return r.init(null),{headers:i,restore:function(e){var t,n;for(r.init(e),o=65504==r.SHORT(2)?4+r.SHORT(4):2,n=0,t=i.length;t>n;n++)r.SEGMENT(o,0,i[n].segment),o+=i[n].length;return e=r.SEGMENT(),r.init(null),e},strip:function(e){var n,i,o;for(i=new t(e),n=i.headers,i.purge(),r.init(e),o=n.length;o--;)r.SEGMENT(n[o].start,n[o].length,"");return e=r.SEGMENT(),r.init(null),e},get:function(e){for(var t=[],n=0,r=i.length;r>n;n++)i[n].name===e.toUpperCase()&&t.push(i[n].segment);return t},set:function(e,t){var n=[],r,o,a;for("string"==typeof t?n.push(t):n=t,r=o=0,a=i.length;a>r&&(i[r].name===e.toUpperCase()&&(i[r].segment=n[o],i[r].length=n[o].length,o++),!(o>=n.length));r++);},purge:function(){i=[],r.init(null),r=null}}}}}),i(G,[u,B],function(e,n){return function i(){function i(e,n){var i=a.SHORT(e),r,o,s,u,d,f,p,h,m=[],g={};for(r=0;i>r;r++)if(p=f=e+12*r+2,s=n[a.SHORT(p)],s!==t){switch(u=a.SHORT(p+=2),d=a.LONG(p+=2),p+=4,m=[],u){case 1:case 7:for(d>4&&(p=a.LONG(p)+c.tiffHeader),o=0;d>o;o++)m[o]=a.BYTE(p+o);break;case 2:d>4&&(p=a.LONG(p)+c.tiffHeader),g[s]=a.STRING(p,d-1);continue;case 3:for(d>2&&(p=a.LONG(p)+c.tiffHeader),o=0;d>o;o++)m[o]=a.SHORT(p+2*o);break;case 4:for(d>1&&(p=a.LONG(p)+c.tiffHeader),o=0;d>o;o++)m[o]=a.LONG(p+4*o);break;case 5:for(p=a.LONG(p)+c.tiffHeader,o=0;d>o;o++)m[o]=a.LONG(p+4*o)/a.LONG(p+4*o+4);break;case 9:for(p=a.LONG(p)+c.tiffHeader,o=0;d>o;o++)m[o]=a.SLONG(p+4*o);break;case 10:for(p=a.LONG(p)+c.tiffHeader,o=0;d>o;o++)m[o]=a.SLONG(p+4*o)/a.SLONG(p+4*o+4);break;default:continue}h=1==d?m[0]:m,g[s]=l.hasOwnProperty(s)&&"object"!=typeof h?l[s][h]:h}return g}function r(){var e=c.tiffHeader;return a.II(18761==a.SHORT(e)),42!==a.SHORT(e+=2)?!1:(c.IFD0=c.tiffHeader+a.LONG(e+=2),u=i(c.IFD0,s.tiff),"ExifIFDPointer"in u&&(c.exifIFD=c.tiffHeader+u.ExifIFDPointer,delete u.ExifIFDPointer),"GPSInfoIFDPointer"in u&&(c.gpsIFD=c.tiffHeader+u.GPSInfoIFDPointer,delete u.GPSInfoIFDPointer),!0)}function o(e,t,n){var i,r,o,u=0;if("string"==typeof t){var l=s[e.toLowerCase()];for(var d in l)if(l[d]===t){t=d;break}}i=c[e.toLowerCase()+"IFD"],r=a.SHORT(i);for(var f=0;r>f;f++)if(o=i+12*f+2,a.SHORT(o)==t){u=o+8;break}return u?(a.LONG(u,n),!0):!1}var a,s,u,c={},l;return a=new n,s={tiff:{274:"Orientation",270:"ImageDescription",271:"Make",272:"Model",305:"Software",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer"},exif:{36864:"ExifVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",36867:"DateTimeOriginal",33434:"ExposureTime",33437:"FNumber",34855:"ISOSpeedRatings",37377:"ShutterSpeedValue",37378:"ApertureValue",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37386:"FocalLength",41986:"ExposureMode",41987:"WhiteBalance",41990:"SceneCaptureType",41988:"DigitalZoomRatio",41992:"Contrast",41993:"Saturation",41994:"Sharpness"},gps:{0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude"}},l={ColorSpace:{1:"sRGB",0:"Uncalibrated"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{1:"Daylight",2:"Fliorescent",3:"Tungsten",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 -5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire.",1:"Flash fired.",5:"Strobe return light not detected.",7:"Strobe return light detected.",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},ExposureMode:{0:"Auto exposure",1:"Manual exposure",2:"Auto bracket"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},GPSLatitudeRef:{N:"North latitude",S:"South latitude"},GPSLongitudeRef:{E:"East longitude",W:"West longitude"}},{init:function(e){return c={tiffHeader:10},e!==t&&e.length?(a.init(e),65505===a.SHORT(0)&&"EXIF\0"===a.STRING(4,5).toUpperCase()?r():!1):!1},TIFF:function(){return u},EXIF:function(){var t;if(t=i(c.exifIFD,s.exif),t.ExifVersion&&"array"===e.typeOf(t.ExifVersion)){for(var n=0,r="";n<t.ExifVersion.length;n++)r+=String.fromCharCode(t.ExifVersion[n]);t.ExifVersion=r}return t},GPS:function(){var t;return t=i(c.gpsIFD,s.gps),t.GPSVersionID&&"array"===e.typeOf(t.GPSVersionID)&&(t.GPSVersionID=t.GPSVersionID.join(".")),t},setExif:function(e,t){return"PixelXDimension"!==e&&"PixelYDimension"!==e?!1:o("exif",e,t)},getBinary:function(){return a.SEGMENT()},purge:function(){a.init(null),a=u=null,c={}}}}}),i(z,[u,p,U,B,G],function(e,t,n,i,r){function o(o){function a(){for(var e=0,t,n;e<=u.length;){if(t=c.SHORT(e+=2),t>=65472&&65475>=t)return e+=5,{height:c.SHORT(e),width:c.SHORT(e+=2)};n=c.SHORT(e+=2),e+=n-2}return null}function s(){d&&l&&c&&(d.purge(),l.purge(),c.init(null),u=f=l=d=c=null)}var u,c,l,d,f,p;if(u=o,c=new i,c.init(u),65496!==c.SHORT(0))throw new t.ImageError(t.ImageError.WRONG_FORMAT);l=new n(o),d=new r,p=!!d.init(l.get("app1")[0]),f=a.call(this),e.extend(this,{type:"image/jpeg",size:u.length,width:f&&f.width||0,height:f&&f.height||0,setExif:function(t,n){return p?("object"===e.typeOf(t)?e.each(t,function(e,t){d.setExif(t,e) -}):d.setExif(t,n),l.set("app1",d.getBinary()),void 0):!1},writeHeaders:function(){return arguments.length?l.restore(arguments[0]):u=l.restore(u)},stripHeaders:function(e){return l.strip(e)},purge:function(){s.call(this)}}),p&&(this.meta={tiff:d.TIFF(),exif:d.EXIF(),gps:d.GPS()})}return o}),i(q,[p,u,B],function(e,t,n){function i(i){function r(){var e,t;return e=a.call(this,8),"IHDR"==e.type?(t=e.start,{width:u.LONG(t),height:u.LONG(t+=4)}):null}function o(){u&&(u.init(null),s=d=c=l=u=null)}function a(e){var t,n,i,r;return t=u.LONG(e),n=u.STRING(e+=4,4),i=e+=4,r=u.LONG(e+t),{length:t,type:n,start:i,CRC:r}}var s,u,c,l,d;s=i,u=new n,u.init(s),function(){var t=0,n=0,i=[35152,20039,3338,6666];for(n=0;n<i.length;n++,t+=2)if(i[n]!=u.SHORT(t))throw new e.ImageError(e.ImageError.WRONG_FORMAT)}(),d=r.call(this),t.extend(this,{type:"image/png",size:s.length,width:d.width,height:d.height,purge:function(){o.call(this)}}),o.call(this)}return i}),i(k,[u,p,z,q],function(e,t,n,i){return function(r){var o=[n,i],a;a=function(){for(var e=0;e<o.length;e++)try{return new o[e](r)}catch(n){}throw new t.ImageError(t.ImageError.WRONG_FORMAT)}(),e.extend(this,{type:"",size:0,width:0,height:0,setExif:function(){},writeHeaders:function(e){return e},stripHeaders:function(e){return e},purge:function(){}}),e.extend(this,a),this.purge=function(){a.purge(),a=null}}}),i(X,[],function(){function e(e,i,r){var o=e.naturalWidth,a=e.naturalHeight,s=r.width,u=r.height,c=r.x||0,l=r.y||0,d=i.getContext("2d");t(e)&&(o/=2,a/=2);var f=1024,p=document.createElement("canvas");p.width=p.height=f;for(var h=p.getContext("2d"),m=n(e,o,a),g=0;a>g;){for(var v=g+f>a?a-g:f,y=0;o>y;){var E=y+f>o?o-y:f;h.clearRect(0,0,f,f),h.drawImage(e,-y,-g);var _=y*s/o+c<<0,R=Math.ceil(E*s/o),w=g*u/a/m+l<<0,x=Math.ceil(v*u/a/m);d.drawImage(p,0,0,E,v,_,w,R,x),y+=f}g+=f}p=h=null}function t(e){var t=e.naturalWidth,n=e.naturalHeight;if(t*n>1048576){var i=document.createElement("canvas");i.width=i.height=1;var r=i.getContext("2d");return r.drawImage(e,-t+1,0),0===r.getImageData(0,0,1,1).data[3]}return!1}function n(e,t,n){var i=document.createElement("canvas");i.width=1,i.height=n;var r=i.getContext("2d");r.drawImage(e,0,0);for(var o=r.getImageData(0,0,1,n).data,a=0,s=n,u=n;u>a;){var c=o[4*(u-1)+3];0===c?s=u:a=u,u=s+a>>1}i=null;var l=u/n;return 0===l?1:l}return{isSubsampled:t,renderTo:e}}),i(j,[L,u,p,m,y,k,X,l,d],function(e,t,n,i,r,o,a,s,u){function c(){function e(){if(!_&&!y)throw new n.ImageError(n.DOMException.INVALID_STATE_ERR);return _||y}function c(e){return i.atob(e.substring(e.indexOf("base64,")+7))}function l(e,t){return"data:"+(t||"")+";base64,"+i.btoa(e)}function d(e){var t=this;y=new Image,y.onerror=function(){g.call(this),t.trigger("error",new n.ImageError(n.ImageError.WRONG_FORMAT))},y.onload=function(){t.trigger("load")},y.src=/^data:[^;]*;base64,/.test(e)?e:l(e,w.type)}function f(e,t){var i=this,r;return window.FileReader?(r=new FileReader,r.onload=function(){t(this.result)},r.onerror=function(){i.trigger("error",new n.FileException(n.FileException.NOT_READABLE_ERR))},r.readAsDataURL(e),void 0):t(e.getAsDataURL())}function p(n,i,r,o){var a=this,s,u,c,l,d,f,p,g,v;if(b=o,v=this.meta&&this.meta.tiff&&this.meta.tiff.Orientation||1,-1!==t.inArray(v,[5,6,7,8])){var y=n;n=i,i=y}return f=e(),c=r?Math.max:Math.min,u=c(n/f.width,i/f.height),u>1&&(!r||o)?(this.trigger("Resize"),void 0):(p=Math.round(f.width*u),g=Math.round(f.height*u),_||(_=document.createElement("canvas")),s=_.getContext("2d"),r?(_.width=n,_.height=i):(_.width=p,_.height=g),l=p>_.width?Math.round((p-_.width)/2):0,d=g>_.height?Math.round((g-_.height)/2):0,b||m(_.width,_.height,v),h.call(this,f,_,-l,-d,p,g),this.width=_.width,this.height=_.height,x=!0,a.trigger("Resize"),void 0)}function h(e,t,n,i,r,o){if("iOS"===u.OS)a.renderTo(e,t,{width:r,height:o,x:n,y:i});else{var s=t.getContext("2d");s.drawImage(e,n,i,r,o)}}function m(e,t,n){switch(n){case 5:case 6:case 7:case 8:_.width=t,_.height=e;break;default:_.width=e,_.height=t}var i=_.getContext("2d");switch(n){case 2:i.translate(e,0),i.scale(-1,1);break;case 3:i.translate(e,t),i.rotate(Math.PI);break;case 4:i.translate(0,t),i.scale(1,-1);break;case 5:i.rotate(.5*Math.PI),i.scale(1,-1);break;case 6:i.rotate(.5*Math.PI),i.translate(0,-t);break;case 7:i.rotate(.5*Math.PI),i.translate(e,-t),i.scale(-1,1);break;case 8:i.rotate(-.5*Math.PI),i.translate(-e,0)}}function g(){E&&(E.purge(),E=null),R=y=_=w=null,x=!1}var v=this,y,E,_,R,w,x=!1,b=!0;t.extend(this,{loadFromBlob:function(e){var t=this,i=t.getRuntime(),r=arguments.length>1?arguments[1]:!0;if(!i.can("access_binary"))throw new n.RuntimeError(n.RuntimeError.NOT_SUPPORTED_ERR);return w=e,e.isDetached()?(R=e.getSource(),d.call(this,R),void 0):(f.call(this,e.getSource(),function(e){r&&(R=c(e)),d.call(t,e)}),void 0)},loadFromImage:function(e,t){this.meta=e.meta,w=new r(null,{name:e.name,size:e.size,type:e.type}),d.call(this,t?R=e.getAsBinaryString():e.getAsDataURL())},getInfo:function(){var t=this.getRuntime(),n;return!E&&R&&t.can("access_image_binary")&&(E=new o(R)),n={width:e().width||0,height:e().height||0,type:w.type||s.getFileMime(w.name),size:R&&R.length||w.size||0,name:w.name||"",meta:E&&E.meta||this.meta||{}}},downsize:function(){p.apply(this,arguments)},getAsCanvas:function(){return _&&(_.id=this.uid+"_canvas"),_},getAsBlob:function(e,t){return e!==this.type&&p.call(this,this.width,this.height,!1),new r(null,{type:e,data:v.getAsBinaryString.call(this,e,t)})},getAsDataURL:function(e){var t=arguments[1]||90;if(!x)return y.src;if("image/jpeg"!==e)return _.toDataURL("image/png");try{return _.toDataURL("image/jpeg",t/100)}catch(n){return _.toDataURL("image/jpeg")}},getAsBinaryString:function(e,t){if(!x)return R||(R=c(v.getAsDataURL(e,t))),R;if("image/jpeg"!==e)R=c(v.getAsDataURL(e,t));else{var n;t||(t=90);try{n=_.toDataURL("image/jpeg",t/100)}catch(i){n=_.toDataURL("image/jpeg")}R=c(n),E&&(R=E.stripHeaders(R),b&&(E.meta&&E.meta.exif&&E.setExif({PixelXDimension:this.width,PixelYDimension:this.height}),R=E.writeHeaders(R)),E.purge(),E=null)}return x=!1,R},destroy:function(){v=null,g.call(this),this.getRuntime().getShim().removeInstance(this.uid)}})}return e.Image=c}),i(V,[u,d,f,p,g],function(e,t,n,i,r){function o(){var e;try{e=navigator.plugins["Shockwave Flash"],e=e.description}catch(t){try{e=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(n){e="0.0"}}return e=e.match(/\d+/g),parseFloat(e[0]+"."+e[1])}function a(a){var c=this,l;a=e.extend({swf_url:t.swf_url},a),r.call(this,a,s,{access_binary:function(e){return e&&"browser"===c.mode},access_image_binary:function(e){return e&&"browser"===c.mode},display_media:r.capTrue,do_cors:r.capTrue,drag_and_drop:!1,report_upload_progress:function(){return"client"===c.mode},resize_image:r.capTrue,return_response_headers:!1,return_response_type:function(t){return!e.arrayDiff(t,["","text","json","document"])||"browser"===c.mode},return_status_code:function(t){return"browser"===c.mode||!e.arrayDiff(t,[200,404])},select_file:r.capTrue,select_multiple:r.capTrue,send_binary_string:function(e){return e&&"browser"===c.mode},send_browser_cookies:function(e){return e&&"browser"===c.mode},send_custom_headers:function(e){return e&&"browser"===c.mode},send_multipart:r.capTrue,slice_blob:r.capTrue,stream_upload:function(e){return e&&"browser"===c.mode},summon_file_dialog:!1,upload_filesize:function(t){return e.parseSizeStr(t)<=2097152||"client"===c.mode},use_http_method:function(t){return!e.arrayDiff(t,["GET","POST"])}},{access_binary:function(e){return e?"browser":"client"},access_image_binary:function(e){return e?"browser":"client"},report_upload_progress:function(e){return e?"browser":"client"},return_response_type:function(t){return e.arrayDiff(t,["","text","json","document"])?"browser":["client","browser"]},return_status_code:function(t){return e.arrayDiff(t,[200,404])?"browser":["client","browser"]},send_binary_string:function(e){return e?"browser":"client"},send_browser_cookies:function(e){return e?"browser":"client"},send_custom_headers:function(e){return e?"browser":"client"},stream_upload:function(e){return e?"client":"browser"},upload_filesize:function(t){return e.parseSizeStr(t)>=2097152?"client":"browser"}},"client"),o()<10&&(this.mode=!1),e.extend(this,{getShim:function(){return n.get(this.uid)},shimExec:function(e,t){var n=[].slice.call(arguments,2);return c.getShim().exec(this.uid,e,t,n)},init:function(){var n,r,o;o=this.getShimContainer(),e.extend(o.style,{position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),n='<object id="'+this.uid+'" type="application/x-shockwave-flash" data="'+a.swf_url+'" ',"IE"===t.browser&&(n+='classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '),n+='width="100%" height="100%" style="outline:0"><param name="movie" value="'+a.swf_url+'" />'+'<param name="flashvars" value="uid='+escape(this.uid)+"&target="+t.global_event_dispatcher+'" />'+'<param name="wmode" value="transparent" />'+'<param name="allowscriptaccess" value="always" />'+"</object>","IE"===t.browser?(r=document.createElement("div"),o.appendChild(r),r.outerHTML=n,r=o=null):o.innerHTML=n,l=setTimeout(function(){c&&!c.initialized&&c.trigger("Error",new i.RuntimeError(i.RuntimeError.NOT_INIT_ERR))},5e3)},destroy:function(e){return function(){e.call(c),clearTimeout(l),a=l=e=c=null}}(this.destroy)},u)}var s="flash",u={};return r.addConstructor(s,a),u}),i(W,[V,y],function(e,t){var n={slice:function(e,n,i,r){var o=this.getRuntime();return 0>n?n=Math.max(e.size+n,0):n>0&&(n=Math.min(n,e.size)),0>i?i=Math.max(e.size+i,0):i>0&&(i=Math.min(i,e.size)),e=o.shimExec.call(this,"Blob","slice",n,i,r||""),e&&(e=new t(o.uid,e)),e}};return e.Blob=n}),i(Y,[V],function(e){var t={init:function(e){this.getRuntime().shimExec.call(this,"FileInput","init",{name:e.name,accept:e.accept,multiple:e.multiple}),this.trigger("ready")}};return e.FileInput=t}),i($,[V,m],function(e,t){function n(e,n){switch(n){case"readAsText":return t.atob(e,"utf8");case"readAsBinaryString":return t.atob(e);case"readAsDataURL":return e}return null}var i="",r={read:function(e,t){var r=this,o=r.getRuntime();return"readAsDataURL"===e&&(i="data:"+(t.type||"")+";base64,"),r.bind("Progress",function(t,r){r&&(i+=n(r,e))}),o.shimExec.call(this,"FileReader","readAsBase64",t.uid)},getResult:function(){return i},destroy:function(){i=null}};return e.FileReader=r}),i(K,[V,m],function(e,t){function n(e,n){switch(n){case"readAsText":return t.atob(e,"utf8");case"readAsBinaryString":return t.atob(e);case"readAsDataURL":return e}return null}var i={read:function(e,t){var i,r=this.getRuntime();return(i=r.shimExec.call(this,"FileReaderSync","readAsBase64",t.uid))?("readAsDataURL"===e&&(i="data:"+(t.type||"")+";base64,"+i),n(i,e,t.type)):null}};return e.FileReaderSync=i}),i(Z,[V,u,y,E,A,T,O,I],function(e,t,n,i,r,o,a,s){var u={send:function(e,i){function r(){e.transport=l.mode,l.shimExec.call(c,"XMLHttpRequest","send",e,i)}function s(e,t){l.shimExec.call(c,"XMLHttpRequest","appendBlob",e,t.uid),i=null,r()}function u(e,t){var n=new a;n.bind("TransportingComplete",function(){t(this.result)}),n.transport(e.getSource(),e.type,{ruid:l.uid})}var c=this,l=c.getRuntime();if(t.isEmptyObj(e.headers)||t.each(e.headers,function(e,t){l.shimExec.call(c,"XMLHttpRequest","setRequestHeader",t,e.toString())}),i instanceof o){var d;if(i.each(function(e,t){e instanceof n?d=t:l.shimExec.call(c,"XMLHttpRequest","append",t,e)}),i.hasBlob()){var f=i.getBlob();f.isDetached()?u(f,function(e){f.destroy(),s(d,e)}):s(d,f)}else i=null,r()}else i instanceof n?i.isDetached()?u(i,function(e){i.destroy(),i=e.uid,r()}):(i=i.uid,r()):r()},getResponse:function(e){var n,o,a=this.getRuntime();if(o=a.shimExec.call(this,"XMLHttpRequest","getResponseAsBlob")){if(o=new i(a.uid,o),"blob"===e)return o;if(~t.inArray(e,["","text"]))return n=new r,n.readAsText(o);if("arraybuffer"===e);else if("json"===e){n=new r;try{return s(n.readAsText(o))}catch(u){return null}}}return null},abort:function(e){var t=this.getRuntime();t.shimExec.call(this,"XMLHttpRequest","abort"),this.dispatchEvent("readystatechange"),this.dispatchEvent("abort")}};return e.XMLHttpRequest=u}),i(J,[V,y],function(e,t){var n={getAsBlob:function(e){var n=this.getRuntime(),i=n.shimExec.call(this,"Transporter","getAsBlob",e);return i?new t(n.uid,i):null}};return e.Transporter=n}),i(Q,[V,u,O,y,A],function(e,t,n,i,r){var o={loadFromBlob:function(e){function t(e){r.shimExec.call(i,"Image","loadFromBlob",e.uid),i=r=null}var i=this,r=i.getRuntime();if(e.isDetached()){var o=new n;o.bind("TransportingComplete",function(){t(o.result.getSource())}),o.transport(e.getSource(),e.type,{ruid:r.uid})}else t(e.getSource())},loadFromImage:function(e){var t=this.getRuntime();return t.shimExec.call(this,"Image","loadFromImage",e.uid)},getAsBlob:function(e,t){var n=this.getRuntime(),r=n.shimExec.call(this,"Image","getAsBlob",e,t);return r?new i(n.uid,r):null},getAsDataURL:function(){var e=this.getRuntime(),t=e.Image.getAsBlob.apply(this,arguments),n;return t?(n=new r,n.readAsDataURL(t)):null}};return e.Image=o}),i(et,[u,d,f,p,g],function(e,t,n,i,r){function o(e){var t=!1,n=null,i,r,o,a,s,u=0;try{try{n=new ActiveXObject("AgControl.AgControl"),n.IsVersionSupported(e)&&(t=!0),n=null}catch(c){var l=navigator.plugins["Silverlight Plug-In"];if(l){for(i=l.description,"1.0.30226.2"===i&&(i="2.0.30226.2"),r=i.split(".");r.length>3;)r.pop();for(;r.length<4;)r.push(0);for(o=e.split(".");o.length>4;)o.pop();do a=parseInt(o[u],10),s=parseInt(r[u],10),u++;while(u<o.length&&a===s);s>=a&&!isNaN(a)&&(t=!0)}}}catch(d){t=!1}return t}function a(a){var c=this,l;a=e.extend({xap_url:t.xap_url},a),r.call(this,a,s,{access_binary:r.capTrue,access_image_binary:r.capTrue,display_media:r.capTrue,do_cors:r.capTrue,drag_and_drop:!1,report_upload_progress:r.capTrue,resize_image:r.capTrue,return_response_headers:function(e){return e&&"client"===c.mode},return_response_type:r.capTrue,return_status_code:function(t){return"client"===c.mode||!e.arrayDiff(t,[200,404])},select_file:r.capTrue,select_multiple:r.capTrue,send_binary_string:r.capTrue,send_browser_cookies:function(e){return e&&"browser"===c.mode},send_custom_headers:function(e){return e&&"client"===c.mode},send_multipart:r.capTrue,slice_blob:r.capTrue,stream_upload:!0,summon_file_dialog:!1,upload_filesize:r.capTrue,use_http_method:function(t){return"client"===c.mode||!e.arrayDiff(t,["GET","POST"])}},{return_response_headers:function(e){return e?"client":"browser"},return_status_code:function(t){return e.arrayDiff(t,[200,404])?"client":["client","browser"]},send_browser_cookies:function(e){return e?"browser":"client"},send_custom_headers:function(e){return e?"client":"browser"},use_http_method:function(t){return e.arrayDiff(t,["GET","POST"])?"client":["client","browser"]}}),o("2.0.31005.0")&&"Opera"!==t.browser||(this.mode=!1),e.extend(this,{getShim:function(){return n.get(this.uid).content.Moxie},shimExec:function(e,t){var n=[].slice.call(arguments,2);return c.getShim().exec(this.uid,e,t,n)},init:function(){var e;e=this.getShimContainer(),e.innerHTML='<object id="'+this.uid+'" data="data:application/x-silverlight," type="application/x-silverlight-2" width="100%" height="100%" style="outline:none;">'+'<param name="source" value="'+a.xap_url+'"/>'+'<param name="background" value="Transparent"/>'+'<param name="windowless" value="true"/>'+'<param name="enablehtmlaccess" value="true"/>'+'<param name="initParams" value="uid='+this.uid+",target="+t.global_event_dispatcher+'"/>'+"</object>",l=setTimeout(function(){c&&!c.initialized&&c.trigger("Error",new i.RuntimeError(i.RuntimeError.NOT_INIT_ERR))},"Windows"!==t.OS?1e4:5e3)},destroy:function(e){return function(){e.call(c),clearTimeout(l),a=l=e=c=null}}(this.destroy)},u)}var s="silverlight",u={};return r.addConstructor(s,a),u}),i(tt,[et,u,W],function(e,t,n){return e.Blob=t.extend({},n)}),i(nt,[et],function(e){var t={init:function(e){function t(e){for(var t="",n=0;n<e.length;n++)t+=(""!==t?"|":"")+e[n].title+" | *."+e[n].extensions.replace(/,/g,";*.");return t}this.getRuntime().shimExec.call(this,"FileInput","init",t(e.accept),e.name,e.multiple),this.trigger("ready")}};return e.FileInput=t}),i(it,[et,f,M],function(e,t,n){var i={init:function(){var e=this,i=e.getRuntime(),r;return r=i.getShimContainer(),n.addEvent(r,"dragover",function(e){e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="copy"},e.uid),n.addEvent(r,"dragenter",function(e){e.preventDefault();var n=t.get(i.uid).dragEnter(e);n&&e.stopPropagation()},e.uid),n.addEvent(r,"drop",function(e){e.preventDefault();var n=t.get(i.uid).dragDrop(e);n&&e.stopPropagation()},e.uid),i.shimExec.call(this,"FileDrop","init")}};return e.FileDrop=i}),i(rt,[et,u,$],function(e,t,n){return e.FileReader=t.extend({},n)}),i(ot,[et,u,K],function(e,t,n){return e.FileReaderSync=t.extend({},n)}),i(at,[et,u,Z],function(e,t,n){return e.XMLHttpRequest=t.extend({},n)}),i(st,[et,u,J],function(e,t,n){return e.Transporter=t.extend({},n)}),i(ut,[et,u,Q],function(e,t,n){return e.Image=t.extend({},n)}),i(ct,[u,p,g,d],function(e,t,n,i){function r(t){var r=this,s=n.capTest,u=n.capTrue;n.call(this,t,o,{access_binary:s(window.FileReader||window.File&&File.getAsDataURL),access_image_binary:!1,display_media:s(a.Image&&(i.can("create_canvas")||i.can("use_data_uri_over32kb"))),do_cors:!1,drag_and_drop:!1,filter_by_extension:s(function(){return"Chrome"===i.browser&&i.version>=28||"IE"===i.browser&&i.version>=10}()),resize_image:function(){return a.Image&&r.can("access_binary")&&i.can("create_canvas")},report_upload_progress:!1,return_response_headers:!1,return_response_type:function(t){return!!~e.inArray(t,["json","text","document",""])},return_status_code:function(t){return!e.arrayDiff(t,[200,404])},select_file:function(){return i.can("use_fileinput")},select_multiple:!1,send_binary_string:!1,send_custom_headers:!1,send_multipart:!0,slice_blob:!1,stream_upload:function(){return r.can("select_file")},summon_file_dialog:s(function(){return"Firefox"===i.browser&&i.version>=4||"Opera"===i.browser&&i.version>=12||"IE"===i.browser&&i.version>=10||!!~e.inArray(i.browser,["Chrome","Safari"])}()),upload_filesize:u,use_http_method:function(t){return!e.arrayDiff(t,["GET","POST"])}}),e.extend(this,{init:function(){this.trigger("Init")},destroy:function(e){return function(){e.call(r),e=r=null}}(this.destroy)}),e.extend(this.getShim(),a)}var o="html4",a={};return n.addConstructor(o,r),a}),i(lt,[ct,u,f,M,l,d],function(e,t,n,i,r,o){function a(){function e(){var r=this,l=r.getRuntime(),d,f,p,h,m,g;g=t.guid("uid_"),d=l.getShimContainer(),a&&(p=n.get(a+"_form"),p&&t.extend(p.style,{top:"100%"})),h=document.createElement("form"),h.setAttribute("id",g+"_form"),h.setAttribute("method","post"),h.setAttribute("enctype","multipart/form-data"),h.setAttribute("encoding","multipart/form-data"),t.extend(h.style,{overflow:"hidden",position:"absolute",top:0,left:0,width:"100%",height:"100%"}),m=document.createElement("input"),m.setAttribute("id",g),m.setAttribute("type","file"),m.setAttribute("name","Filedata"),m.setAttribute("accept",u.join(",")),t.extend(m.style,{fontSize:"999px",opacity:0}),h.appendChild(m),d.appendChild(h),t.extend(m.style,{position:"absolute",top:0,left:0,width:"100%",height:"100%"}),"IE"===o.browser&&o.version<10&&t.extend(m.style,{filter:"progid:DXImageTransform.Microsoft.Alpha(opacity=0)"}),m.onchange=function(){var t;this.value&&(t=this.files?this.files[0]:{name:this.value},s=[t],this.onchange=function(){},e.call(r),r.bind("change",function(){var e=n.get(g),t=n.get(g+"_form"),i;r.files.length&&e&&t&&(i=r.files[0],e.setAttribute("id",i.uid),t.setAttribute("id",i.uid+"_form"),t.setAttribute("target",i.uid+"_iframe")),e=t=null},998),m=h=null,r.trigger("change"))},l.can("summon_file_dialog")&&(f=n.get(c.browse_button),i.removeEvent(f,"click",r.uid),i.addEvent(f,"click",function(e){m&&!m.disabled&&m.click(),e.preventDefault()},r.uid)),a=g,d=p=f=null,r.trigger({type:"ready",async:!0})}var a,s=[],u=[],c;t.extend(this,{init:function(t){var o=this,a=o.getRuntime(),s;c=t,u=t.accept.mimes||r.extList2mimes(t.accept,a.can("filter_by_extension")),s=a.getShimContainer(),function(){var e,r,u;e=n.get(t.browse_button),a.can("summon_file_dialog")&&("static"===n.getStyle(e,"position")&&(e.style.position="relative"),r=parseInt(n.getStyle(e,"z-index"),10)||1,e.style.zIndex=r,s.style.zIndex=r-1),u=a.can("summon_file_dialog")?e:s,i.addEvent(u,"mouseover",function(){o.trigger("mouseenter")},o.uid),i.addEvent(u,"mouseout",function(){o.trigger("mouseleave")},o.uid),i.addEvent(u,"mousedown",function(){o.trigger("mousedown")},o.uid),i.addEvent(n.get(t.container),"mouseup",function(){o.trigger("mouseup")},o.uid),e=null}(),e.call(this),s=null},getFiles:function(){return s},disable:function(e){var t;(t=n.get(a))&&(t.disabled=!!e)},destroy:function(){var e=this.getRuntime(),t=e.getShimContainer();i.removeAllEvents(t,this.uid),i.removeAllEvents(c&&n.get(c.container),this.uid),i.removeAllEvents(c&&n.get(c.browse_button),this.uid),t&&(t.innerHTML=""),a=s=u=c=null}})}return e.FileInput=a}),i(dt,[ct,P],function(e,t){return e.FileReader=t}),i(ft,[ct,u,f,b,p,M,y,T,I],function(e,t,n,i,r,o,a,s,u){function c(){function e(e){var t=this,i,r,a,s,u=!1;if(d){if(i=d.id.replace(/_iframe$/,""),r=n.get(i+"_form")){for(a=r.getElementsByTagName("input"),s=a.length;s--;)switch(a[s].getAttribute("type")){case"hidden":a[s].parentNode.removeChild(a[s]);break;case"file":u=!0}a=[],u||r.parentNode.removeChild(r),r=null}setTimeout(function(){o.removeEvent(d,"load",t.uid),d.parentNode&&d.parentNode.removeChild(d);var n=t.getRuntime().getShimContainer();n.children.length||n.parentNode.removeChild(n),n=d=null,e()},1)}}var c,l,d;t.extend(this,{send:function(u,f){function p(){var n=m.getShimContainer()||document.body,r=document.createElement("div");r.innerHTML='<iframe id="'+g+'_iframe" name="'+g+'_iframe" src="javascript:""" style="display:none"></iframe>',d=r.firstChild,n.appendChild(d),o.addEvent(d,"load",function(){var n;try{n=d.contentWindow.document||d.contentDocument||window.frames[d.id].document,/^4\d{2}\s/.test(n.title)&&n.getElementsByTagName("address").length?c=n.title.replace(/^(\d+).*$/,"$1"):(c=200,l=t.trim(n.body.innerHTML),h.trigger({type:"progress",loaded:l.length,total:l.length}),E&&h.trigger({type:"uploadprogress",loaded:E.size||1025,total:E.size||1025}))}catch(r){if(!i.hasSameOrigin(u.url))return e.call(h,function(){h.trigger("error")}),void 0;c=404}e.call(h,function(){h.trigger("load")})},h.uid)}var h=this,m=h.getRuntime(),g,v,y,E;if(c=l=null,f instanceof s&&f.hasBlob()){if(E=f.getBlob(),g=E.uid,y=n.get(g),v=n.get(g+"_form"),!v)throw new r.DOMException(r.DOMException.NOT_FOUND_ERR)}else g=t.guid("uid_"),v=document.createElement("form"),v.setAttribute("id",g+"_form"),v.setAttribute("method",u.method),v.setAttribute("enctype","multipart/form-data"),v.setAttribute("encoding","multipart/form-data"),v.setAttribute("target",g+"_iframe"),m.getShimContainer().appendChild(v);f instanceof s&&f.each(function(e,n){if(e instanceof a)y&&y.setAttribute("name",n);else{var i=document.createElement("input");t.extend(i,{type:"hidden",name:n,value:e}),v.appendChild(i)}}),v.setAttribute("action",u.url),p(),v.submit(),h.trigger("loadstart")},getStatus:function(){return c},getResponse:function(e){if("json"===e&&"string"===t.typeOf(l))try{return u(l.replace(/^\s*<pre[^>]*>/,"").replace(/<\/pre>\s*$/,""))}catch(n){return null}return l},abort:function(){var t=this;d&&d.contentWindow&&(d.contentWindow.stop?d.contentWindow.stop():d.contentWindow.document.execCommand?d.contentWindow.document.execCommand("Stop"):d.src="about:blank"),e.call(this,function(){t.dispatchEvent("abort")})}})}return e.XMLHttpRequest=c}),i(pt,[ct,j],function(e,t){return e.Image=t}),a([u,c,l,d,f,p,h,m,g,v,y,E,_,R,w,x,b,A,T,S,O,I,D,M])}(this);;(function(){"use strict";var e={},t=moxie.core.utils.Basic.inArray;return function n(r){var i,s;for(i in r)s=typeof r[i],s==="object"&&!~t(i,["Exceptions","Env","Mime"])?n(r[i]):s==="function"&&(e[i]=r[i])}(window.moxie),e.Env=window.moxie.core.utils.Env,e.Mime=window.moxie.core.utils.Mime,e.Exceptions=window.moxie.core.Exceptions,window.mOxie=e,window.o||(window.o=e),e})(); +!function(e,t){"use strict";function n(e,t){for(var n,i=[],r=0;r<e.length;++r){if(n=s[e[r]]||o(e[r]),!n)throw"module definition dependecy not found: "+e[r];i.push(n)}t.apply(null,i)}function i(e,i,r){if("string"!=typeof e)throw"invalid module definition, module id must be defined and be a string";if(i===t)throw"invalid module definition, dependencies must be specified";if(r===t)throw"invalid module definition, definition function must be specified";n(i,function(){s[e]=r.apply(null,arguments)})}function r(e){return!!s[e]}function o(t){for(var n=e,i=t.split(/[.\/]/),r=0;r<i.length;++r){if(!n[i[r]])return;n=n[i[r]]}return n}function a(n){for(var i=0;i<n.length;i++){for(var r=e,o=n[i],a=o.split(/[.\/]/),u=0;u<a.length-1;++u)r[a[u]]===t&&(r[a[u]]={}),r=r[a[u]];r[a[a.length-1]]=s[o]}}var s={},u="moxie/core/utils/Basic",c="moxie/core/I18n",l="moxie/core/utils/Mime",d="moxie/core/utils/Env",f="moxie/core/utils/Dom",p="moxie/core/Exceptions",h="moxie/core/EventTarget",m="moxie/core/utils/Encode",g="moxie/runtime/Runtime",v="moxie/runtime/RuntimeClient",y="moxie/file/Blob",w="moxie/file/File",E="moxie/file/FileInput",_="moxie/file/FileDrop",x="moxie/runtime/RuntimeTarget",R="moxie/file/FileReader",b="moxie/core/utils/Url",T="moxie/file/FileReaderSync",S="moxie/xhr/FormData",A="moxie/xhr/XMLHttpRequest",O="moxie/runtime/Transporter",I="moxie/image/Image",D="moxie/runtime/html5/Runtime",N="moxie/runtime/html5/file/Blob",L="moxie/core/utils/Events",M="moxie/runtime/html5/file/FileInput",C="moxie/runtime/html5/file/FileDrop",F="moxie/runtime/html5/file/FileReader",H="moxie/runtime/html5/xhr/XMLHttpRequest",P="moxie/runtime/html5/utils/BinaryReader",k="moxie/runtime/html5/image/JPEGHeaders",U="moxie/runtime/html5/image/ExifParser",B="moxie/runtime/html5/image/JPEG",z="moxie/runtime/html5/image/PNG",G="moxie/runtime/html5/image/ImageInfo",q="moxie/runtime/html5/image/MegaPixel",X="moxie/runtime/html5/image/Image",j="moxie/runtime/flash/Runtime",V="moxie/runtime/flash/file/Blob",W="moxie/runtime/flash/file/FileInput",Y="moxie/runtime/flash/file/FileReader",$="moxie/runtime/flash/file/FileReaderSync",J="moxie/runtime/flash/xhr/XMLHttpRequest",Z="moxie/runtime/flash/runtime/Transporter",K="moxie/runtime/flash/image/Image",Q="moxie/runtime/silverlight/Runtime",et="moxie/runtime/silverlight/file/Blob",tt="moxie/runtime/silverlight/file/FileInput",nt="moxie/runtime/silverlight/file/FileDrop",it="moxie/runtime/silverlight/file/FileReader",rt="moxie/runtime/silverlight/file/FileReaderSync",ot="moxie/runtime/silverlight/xhr/XMLHttpRequest",at="moxie/runtime/silverlight/runtime/Transporter",st="moxie/runtime/silverlight/image/Image",ut="moxie/runtime/html4/Runtime",ct="moxie/runtime/html4/file/FileInput",lt="moxie/runtime/html4/file/FileReader",dt="moxie/runtime/html4/xhr/XMLHttpRequest",ft="moxie/runtime/html4/image/Image";i(u,[],function(){var e=function(e){var t;return e===t?"undefined":null===e?"null":e.nodeType?"node":{}.toString.call(e).match(/\s([a-z|A-Z]+)/)[1].toLowerCase()},t=function(i){var r;return n(arguments,function(o,s){s>0&&n(o,function(n,o){n!==r&&(e(i[o])===e(n)&&~a(e(n),["array","object"])?t(i[o],n):i[o]=n)})}),i},n=function(e,t){var n,i,r,o;if(e){try{n=e.length}catch(a){n=o}if(n===o){for(i in e)if(e.hasOwnProperty(i)&&t(e[i],i)===!1)return}else for(r=0;n>r;r++)if(t(e[r],r)===!1)return}},i=function(t){var n;if(!t||"object"!==e(t))return!0;for(n in t)return!1;return!0},r=function(t,n){function i(r){"function"===e(t[r])&&t[r](function(e){++r<o&&!e?i(r):n(e)})}var r=0,o=t.length;"function"!==e(n)&&(n=function(){}),t&&t.length||n(),i(r)},o=function(e,t){var i=0,r=e.length,o=new Array(r);n(e,function(e,n){e(function(e){if(e)return t(e);var a=[].slice.call(arguments);a.shift(),o[n]=a,i++,i===r&&(o.unshift(null),t.apply(this,o))})})},a=function(e,t){if(t){if(Array.prototype.indexOf)return Array.prototype.indexOf.call(t,e);for(var n=0,i=t.length;i>n;n++)if(t[n]===e)return n}return-1},s=function(t,n){var i=[];"array"!==e(t)&&(t=[t]),"array"!==e(n)&&(n=[n]);for(var r in t)-1===a(t[r],n)&&i.push(t[r]);return i.length?i:!1},u=function(e,t){var i=[];return n(e,function(e){-1!==a(e,t)&&i.push(e)}),i.length?i:null},c=function(e){var t,n=[];for(t=0;t<e.length;t++)n[t]=e[t];return n},l=function(){var e=0;return function(t){var n=(new Date).getTime().toString(32),i;for(i=0;5>i;i++)n+=Math.floor(65535*Math.random()).toString(32);return(t||"o_")+n+(e++).toString(32)}}(),d=function(e){return e?String.prototype.trim?String.prototype.trim.call(e):e.toString().replace(/^\s*/,"").replace(/\s*$/,""):e},f=function(e){if("string"!=typeof e)return e;var t={t:1099511627776,g:1073741824,m:1048576,k:1024},n;return e=/^([0-9]+)([mgk]?)$/.exec(e.toLowerCase().replace(/[^0-9mkg]/g,"")),n=e[2],e=+e[1],t.hasOwnProperty(n)&&(e*=t[n]),e};return{guid:l,typeOf:e,extend:t,each:n,isEmptyObj:i,inSeries:r,inParallel:o,inArray:a,arrayDiff:s,arrayIntersect:u,toArray:c,trim:d,parseSizeStr:f}}),i(c,[u],function(e){var t={};return{addI18n:function(n){return e.extend(t,n)},translate:function(e){return t[e]||e},_:function(e){return this.translate(e)},sprintf:function(t){var n=[].slice.call(arguments,1);return t.replace(/%[a-z]/g,function(){var t=n.shift();return"undefined"!==e.typeOf(t)?t:""})}}}),i(l,[u,c],function(e,t){var n="application/msword,doc dot,application/pdf,pdf,application/pgp-signature,pgp,application/postscript,ps ai eps,application/rtf,rtf,application/vnd.ms-excel,xls xlb,application/vnd.ms-powerpoint,ppt pps pot,application/zip,zip,application/x-shockwave-flash,swf swfl,application/vnd.openxmlformats-officedocument.wordprocessingml.document,docx,application/vnd.openxmlformats-officedocument.wordprocessingml.template,dotx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,xlsx,application/vnd.openxmlformats-officedocument.presentationml.presentation,pptx,application/vnd.openxmlformats-officedocument.presentationml.template,potx,application/vnd.openxmlformats-officedocument.presentationml.slideshow,ppsx,application/x-javascript,js,application/json,json,audio/mpeg,mp3 mpga mpega mp2,audio/x-wav,wav,audio/x-m4a,m4a,audio/ogg,oga ogg,audio/aiff,aiff aif,audio/flac,flac,audio/aac,aac,audio/ac3,ac3,audio/x-ms-wma,wma,image/bmp,bmp,image/gif,gif,image/jpeg,jpg jpeg jpe,image/photoshop,psd,image/png,png,image/svg+xml,svg svgz,image/tiff,tiff tif,text/plain,asc txt text diff log,text/html,htm html xhtml,text/css,css,text/csv,csv,text/rtf,rtf,video/mpeg,mpeg mpg mpe m2v,video/quicktime,qt mov,video/mp4,mp4,video/x-m4v,m4v,video/x-flv,flv,video/x-ms-wmv,wmv,video/avi,avi,video/webm,webm,video/3gpp,3gpp 3gp,video/3gpp2,3g2,video/vnd.rn-realvideo,rv,video/ogg,ogv,video/x-matroska,mkv,application/vnd.oasis.opendocument.formula-template,otf,application/octet-stream,exe",i={mimes:{},extensions:{},addMimeType:function(e){var t=e.split(/,/),n,i,r;for(n=0;n<t.length;n+=2){for(r=t[n+1].split(/ /),i=0;i<r.length;i++)this.mimes[r[i]]=t[n];this.extensions[t[n]]=r}},extList2mimes:function(t,n){var i=this,r,o,a,s,u=[];for(o=0;o<t.length;o++)for(r=t[o].extensions.split(/\s*,\s*/),a=0;a<r.length;a++){if("*"===r[a])return[];if(s=i.mimes[r[a]])-1===e.inArray(s,u)&&u.push(s);else{if(!n||!/^\w+$/.test(r[a]))return[];u.push("."+r[a])}}return u},mimes2exts:function(t){var n=this,i=[];return e.each(t,function(t){if("*"===t)return i=[],!1;var r=t.match(/^(\w+)\/(\*|\w+)$/);r&&("*"===r[2]?e.each(n.extensions,function(e,t){new RegExp("^"+r[1]+"/").test(t)&&[].push.apply(i,n.extensions[t])}):n.extensions[t]&&[].push.apply(i,n.extensions[t]))}),i},mimes2extList:function(n){var i=[],r=[];return"string"===e.typeOf(n)&&(n=e.trim(n).split(/\s*,\s*/)),r=this.mimes2exts(n),i.push({title:t.translate("Files"),extensions:r.length?r.join(","):"*"}),i.mimes=n,i},getFileExtension:function(e){var t=e&&e.match(/\.([^.]+)$/);return t?t[1].toLowerCase():""},getFileMime:function(e){return this.mimes[this.getFileExtension(e)]||""}};return i.addMimeType(n),i}),i(d,[u],function(e){function t(e,t,n){var i=0,r=0,o=0,a={dev:-6,alpha:-5,a:-5,beta:-4,b:-4,RC:-3,rc:-3,"#":-2,p:1,pl:1},s=function(e){return e=(""+e).replace(/[_\-+]/g,"."),e=e.replace(/([^.\d]+)/g,".$1.").replace(/\.{2,}/g,"."),e.length?e.split("."):[-8]},u=function(e){return e?isNaN(e)?a[e]||-7:parseInt(e,10):0};for(e=s(e),t=s(t),r=Math.max(e.length,t.length),i=0;r>i;i++)if(e[i]!=t[i]){if(e[i]=u(e[i]),t[i]=u(t[i]),e[i]<t[i]){o=-1;break}if(e[i]>t[i]){o=1;break}}if(!n)return o;switch(n){case">":case"gt":return o>0;case">=":case"ge":return o>=0;case"<=":case"le":return 0>=o;case"==":case"=":case"eq":return 0===o;case"<>":case"!=":case"ne":return 0!==o;case"":case"<":case"lt":return 0>o;default:return null}}var n=function(e){var t="",n="?",i="function",r="undefined",o="object",a="major",s="model",u="name",c="type",l="vendor",d="version",f="architecture",p="console",h="mobile",m="tablet",g={has:function(e,t){return-1!==t.toLowerCase().indexOf(e.toLowerCase())},lowerize:function(e){return e.toLowerCase()}},v={rgx:function(){for(var t,n=0,a,s,u,c,l,d,f=arguments;n<f.length;n+=2){var p=f[n],h=f[n+1];if(typeof t===r){t={};for(u in h)c=h[u],typeof c===o?t[c[0]]=e:t[c]=e}for(a=s=0;a<p.length;a++)if(l=p[a].exec(this.getUA())){for(u=0;u<h.length;u++)d=l[++s],c=h[u],typeof c===o&&c.length>0?2==c.length?t[c[0]]=typeof c[1]==i?c[1].call(this,d):c[1]:3==c.length?t[c[0]]=typeof c[1]!==i||c[1].exec&&c[1].test?d?d.replace(c[1],c[2]):e:d?c[1].call(this,d,c[2]):e:4==c.length&&(t[c[0]]=d?c[3].call(this,d.replace(c[1],c[2])):e):t[c]=d?d:e;break}if(l)break}return t},str:function(t,i){for(var r in i)if(typeof i[r]===o&&i[r].length>0){for(var a=0;a<i[r].length;a++)if(g.has(i[r][a],t))return r===n?e:r}else if(g.has(i[r],t))return r===n?e:r;return t}},y={browser:{oldsafari:{major:{1:["/8","/1","/3"],2:"/4","?":"/"},version:{"1.0":"/8",1.2:"/1",1.3:"/3","2.0":"/412","2.0.2":"/416","2.0.3":"/417","2.0.4":"/419","?":"/"}}},device:{sprint:{model:{"Evo Shift 4G":"7373KT"},vendor:{HTC:"APA",Sprint:"Sprint"}}},os:{windows:{version:{ME:"4.90","NT 3.11":"NT3.51","NT 4.0":"NT4.0",2000:"NT 5.0",XP:["NT 5.1","NT 5.2"],Vista:"NT 6.0",7:"NT 6.1",8:"NT 6.2",8.1:"NT 6.3",RT:"ARM"}}}},w={browser:[[/(opera\smini)\/((\d+)?[\w\.-]+)/i,/(opera\s[mobiletab]+).+version\/((\d+)?[\w\.-]+)/i,/(opera).+version\/((\d+)?[\w\.]+)/i,/(opera)[\/\s]+((\d+)?[\w\.]+)/i],[u,d,a],[/\s(opr)\/((\d+)?[\w\.]+)/i],[[u,"Opera"],d,a],[/(kindle)\/((\d+)?[\w\.]+)/i,/(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?((\d+)?[\w\.]+)*/i,/(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?((\d+)?[\w\.]*)/i,/(?:ms|\()(ie)\s((\d+)?[\w\.]+)/i,/(rekonq)((?:\/)[\w\.]+)*/i,/(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron)\/((\d+)?[\w\.-]+)/i],[u,d,a],[/(trident).+rv[:\s]((\d+)?[\w\.]+).+like\sgecko/i],[[u,"IE"],d,a],[/(yabrowser)\/((\d+)?[\w\.]+)/i],[[u,"Yandex"],d,a],[/(comodo_dragon)\/((\d+)?[\w\.]+)/i],[[u,/_/g," "],d,a],[/(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?((\d+)?[\w\.]+)/i],[u,d,a],[/(dolfin)\/((\d+)?[\w\.]+)/i],[[u,"Dolphin"],d,a],[/((?:android.+)crmo|crios)\/((\d+)?[\w\.]+)/i],[[u,"Chrome"],d,a],[/((?:android.+))version\/((\d+)?[\w\.]+)\smobile\ssafari/i],[[u,"Android Browser"],d,a],[/version\/((\d+)?[\w\.]+).+?mobile\/\w+\s(safari)/i],[d,a,[u,"Mobile Safari"]],[/version\/((\d+)?[\w\.]+).+?(mobile\s?safari|safari)/i],[d,a,u],[/webkit.+?(mobile\s?safari|safari)((\/[\w\.]+))/i],[u,[a,v.str,y.browser.oldsafari.major],[d,v.str,y.browser.oldsafari.version]],[/(konqueror)\/((\d+)?[\w\.]+)/i,/(webkit|khtml)\/((\d+)?[\w\.]+)/i],[u,d,a],[/(navigator|netscape)\/((\d+)?[\w\.-]+)/i],[[u,"Netscape"],d,a],[/(swiftfox)/i,/(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?((\d+)?[\w\.\+]+)/i,/(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/((\d+)?[\w\.-]+)/i,/(mozilla)\/((\d+)?[\w\.]+).+rv\:.+gecko\/\d+/i,/(uc\s?browser|polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|qqbrowser)[\/\s]?((\d+)?[\w\.]+)/i,/(links)\s\(((\d+)?[\w\.]+)/i,/(gobrowser)\/?((\d+)?[\w\.]+)*/i,/(ice\s?browser)\/v?((\d+)?[\w\._]+)/i,/(mosaic)[\/\s]((\d+)?[\w\.]+)/i],[u,d,a]],engine:[[/(presto)\/([\w\.]+)/i,/(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i,/(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i,/(icab)[\/\s]([23]\.[\d\.]+)/i],[u,d],[/rv\:([\w\.]+).*(gecko)/i],[d,u]],os:[[/(windows)\snt\s6\.2;\s(arm)/i,/(windows\sphone(?:\sos)*|windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i],[u,[d,v.str,y.os.windows.version]],[/(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i],[[u,"Windows"],[d,v.str,y.os.windows.version]],[/\((bb)(10);/i],[[u,"BlackBerry"],d],[/(blackberry)\w*\/?([\w\.]+)*/i,/(tizen)\/([\w\.]+)/i,/(android|webos|palm\os|qnx|bada|rim\stablet\sos|meego)[\/\s-]?([\w\.]+)*/i],[u,d],[/(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i],[[u,"Symbian"],d],[/mozilla.+\(mobile;.+gecko.+firefox/i],[[u,"Firefox OS"],d],[/(nintendo|playstation)\s([wids3portablevu]+)/i,/(mint)[\/\s\(]?(\w+)*/i,/(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk)[\/\s-]?([\w\.-]+)*/i,/(hurd|linux)\s?([\w\.]+)*/i,/(gnu)\s?([\w\.]+)*/i],[u,d],[/(cros)\s[\w]+\s([\w\.]+\w)/i],[[u,"Chromium OS"],d],[/(sunos)\s?([\w\.]+\d)*/i],[[u,"Solaris"],d],[/\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i],[u,d],[/(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i],[[u,"iOS"],[d,/_/g,"."]],[/(mac\sos\sx)\s?([\w\s\.]+\w)*/i],[u,[d,/_/g,"."]],[/(haiku)\s(\w+)/i,/(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i,/(macintosh|mac(?=_powerpc)|plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos)/i,/(unix)\s?([\w\.]+)*/i],[u,d]]},E=function(e){var n=e||(window&&window.navigator&&window.navigator.userAgent?window.navigator.userAgent:t);this.getBrowser=function(){return v.rgx.apply(this,w.browser)},this.getEngine=function(){return v.rgx.apply(this,w.engine)},this.getOS=function(){return v.rgx.apply(this,w.os)},this.getResult=function(){return{ua:this.getUA(),browser:this.getBrowser(),engine:this.getEngine(),os:this.getOS()}},this.getUA=function(){return n},this.setUA=function(e){return n=e,this},this.setUA(n)};return(new E).getResult()}(),i=function(){var t={define_property:function(){return!1}(),create_canvas:function(){var e=document.createElement("canvas");return!(!e.getContext||!e.getContext("2d"))}(),return_response_type:function(t){try{if(-1!==e.inArray(t,["","text","document"]))return!0;if(window.XMLHttpRequest){var n=new XMLHttpRequest;if(n.open("get","/"),"responseType"in n)return n.responseType=t,n.responseType!==t?!1:!0}}catch(i){}return!1},use_data_uri:function(){var e=new Image;return e.onload=function(){t.use_data_uri=1===e.width&&1===e.height},setTimeout(function(){e.src="data:image/gif;base64,R0lGODlhAQABAIAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="},1),!1}(),use_data_uri_over32kb:function(){return t.use_data_uri&&("IE"!==r.browser||r.version>=9)},use_data_uri_of:function(e){return t.use_data_uri&&33e3>e||t.use_data_uri_over32kb()},use_fileinput:function(){var e=document.createElement("input");return e.setAttribute("type","file"),!e.disabled}};return function(n){var i=[].slice.call(arguments);return i.shift(),"function"===e.typeOf(t[n])?t[n].apply(this,i):!!t[n]}}(),r={can:i,browser:n.browser.name,version:parseFloat(n.browser.major),os:n.os.name,osVersion:n.os.version,verComp:t,swf_url:"../flash/Moxie.swf",xap_url:"../silverlight/Moxie.xap",global_event_dispatcher:"moxie.core.EventTarget.instance.dispatchEvent"};return r.OS=r.os,r}),i(f,[d],function(e){var t=function(e){return"string"!=typeof e?e:document.getElementById(e)},n=function(e,t){if(!e.className)return!1;var n=new RegExp("(^|\\s+)"+t+"(\\s+|$)");return n.test(e.className)},i=function(e,t){n(e,t)||(e.className=e.className?e.className.replace(/\s+$/,"")+" "+t:t)},r=function(e,t){if(e.className){var n=new RegExp("(^|\\s+)"+t+"(\\s+|$)");e.className=e.className.replace(n,function(e,t,n){return" "===t&&" "===n?" ":""})}},o=function(e,t){return e.currentStyle?e.currentStyle[t]:window.getComputedStyle?window.getComputedStyle(e,null)[t]:void 0},a=function(t,n){function i(e){var t,n,i=0,r=0;return e&&(n=e.getBoundingClientRect(),t="CSS1Compat"===s.compatMode?s.documentElement:s.body,i=n.left+t.scrollLeft,r=n.top+t.scrollTop),{x:i,y:r}}var r=0,o=0,a,s=document,u,c;if(t=t,n=n||s.body,t&&t.getBoundingClientRect&&"IE"===e.browser&&(!s.documentMode||s.documentMode<8))return u=i(t),c=i(n),{x:u.x-c.x,y:u.y-c.y};for(a=t;a&&a!=n&&a.nodeType;)r+=a.offsetLeft||0,o+=a.offsetTop||0,a=a.offsetParent;for(a=t.parentNode;a&&a!=n&&a.nodeType;)r-=a.scrollLeft||0,o-=a.scrollTop||0,a=a.parentNode;return{x:r,y:o}},s=function(e){return{w:e.offsetWidth||e.clientWidth,h:e.offsetHeight||e.clientHeight}};return{get:t,hasClass:n,addClass:i,removeClass:r,getStyle:o,getPos:a,getSize:s}}),i(p,[u],function(e){function t(e,t){var n;for(n in e)if(e[n]===t)return n;return null}return{RuntimeError:function(){function n(e){this.code=e,this.name=t(i,e),this.message=this.name+": RuntimeError "+this.code}var i={NOT_INIT_ERR:1,NOT_SUPPORTED_ERR:9,JS_ERR:4};return e.extend(n,i),n.prototype=Error.prototype,n}(),OperationNotAllowedException:function(){function t(e){this.code=e,this.name="OperationNotAllowedException"}return e.extend(t,{NOT_ALLOWED_ERR:1}),t.prototype=Error.prototype,t}(),ImageError:function(){function n(e){this.code=e,this.name=t(i,e),this.message=this.name+": ImageError "+this.code}var i={WRONG_FORMAT:1,MAX_RESOLUTION_ERR:2};return e.extend(n,i),n.prototype=Error.prototype,n}(),FileException:function(){function n(e){this.code=e,this.name=t(i,e),this.message=this.name+": FileException "+this.code}var i={NOT_FOUND_ERR:1,SECURITY_ERR:2,ABORT_ERR:3,NOT_READABLE_ERR:4,ENCODING_ERR:5,NO_MODIFICATION_ALLOWED_ERR:6,INVALID_STATE_ERR:7,SYNTAX_ERR:8};return e.extend(n,i),n.prototype=Error.prototype,n}(),DOMException:function(){function n(e){this.code=e,this.name=t(i,e),this.message=this.name+": DOMException "+this.code}var i={INDEX_SIZE_ERR:1,DOMSTRING_SIZE_ERR:2,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,INVALID_CHARACTER_ERR:5,NO_DATA_ALLOWED_ERR:6,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INUSE_ATTRIBUTE_ERR:10,INVALID_STATE_ERR:11,SYNTAX_ERR:12,INVALID_MODIFICATION_ERR:13,NAMESPACE_ERR:14,INVALID_ACCESS_ERR:15,VALIDATION_ERR:16,TYPE_MISMATCH_ERR:17,SECURITY_ERR:18,NETWORK_ERR:19,ABORT_ERR:20,URL_MISMATCH_ERR:21,QUOTA_EXCEEDED_ERR:22,TIMEOUT_ERR:23,INVALID_NODE_TYPE_ERR:24,DATA_CLONE_ERR:25};return e.extend(n,i),n.prototype=Error.prototype,n}(),EventException:function(){function t(e){this.code=e,this.name="EventException"}return e.extend(t,{UNSPECIFIED_EVENT_TYPE_ERR:0}),t.prototype=Error.prototype,t}()}}),i(h,[p,u],function(e,t){function n(){var n={};t.extend(this,{uid:null,init:function(){this.uid||(this.uid=t.guid("uid_"))},addEventListener:function(e,i,r,o){var a=this,s;return e=t.trim(e),/\s/.test(e)?(t.each(e.split(/\s+/),function(e){a.addEventListener(e,i,r,o)}),void 0):(e=e.toLowerCase(),r=parseInt(r,10)||0,s=n[this.uid]&&n[this.uid][e]||[],s.push({fn:i,priority:r,scope:o||this}),n[this.uid]||(n[this.uid]={}),n[this.uid][e]=s,void 0)},hasEventListener:function(e){return e?!(!n[this.uid]||!n[this.uid][e]):!!n[this.uid]},removeEventListener:function(e,i){e=e.toLowerCase();var r=n[this.uid]&&n[this.uid][e],o;if(r){if(i){for(o=r.length-1;o>=0;o--)if(r[o].fn===i){r.splice(o,1);break}}else r=[];r.length||(delete n[this.uid][e],t.isEmptyObj(n[this.uid])&&delete n[this.uid])}},removeAllEventListeners:function(){n[this.uid]&&delete n[this.uid]},dispatchEvent:function(i){var r,o,a,s,u={},c=!0,l;if("string"!==t.typeOf(i)){if(s=i,"string"!==t.typeOf(s.type))throw new e.EventException(e.EventException.UNSPECIFIED_EVENT_TYPE_ERR);i=s.type,s.total!==l&&s.loaded!==l&&(u.total=s.total,u.loaded=s.loaded),u.async=s.async||!1}if(-1!==i.indexOf("::")?function(e){r=e[0],i=e[1]}(i.split("::")):r=this.uid,i=i.toLowerCase(),o=n[r]&&n[r][i]){o.sort(function(e,t){return t.priority-e.priority}),a=[].slice.call(arguments),a.shift(),u.type=i,a.unshift(u);var d=[];t.each(o,function(e){a[0].target=e.scope,u.async?d.push(function(t){setTimeout(function(){t(e.fn.apply(e.scope,a)===!1)},1)}):d.push(function(t){t(e.fn.apply(e.scope,a)===!1)})}),d.length&&t.inSeries(d,function(e){c=!e})}return c},bind:function(){this.addEventListener.apply(this,arguments)},unbind:function(){this.removeEventListener.apply(this,arguments)},unbindAll:function(){this.removeAllEventListeners.apply(this,arguments)},trigger:function(){return this.dispatchEvent.apply(this,arguments)},convertEventPropsToHandlers:function(e){var n;"array"!==t.typeOf(e)&&(e=[e]);for(var i=0;i<e.length;i++)n="on"+e[i],"function"===t.typeOf(this[n])?this.addEventListener(e[i],this[n]):"undefined"===t.typeOf(this[n])&&(this[n]=null)}})}return n.instance=new n,n}),i(m,[],function(){var e=function(e){return unescape(encodeURIComponent(e))},t=function(e){return decodeURIComponent(escape(e))},n=function(e,n){if("function"==typeof window.atob)return n?t(window.atob(e)):window.atob(e);var i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r,o,a,s,u,c,l,d,f=0,p=0,h="",m=[];if(!e)return e;e+="";do s=i.indexOf(e.charAt(f++)),u=i.indexOf(e.charAt(f++)),c=i.indexOf(e.charAt(f++)),l=i.indexOf(e.charAt(f++)),d=s<<18|u<<12|c<<6|l,r=255&d>>16,o=255&d>>8,a=255&d,m[p++]=64==c?String.fromCharCode(r):64==l?String.fromCharCode(r,o):String.fromCharCode(r,o,a);while(f<e.length);return h=m.join(""),n?t(h):h},i=function(t,n){if(n&&e(t),"function"==typeof window.btoa)return window.btoa(t);var i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",r,o,a,s,u,c,l,d,f=0,p=0,h="",m=[];if(!t)return t;do r=t.charCodeAt(f++),o=t.charCodeAt(f++),a=t.charCodeAt(f++),d=r<<16|o<<8|a,s=63&d>>18,u=63&d>>12,c=63&d>>6,l=63&d,m[p++]=i.charAt(s)+i.charAt(u)+i.charAt(c)+i.charAt(l);while(f<t.length);h=m.join("");var g=t.length%3;return(g?h.slice(0,g-3):h)+"===".slice(g||3)};return{utf8_encode:e,utf8_decode:t,atob:n,btoa:i}}),i(g,[u,f,h],function(e,t,n){function i(n,r,a,s,u){var c=this,l,d=e.guid(r+"_"),f=u||"browser";n=n||{},o[d]=this,a=e.extend({access_binary:!1,access_image_binary:!1,display_media:!1,do_cors:!1,drag_and_drop:!1,filter_by_extension:!0,resize_image:!1,report_upload_progress:!1,return_response_headers:!1,return_response_type:!1,return_status_code:!0,send_custom_headers:!1,select_file:!1,select_folder:!1,select_multiple:!0,send_binary_string:!1,send_browser_cookies:!0,send_multipart:!0,slice_blob:!1,stream_upload:!1,summon_file_dialog:!1,upload_filesize:!0,use_http_method:!0},a),n.preferred_caps&&(f=i.getMode(s,n.preferred_caps,f)),l=function(){var t={};return{exec:function(e,n,i,r){return l[n]&&(t[e]||(t[e]={context:this,instance:new l[n]}),t[e].instance[i])?t[e].instance[i].apply(this,r):void 0},removeInstance:function(e){delete t[e]},removeAllInstances:function(){var n=this;e.each(t,function(t,i){"function"===e.typeOf(t.instance.destroy)&&t.instance.destroy.call(t.context),n.removeInstance(i)})}}}(),e.extend(this,{initialized:!1,uid:d,type:r,mode:i.getMode(s,n.required_caps,f),shimid:d+"_container",clients:0,options:n,can:function(t,n){var r=arguments[2]||a;if("string"===e.typeOf(t)&&"undefined"===e.typeOf(n)&&(t=i.parseCaps(t)),"object"===e.typeOf(t)){for(var o in t)if(!this.can(o,t[o],r))return!1;return!0}return"function"===e.typeOf(r[t])?r[t].call(this,n):n===r[t]},getShimContainer:function(){var n,i=t.get(this.shimid);return i||(n=this.options.container?t.get(this.options.container):document.body,i=document.createElement("div"),i.id=this.shimid,i.className="moxie-shim moxie-shim-"+this.type,e.extend(i.style,{position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),n.appendChild(i),n=null),i},getShim:function(){return l},shimExec:function(e,t){var n=[].slice.call(arguments,2);return c.getShim().exec.call(this,this.uid,e,t,n)},exec:function(e,t){var n=[].slice.call(arguments,2);return c[e]&&c[e][t]?c[e][t].apply(this,n):c.shimExec.apply(this,arguments)},destroy:function(){if(c){var e=t.get(this.shimid);e&&e.parentNode.removeChild(e),l&&l.removeAllInstances(),this.unbindAll(),delete o[this.uid],this.uid=null,d=c=l=e=null}}}),this.mode&&n.required_caps&&!this.can(n.required_caps)&&(this.mode=!1)}var r={},o={};return i.order="html5,flash,silverlight,html4",i.getRuntime=function(e){return o[e]?o[e]:!1},i.addConstructor=function(e,t){t.prototype=n.instance,r[e]=t},i.getConstructor=function(e){return r[e]||null},i.getInfo=function(e){var t=i.getRuntime(e);return t?{uid:t.uid,type:t.type,mode:t.mode,can:function(){return t.can.apply(t,arguments)}}:null},i.parseCaps=function(t){var n={};return"string"!==e.typeOf(t)?t||{}:(e.each(t.split(","),function(e){n[e]=!0}),n)},i.can=function(e,t){var n,r=i.getConstructor(e),o;return r?(n=new r({required_caps:t}),o=n.mode,n.destroy(),!!o):!1},i.thatCan=function(e,t){var n=(t||i.order).split(/\s*,\s*/);for(var r in n)if(i.can(n[r],e))return n[r];return null},i.getMode=function(t,n,i){var r=null;if("undefined"===e.typeOf(i)&&(i="browser"),n&&!e.isEmptyObj(t)){if(e.each(n,function(n,i){if(t.hasOwnProperty(i)){var o=t[i](n);if("string"==typeof o&&(o=[o]),r){if(!(r=e.arrayIntersect(r,o)))return r=!1}else r=o}}),r)return-1!==e.inArray(i,r)?i:r[0];if(r===!1)return!1}return i},i.capTrue=function(){return!0},i.capFalse=function(){return!1},i.capTest=function(e){return function(){return!!e}},i}),i(v,[p,u,g],function(e,t,n){return function i(){var i;t.extend(this,{connectRuntime:function(r){function o(t){var s,u;return t.length?(s=t.shift(),(u=n.getConstructor(s))?(i=new u(r),i.bind("Init",function(){i.initialized=!0,setTimeout(function(){i.clients++,a.trigger("RuntimeInit",i)},1)}),i.bind("Error",function(){i.destroy(),o(t)}),i.mode?(i.init(),void 0):(i.trigger("Error"),void 0)):(o(t),void 0)):(a.trigger("RuntimeError",new e.RuntimeError(e.RuntimeError.NOT_INIT_ERR)),i=null,void 0)}var a=this,s;if("string"===t.typeOf(r)?s=r:"string"===t.typeOf(r.ruid)&&(s=r.ruid),s){if(i=n.getRuntime(s))return i.clients++,i;throw new e.RuntimeError(e.RuntimeError.NOT_INIT_ERR)}o((r.runtime_order||n.order).split(/\s*,\s*/))},getRuntime:function(){return i&&i.uid?i:(i=null,null)},disconnectRuntime:function(){i&&--i.clients<=0&&(i.destroy(),i=null)}})}}),i(y,[u,m,v],function(e,t,n){function i(o,a){function s(t,n,o){var a,s=r[this.uid];return"string"===e.typeOf(s)&&s.length?(a=new i(null,{type:o,size:n-t}),a.detach(s.substr(t,a.size)),a):null}n.call(this),o&&this.connectRuntime(o),a?"string"===e.typeOf(a)&&(a={data:a}):a={},e.extend(this,{uid:a.uid||e.guid("uid_"),ruid:o,size:a.size||0,type:a.type||"",slice:function(e,t,n){return this.isDetached()?s.apply(this,arguments):this.getRuntime().exec.call(this,"Blob","slice",this.getSource(),e,t,n)},getSource:function(){return r[this.uid]?r[this.uid]:null},detach:function(e){this.ruid&&(this.getRuntime().exec.call(this,"Blob","destroy",r[this.uid]),this.disconnectRuntime(),this.ruid=null),e=e||"";var n=e.match(/^data:([^;]*);base64,/);n&&(this.type=n[1],e=t.atob(e.substring(e.indexOf("base64,")+7))),this.size=e.length,r[this.uid]=e},isDetached:function(){return!this.ruid&&"string"===e.typeOf(r[this.uid])},destroy:function(){this.detach(),delete r[this.uid]}}),a.data?this.detach(a.data):r[this.uid]=a}var r={};return i}),i(w,[u,l,y],function(e,t,n){function i(i,r){var o,a;if(r||(r={}),a=r.type&&""!==r.type?r.type:t.getFileMime(r.name),r.name)o=r.name.replace(/\\/g,"/"),o=o.substr(o.lastIndexOf("/")+1);else{var s=a.split("/")[0];o=e.guid((""!==s?s:"file")+"_"),t.extensions[a]&&(o+="."+t.extensions[a][0])}n.apply(this,arguments),e.extend(this,{type:a||"",name:o||e.guid("file_"),lastModifiedDate:r.lastModifiedDate||(new Date).toLocaleString()})}return i.prototype=n.prototype,i}),i(E,[u,l,f,p,h,c,w,g,v],function(e,t,n,i,r,o,a,s,u){function c(r){var c=this,d,f,p;if(-1!==e.inArray(e.typeOf(r),["string","node"])&&(r={browse_button:r}),f=n.get(r.browse_button),!f)throw new i.DOMException(i.DOMException.NOT_FOUND_ERR);p={accept:[{title:o.translate("All Files"),extensions:"*"}],name:"file",multiple:!1,required_caps:!1,container:f.parentNode||document.body},r=e.extend({},p,r),"string"==typeof r.required_caps&&(r.required_caps=s.parseCaps(r.required_caps)),"string"==typeof r.accept&&(r.accept=t.mimes2extList(r.accept)),d=n.get(r.container),d||(d=document.body),"static"===n.getStyle(d,"position")&&(d.style.position="relative"),d=f=null,u.call(c),e.extend(c,{uid:e.guid("uid_"),ruid:null,shimid:null,files:null,init:function(){c.convertEventPropsToHandlers(l),c.bind("RuntimeInit",function(t,i){c.ruid=i.uid,c.shimid=i.shimid,c.bind("Ready",function(){c.trigger("Refresh")},999),c.bind("Change",function(){var t=i.exec.call(c,"FileInput","getFiles");c.files=[],e.each(t,function(e){return 0===e.size?!0:(c.files.push(new a(c.ruid,e)),void 0)})},999),c.bind("Refresh",function(){var t,o,a,s;a=n.get(r.browse_button),s=n.get(i.shimid),a&&(t=n.getPos(a,n.get(r.container)),o=n.getSize(a),s&&e.extend(s.style,{top:t.y+"px",left:t.x+"px",width:o.w+"px",height:o.h+"px"})),s=a=null}),i.exec.call(c,"FileInput","init",r)}),c.connectRuntime(e.extend({},r,{required_caps:{select_file:!0}}))},disable:function(t){var n=this.getRuntime();n&&n.exec.call(this,"FileInput","disable","undefined"===e.typeOf(t)?!0:t)},refresh:function(){c.trigger("Refresh")},destroy:function(){var t=this.getRuntime();t&&(t.exec.call(this,"FileInput","destroy"),this.disconnectRuntime()),"array"===e.typeOf(this.files)&&e.each(this.files,function(e){e.destroy()}),this.files=null}})}var l=["ready","change","cancel","mouseenter","mouseleave","mousedown","mouseup"];return c.prototype=r.instance,c}),i(_,[c,f,p,u,w,v,h,l],function(e,t,n,i,r,o,a,s){function u(n){var a=this,u;"string"==typeof n&&(n={drop_zone:n}),u={accept:[{title:e.translate("All Files"),extensions:"*"}],required_caps:{drag_and_drop:!0}},n="object"==typeof n?i.extend({},u,n):u,n.container=t.get(n.drop_zone)||document.body,"static"===t.getStyle(n.container,"position")&&(n.container.style.position="relative"),"string"==typeof n.accept&&(n.accept=s.mimes2extList(n.accept)),o.call(a),i.extend(a,{uid:i.guid("uid_"),ruid:null,files:null,init:function(){a.convertEventPropsToHandlers(c),a.bind("RuntimeInit",function(e,t){a.ruid=t.uid,a.bind("Drop",function(){var e=t.exec.call(a,"FileDrop","getFiles");a.files=[],i.each(e,function(e){a.files.push(new r(a.ruid,e))})},999),t.exec.call(a,"FileDrop","init",n),a.dispatchEvent("ready")}),a.connectRuntime(n)},destroy:function(){var e=this.getRuntime();e&&(e.exec.call(this,"FileDrop","destroy"),this.disconnectRuntime()),this.files=null}})}var c=["ready","dragenter","dragleave","drop","error"];return u.prototype=a.instance,u}),i(x,[u,v,h],function(e,t,n){function i(){this.uid=e.guid("uid_"),t.call(this),this.destroy=function(){this.disconnectRuntime(),this.unbindAll()}}return i.prototype=n.instance,i}),i(R,[u,m,p,h,y,w,x],function(e,t,n,i,r,o,a){function s(){function i(e,i){function l(e){o.readyState=s.DONE,o.error=e,o.trigger("error"),d()}function d(){c.destroy(),c=null,o.trigger("loadend")}function f(t){c.bind("Error",function(e,t){l(t)}),c.bind("Progress",function(e){o.result=t.exec.call(c,"FileReader","getResult"),o.trigger(e)}),c.bind("Load",function(e){o.readyState=s.DONE,o.result=t.exec.call(c,"FileReader","getResult"),o.trigger(e),d()}),t.exec.call(c,"FileReader","read",e,i)}if(c=new a,this.convertEventPropsToHandlers(u),this.readyState===s.LOADING)return l(new n.DOMException(n.DOMException.INVALID_STATE_ERR));if(this.readyState=s.LOADING,this.trigger("loadstart"),i instanceof r)if(i.isDetached()){var p=i.getSource();switch(e){case"readAsText":case"readAsBinaryString":this.result=p;break;case"readAsDataURL":this.result="data:"+i.type+";base64,"+t.btoa(p)}this.readyState=s.DONE,this.trigger("load"),d()}else f(c.connectRuntime(i.ruid));else l(new n.DOMException(n.DOMException.NOT_FOUND_ERR))}var o=this,c;e.extend(this,{uid:e.guid("uid_"),readyState:s.EMPTY,result:null,error:null,readAsBinaryString:function(e){i.call(this,"readAsBinaryString",e)},readAsDataURL:function(e){i.call(this,"readAsDataURL",e)},readAsText:function(e){i.call(this,"readAsText",e) +},abort:function(){this.result=null,-1===e.inArray(this.readyState,[s.EMPTY,s.DONE])&&(this.readyState===s.LOADING&&(this.readyState=s.DONE),c&&c.getRuntime().exec.call(this,"FileReader","abort"),this.trigger("abort"),this.trigger("loadend"))},destroy:function(){this.abort(),c&&(c.getRuntime().exec.call(this,"FileReader","destroy"),c.disconnectRuntime()),o=c=null}})}var u=["loadstart","progress","load","abort","error","loadend"];return s.EMPTY=0,s.LOADING=1,s.DONE=2,s.prototype=i.instance,s}),i(b,[],function(){var e=function(t,n){for(var i=["source","scheme","authority","userInfo","user","pass","host","port","relative","path","directory","file","query","fragment"],r=i.length,o={http:80,https:443},a={},s=/^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\\?([^#]*))?(?:#(.*))?)/,u=s.exec(t||"");r--;)u[r]&&(a[i[r]]=u[r]);if(!a.scheme){n&&"string"!=typeof n||(n=e(n||document.location.href)),a.scheme=n.scheme,a.host=n.host,a.port=n.port;var c="";/^[^\/]/.test(a.path)&&(c=n.path,/(\/|\/[^\.]+)$/.test(c)?c+="/":c=c.replace(/\/[^\/]+$/,"/")),a.path=c+(a.path||"")}return a.port||(a.port=o[a.scheme]||80),a.port=parseInt(a.port,10),a.path||(a.path="/"),delete a.source,a},t=function(t){var n={http:80,https:443},i=e(t);return i.scheme+"://"+i.host+(i.port!==n[i.scheme]?":"+i.port:"")+i.path+(i.query?i.query:"")},n=function(t){function n(e){return[e.scheme,e.host,e.port].join("/")}return"string"==typeof t&&(t=e(t)),n(e())===n(t)};return{parseUrl:e,resolveUrl:t,hasSameOrigin:n}}),i(T,[u,v,m],function(e,t,n){return function(){function i(e,t){if(!t.isDetached()){var i=this.connectRuntime(t.ruid).exec.call(this,"FileReaderSync","read",e,t);return this.disconnectRuntime(),i}var r=t.getSource();switch(e){case"readAsBinaryString":return r;case"readAsDataURL":return"data:"+t.type+";base64,"+n.btoa(r);case"readAsText":for(var o="",a=0,s=r.length;s>a;a++)o+=String.fromCharCode(r[a]);return o}}t.call(this),e.extend(this,{uid:e.guid("uid_"),readAsBinaryString:function(e){return i.call(this,"readAsBinaryString",e)},readAsDataURL:function(e){return i.call(this,"readAsDataURL",e)},readAsText:function(e){return i.call(this,"readAsText",e)}})}}),i(S,[p,u,y],function(e,t,n){function i(){var e,i=[];t.extend(this,{append:function(r,o){var a=this,s=t.typeOf(o);o instanceof n?e={name:r,value:o}:"array"===s?(r+="[]",t.each(o,function(e){a.append(r,e)})):"object"===s?t.each(o,function(e,t){a.append(r+"["+t+"]",e)}):"null"===s||"undefined"===s||"number"===s&&isNaN(o)?a.append(r,"false"):i.push({name:r,value:o.toString()})},hasBlob:function(){return!!this.getBlob()},getBlob:function(){return e&&e.value||null},getBlobName:function(){return e&&e.name||null},each:function(n){t.each(i,function(e){n(e.value,e.name)}),e&&n(e.value,e.name)},destroy:function(){e=null,i=[]}})}return i}),i(A,[u,p,h,m,b,g,x,y,T,S,d,l],function(e,t,n,i,r,o,a,s,u,c,l,d){function f(){this.uid=e.guid("uid_")}function p(){function n(e,t){return y.hasOwnProperty(e)?1===arguments.length?l.can("define_property")?y[e]:v[e]:(l.can("define_property")?y[e]=t:v[e]=t,void 0):void 0}function u(t){function i(){k.destroy(),k=null,s.dispatchEvent("loadend"),s=null}function r(r){k.bind("LoadStart",function(e){n("readyState",p.LOADING),s.dispatchEvent("readystatechange"),s.dispatchEvent(e),I&&s.upload.dispatchEvent(e)}),k.bind("Progress",function(e){n("readyState")!==p.LOADING&&(n("readyState",p.LOADING),s.dispatchEvent("readystatechange")),s.dispatchEvent(e)}),k.bind("UploadProgress",function(e){I&&s.upload.dispatchEvent({type:"progress",lengthComputable:!1,total:e.total,loaded:e.loaded})}),k.bind("Load",function(t){n("readyState",p.DONE),n("status",Number(r.exec.call(k,"XMLHttpRequest","getStatus")||0)),n("statusText",h[n("status")]||""),n("response",r.exec.call(k,"XMLHttpRequest","getResponse",n("responseType"))),~e.inArray(n("responseType"),["text",""])?n("responseText",n("response")):"document"===n("responseType")&&n("responseXML",n("response")),U=r.exec.call(k,"XMLHttpRequest","getAllResponseHeaders"),s.dispatchEvent("readystatechange"),n("status")>0?(I&&s.upload.dispatchEvent(t),s.dispatchEvent(t)):(N=!0,s.dispatchEvent("error")),i()}),k.bind("Abort",function(e){s.dispatchEvent(e),i()}),k.bind("Error",function(e){N=!0,n("readyState",p.DONE),s.dispatchEvent("readystatechange"),D=!0,s.dispatchEvent(e),i()}),r.exec.call(k,"XMLHttpRequest","send",{url:E,method:_,async:w,user:R,password:b,headers:x,mimeType:S,encoding:T,responseType:s.responseType,withCredentials:s.withCredentials,options:P},t)}var s=this;M=(new Date).getTime(),k=new a,"string"==typeof P.required_caps&&(P.required_caps=o.parseCaps(P.required_caps)),P.required_caps=e.extend({},P.required_caps,{return_response_type:s.responseType}),t instanceof c&&(P.required_caps.send_multipart=!0),L||(P.required_caps.do_cors=!0),P.ruid?r(k.connectRuntime(P)):(k.bind("RuntimeInit",function(e,t){r(t)}),k.bind("RuntimeError",function(e,t){s.dispatchEvent("RuntimeError",t)}),k.connectRuntime(P))}function g(){n("responseText",""),n("responseXML",null),n("response",null),n("status",0),n("statusText",""),M=C=null}var v=this,y={timeout:0,readyState:p.UNSENT,withCredentials:!1,status:0,statusText:"",responseType:"",responseXML:null,responseText:null,response:null},w=!0,E,_,x={},R,b,T=null,S=null,A=!1,O=!1,I=!1,D=!1,N=!1,L=!1,M,C,F=null,H=null,P={},k,U="",B;e.extend(this,y,{uid:e.guid("uid_"),upload:new f,open:function(o,a,s,u,c){var l;if(!o||!a)throw new t.DOMException(t.DOMException.SYNTAX_ERR);if(/[\u0100-\uffff]/.test(o)||i.utf8_encode(o)!==o)throw new t.DOMException(t.DOMException.SYNTAX_ERR);if(~e.inArray(o.toUpperCase(),["CONNECT","DELETE","GET","HEAD","OPTIONS","POST","PUT","TRACE","TRACK"])&&(_=o.toUpperCase()),~e.inArray(_,["CONNECT","TRACE","TRACK"]))throw new t.DOMException(t.DOMException.SECURITY_ERR);if(a=i.utf8_encode(a),l=r.parseUrl(a),L=r.hasSameOrigin(l),E=r.resolveUrl(a),(u||c)&&!L)throw new t.DOMException(t.DOMException.INVALID_ACCESS_ERR);if(R=u||l.user,b=c||l.pass,w=s||!0,w===!1&&(n("timeout")||n("withCredentials")||""!==n("responseType")))throw new t.DOMException(t.DOMException.INVALID_ACCESS_ERR);A=!w,O=!1,x={},g.call(this),n("readyState",p.OPENED),this.convertEventPropsToHandlers(["readystatechange"]),this.dispatchEvent("readystatechange")},setRequestHeader:function(r,o){var a=["accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","cookie","cookie2","content-transfer-encoding","date","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","user-agent","via"];if(n("readyState")!==p.OPENED||O)throw new t.DOMException(t.DOMException.INVALID_STATE_ERR);if(/[\u0100-\uffff]/.test(r)||i.utf8_encode(r)!==r)throw new t.DOMException(t.DOMException.SYNTAX_ERR);return r=e.trim(r).toLowerCase(),~e.inArray(r,a)||/^(proxy\-|sec\-)/.test(r)?!1:(x[r]?x[r]+=", "+o:x[r]=o,!0)},getAllResponseHeaders:function(){return U||""},getResponseHeader:function(t){return t=t.toLowerCase(),N||~e.inArray(t,["set-cookie","set-cookie2"])?null:U&&""!==U&&(B||(B={},e.each(U.split(/\r\n/),function(t){var n=t.split(/:\s+/);2===n.length&&(n[0]=e.trim(n[0]),B[n[0].toLowerCase()]={header:n[0],value:e.trim(n[1])})})),B.hasOwnProperty(t))?B[t].header+": "+B[t].value:null},overrideMimeType:function(i){var r,o;if(~e.inArray(n("readyState"),[p.LOADING,p.DONE]))throw new t.DOMException(t.DOMException.INVALID_STATE_ERR);if(i=e.trim(i.toLowerCase()),/;/.test(i)&&(r=i.match(/^([^;]+)(?:;\scharset\=)?(.*)$/))&&(i=r[1],r[2]&&(o=r[2])),!d.mimes[i])throw new t.DOMException(t.DOMException.SYNTAX_ERR);F=i,H=o},send:function(n,r){if(P="string"===e.typeOf(r)?{ruid:r}:r?r:{},this.convertEventPropsToHandlers(m),this.upload.convertEventPropsToHandlers(m),this.readyState!==p.OPENED||O)throw new t.DOMException(t.DOMException.INVALID_STATE_ERR);if(n instanceof s)P.ruid=n.ruid,S=n.type||"application/octet-stream";else if(n instanceof c){if(n.hasBlob()){var o=n.getBlob();P.ruid=o.ruid,S=o.type||"application/octet-stream"}}else"string"==typeof n&&(T="UTF-8",S="text/plain;charset=UTF-8",n=i.utf8_encode(n));this.withCredentials||(this.withCredentials=P.required_caps&&P.required_caps.send_browser_cookies&&!L),I=!A&&this.upload.hasEventListener(),N=!1,D=!n,A||(O=!0),u.call(this,n)},abort:function(){if(N=!0,A=!1,~e.inArray(n("readyState"),[p.UNSENT,p.OPENED,p.DONE]))n("readyState",p.UNSENT);else{if(n("readyState",p.DONE),O=!1,!k)throw new t.DOMException(t.DOMException.INVALID_STATE_ERR);k.getRuntime().exec.call(k,"XMLHttpRequest","abort",D),D=!0}},destroy:function(){k&&("function"===e.typeOf(k.destroy)&&k.destroy(),k=null),this.unbindAll(),this.upload&&(this.upload.unbindAll(),this.upload=null)}})}var h={100:"Continue",101:"Switching Protocols",102:"Processing",200:"OK",201:"Created",202:"Accepted",203:"Non-Authoritative Information",204:"No Content",205:"Reset Content",206:"Partial Content",207:"Multi-Status",226:"IM Used",300:"Multiple Choices",301:"Moved Permanently",302:"Found",303:"See Other",304:"Not Modified",305:"Use Proxy",306:"Reserved",307:"Temporary Redirect",400:"Bad Request",401:"Unauthorized",402:"Payment Required",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",406:"Not Acceptable",407:"Proxy Authentication Required",408:"Request Timeout",409:"Conflict",410:"Gone",411:"Length Required",412:"Precondition Failed",413:"Request Entity Too Large",414:"Request-URI Too Long",415:"Unsupported Media Type",416:"Requested Range Not Satisfiable",417:"Expectation Failed",422:"Unprocessable Entity",423:"Locked",424:"Failed Dependency",426:"Upgrade Required",500:"Internal Server Error",501:"Not Implemented",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout",505:"HTTP Version Not Supported",506:"Variant Also Negotiates",507:"Insufficient Storage",510:"Not Extended"};f.prototype=n.instance;var m=["loadstart","progress","abort","error","load","timeout","loadend"],g=1,v=2;return p.UNSENT=0,p.OPENED=1,p.HEADERS_RECEIVED=2,p.LOADING=3,p.DONE=4,p.prototype=n.instance,p}),i(O,[u,m,v,h],function(e,t,n,i){function r(){function i(){l=d=0,c=this.result=null}function o(t,n){var i=this;u=n,i.bind("TransportingProgress",function(t){d=t.loaded,l>d&&-1===e.inArray(i.state,[r.IDLE,r.DONE])&&a.call(i)},999),i.bind("TransportingComplete",function(){d=l,i.state=r.DONE,c=null,i.result=u.exec.call(i,"Transporter","getAsBlob",t||"")},999),i.state=r.BUSY,i.trigger("TransportingStarted"),a.call(i)}function a(){var e=this,n,i=l-d;f>i&&(f=i),n=t.btoa(c.substr(d,f)),u.exec.call(e,"Transporter","receive",n,l)}var s,u,c,l,d,f;n.call(this),e.extend(this,{uid:e.guid("uid_"),state:r.IDLE,result:null,transport:function(t,n,r){var a=this;if(r=e.extend({chunk_size:204798},r),(s=r.chunk_size%3)&&(r.chunk_size+=3-s),f=r.chunk_size,i.call(this),c=t,l=t.length,"string"===e.typeOf(r)||r.ruid)o.call(a,n,this.connectRuntime(r));else{var u=function(e,t){a.unbind("RuntimeInit",u),o.call(a,n,t)};this.bind("RuntimeInit",u),this.connectRuntime(r)}},abort:function(){var e=this;e.state=r.IDLE,u&&(u.exec.call(e,"Transporter","clear"),e.trigger("TransportingAborted")),i.call(e)},destroy:function(){this.unbindAll(),u=null,this.disconnectRuntime(),i.call(this)}})}return r.IDLE=0,r.BUSY=1,r.DONE=2,r.prototype=i.instance,r}),i(I,[u,f,p,T,A,g,v,O,d,h,y,w,m],function(e,t,n,i,r,o,a,s,u,c,l,d,f){function p(){function i(e){e||(e=this.getRuntime().exec.call(this,"Image","getInfo")),this.size=e.size,this.width=e.width,this.height=e.height,this.type=e.type,this.meta=e.meta,""===this.name&&(this.name=e.name)}function c(t){var i=e.typeOf(t);try{if(t instanceof p){if(!t.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);m.apply(this,arguments)}else if(t instanceof l){if(!~e.inArray(t.type,["image/jpeg","image/png"]))throw new n.ImageError(n.ImageError.WRONG_FORMAT);g.apply(this,arguments)}else if(-1!==e.inArray(i,["blob","file"]))c.call(this,new d(null,t),arguments[1]);else if("string"===i)/^data:[^;]*;base64,/.test(t)?c.call(this,new l(null,{data:t}),arguments[1]):v.apply(this,arguments);else{if("node"!==i||"img"!==t.nodeName.toLowerCase())throw new n.DOMException(n.DOMException.TYPE_MISMATCH_ERR);c.call(this,t.src,arguments[1])}}catch(r){this.trigger("error",r)}}function m(t,n){var i=this.connectRuntime(t.ruid);this.ruid=i.uid,i.exec.call(this,"Image","loadFromImage",t,"undefined"===e.typeOf(n)?!0:n)}function g(t,n){function i(e){r.ruid=e.uid,e.exec.call(r,"Image","loadFromBlob",t)}var r=this;r.name=t.name||"",t.isDetached()?(this.bind("RuntimeInit",function(e,t){i(t)}),n&&"string"==typeof n.required_caps&&(n.required_caps=o.parseCaps(n.required_caps)),this.connectRuntime(e.extend({required_caps:{access_image_binary:!0,resize_image:!0}},n))):i(this.connectRuntime(t.ruid))}function v(e,t){var n=this,i;i=new r,i.open("get",e),i.responseType="blob",i.onprogress=function(e){n.trigger(e)},i.onload=function(){g.call(n,i.response,!0)},i.onerror=function(e){n.trigger(e)},i.onloadend=function(){i.destroy()},i.bind("RuntimeError",function(e,t){n.trigger("RuntimeError",t)}),i.send(null,t)}a.call(this),e.extend(this,{uid:e.guid("uid_"),ruid:null,name:"",size:0,width:0,height:0,type:"",meta:{},clone:function(){this.load.apply(this,arguments)},load:function(){this.bind("Load Resize",function(){i.call(this)},999),this.convertEventPropsToHandlers(h),c.apply(this,arguments)},downsize:function(t,i,r,o){try{if(!this.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);if(this.width>p.MAX_RESIZE_WIDTH||this.height>p.MAX_RESIZE_HEIGHT)throw new n.ImageError(n.ImageError.MAX_RESOLUTION_ERR);(!t&&!i||"undefined"===e.typeOf(r))&&(r=!1),t=t||this.width,i=i||this.height,o="undefined"===e.typeOf(o)?!0:!!o,this.getRuntime().exec.call(this,"Image","downsize",t,i,r,o)}catch(a){this.trigger("error",a)}},crop:function(e,t,n){this.downsize(e,t,!0,n)},getAsCanvas:function(){if(!u.can("create_canvas"))throw new n.RuntimeError(n.RuntimeError.NOT_SUPPORTED_ERR);var e=this.connectRuntime(this.ruid);return e.exec.call(this,"Image","getAsCanvas")},getAsBlob:function(e,t){if(!this.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);return e||(e="image/jpeg"),"image/jpeg"!==e||t||(t=90),this.getRuntime().exec.call(this,"Image","getAsBlob",e,t)},getAsDataURL:function(e,t){if(!this.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);return this.getRuntime().exec.call(this,"Image","getAsDataURL",e,t)},getAsBinaryString:function(e,t){var n=this.getAsDataURL(e,t);return f.atob(n.substring(n.indexOf("base64,")+7))},embed:function(i){function r(){if(u.can("create_canvas")){var t=a.getAsCanvas();if(t)return i.appendChild(t),t=null,a.destroy(),o.trigger("embedded"),void 0}var r=a.getAsDataURL(c,l);if(!r)throw new n.ImageError(n.ImageError.WRONG_FORMAT);if(u.can("use_data_uri_of",r.length))i.innerHTML='<img src="'+r+'" width="'+a.width+'" height="'+a.height+'" />',a.destroy(),o.trigger("embedded");else{var d=new s;d.bind("TransportingComplete",function(){v=o.connectRuntime(this.result.ruid),o.bind("Embedded",function(){e.extend(v.getShimContainer().style,{top:"0px",left:"0px",width:a.width+"px",height:a.height+"px"}),v=null},999),v.exec.call(o,"ImageView","display",this.result.uid,m,g),a.destroy()}),d.transport(f.atob(r.substring(r.indexOf("base64,")+7)),c,e.extend({},h,{required_caps:{display_media:!0},runtime_order:"flash,silverlight",container:i}))}}var o=this,a,c,l,d,h=arguments[1]||{},m=this.width,g=this.height,v;try{if(!(i=t.get(i)))throw new n.DOMException(n.DOMException.INVALID_NODE_TYPE_ERR);if(!this.size)throw new n.DOMException(n.DOMException.INVALID_STATE_ERR);if(this.width>p.MAX_RESIZE_WIDTH||this.height>p.MAX_RESIZE_HEIGHT)throw new n.ImageError(n.ImageError.MAX_RESOLUTION_ERR);if(c=h.type||this.type||"image/jpeg",l=h.quality||90,d="undefined"!==e.typeOf(h.crop)?h.crop:!1,h.width)m=h.width,g=h.height||m;else{var y=t.getSize(i);y.w&&y.h&&(m=y.w,g=y.h)}return a=new p,a.bind("Resize",function(){r.call(o)}),a.bind("Load",function(){a.downsize(m,g,d,!1)}),a.clone(this,!1),a}catch(w){this.trigger("error",w)}},destroy:function(){this.ruid&&(this.getRuntime().exec.call(this,"Image","destroy"),this.disconnectRuntime()),this.unbindAll()}})}var h=["progress","load","error","resize","embedded"];return p.MAX_RESIZE_WIDTH=6500,p.MAX_RESIZE_HEIGHT=6500,p.prototype=c.instance,p}),i(D,[u,p,g,d],function(e,t,n,i){function r(t){var r=this,s=n.capTest,u=n.capTrue,c=e.extend({access_binary:s(window.FileReader||window.File&&window.File.getAsDataURL),access_image_binary:function(){return r.can("access_binary")&&!!a.Image},display_media:s(i.can("create_canvas")||i.can("use_data_uri_over32kb")),do_cors:s(window.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest),drag_and_drop:s(function(){var e=document.createElement("div");return("draggable"in e||"ondragstart"in e&&"ondrop"in e)&&("IE"!==i.browser||i.version>9)}()),filter_by_extension:s(function(){return"Chrome"===i.browser&&i.version>=28||"IE"===i.browser&&i.version>=10}()),return_response_headers:u,return_response_type:function(e){return"json"===e&&window.JSON?!0:i.can("return_response_type",e)},return_status_code:u,report_upload_progress:s(window.XMLHttpRequest&&(new XMLHttpRequest).upload),resize_image:function(){return r.can("access_binary")&&i.can("create_canvas")},select_file:function(){return i.can("use_fileinput")&&window.File},select_folder:function(){return r.can("select_file")&&"Chrome"===i.browser&&i.version>=21},select_multiple:function(){return!(!r.can("select_file")||"Safari"===i.browser&&"Windows"===i.os||"iOS"===i.os&&i.verComp(i.osVersion,"7.0.4","<"))},send_binary_string:s(window.XMLHttpRequest&&((new XMLHttpRequest).sendAsBinary||window.Uint8Array&&window.ArrayBuffer)),send_custom_headers:s(window.XMLHttpRequest),send_multipart:function(){return!!(window.XMLHttpRequest&&(new XMLHttpRequest).upload&&window.FormData)||r.can("send_binary_string")},slice_blob:s(window.File&&(File.prototype.mozSlice||File.prototype.webkitSlice||File.prototype.slice)),stream_upload:function(){return r.can("slice_blob")&&r.can("send_multipart")},summon_file_dialog:s(function(){return"Firefox"===i.browser&&i.version>=4||"Opera"===i.browser&&i.version>=12||"IE"===i.browser&&i.version>=10||!!~e.inArray(i.browser,["Chrome","Safari"])}()),upload_filesize:u},arguments[2]);n.call(this,t,arguments[1]||o,c),e.extend(this,{init:function(){this.trigger("Init")},destroy:function(e){return function(){e.call(r),e=r=null}}(this.destroy)}),e.extend(this.getShim(),a)}var o="html5",a={};return n.addConstructor(o,r),a}),i(N,[D,y],function(e,t){function n(){function e(e,t,n){var i;if(!window.File.prototype.slice)return(i=window.File.prototype.webkitSlice||window.File.prototype.mozSlice)?i.call(e,t,n):null;try{return e.slice(),e.slice(t,n)}catch(r){return e.slice(t,n-t)}}this.slice=function(){return new t(this.getRuntime().uid,e.apply(this,arguments))}}return e.Blob=n}),i(L,[u],function(e){function t(){this.returnValue=!1}function n(){this.cancelBubble=!0}var i={},r="moxie_"+e.guid(),o=function(o,a,s,u){var c,l;a=a.toLowerCase(),o.addEventListener?(c=s,o.addEventListener(a,c,!1)):o.attachEvent&&(c=function(){var e=window.event;e.target||(e.target=e.srcElement),e.preventDefault=t,e.stopPropagation=n,s(e)},o.attachEvent("on"+a,c)),o[r]||(o[r]=e.guid()),i.hasOwnProperty(o[r])||(i[o[r]]={}),l=i[o[r]],l.hasOwnProperty(a)||(l[a]=[]),l[a].push({func:c,orig:s,key:u})},a=function(t,n,o){var a,s;if(n=n.toLowerCase(),t[r]&&i[t[r]]&&i[t[r]][n]){a=i[t[r]][n];for(var u=a.length-1;u>=0&&(a[u].orig!==o&&a[u].key!==o||(t.removeEventListener?t.removeEventListener(n,a[u].func,!1):t.detachEvent&&t.detachEvent("on"+n,a[u].func),a[u].orig=null,a[u].func=null,a.splice(u,1),o===s));u--);if(a.length||delete i[t[r]][n],e.isEmptyObj(i[t[r]])){delete i[t[r]];try{delete t[r]}catch(c){t[r]=s}}}},s=function(t,n){t&&t[r]&&e.each(i[t[r]],function(e,i){a(t,i,n)})};return{addEvent:o,removeEvent:a,removeAllEvents:s}}),i(M,[D,u,f,L,l,d],function(e,t,n,i,r,o){function a(){var e=[],a;t.extend(this,{init:function(s){var u=this,c=u.getRuntime(),l,d,f,p,h,m;a=s,e=[],f=a.accept.mimes||r.extList2mimes(a.accept,c.can("filter_by_extension")),d=c.getShimContainer(),d.innerHTML='<input id="'+c.uid+'" type="file" style="font-size:999px;opacity:0;"'+(a.multiple&&c.can("select_multiple")?"multiple":"")+(a.directory&&c.can("select_folder")?"webkitdirectory directory":"")+(f?' accept="'+f.join(",")+'"':"")+" />",l=n.get(c.uid),t.extend(l.style,{position:"absolute",top:0,left:0,width:"100%",height:"100%"}),p=n.get(a.browse_button),c.can("summon_file_dialog")&&("static"===n.getStyle(p,"position")&&(p.style.position="relative"),h=parseInt(n.getStyle(p,"z-index"),10)||1,p.style.zIndex=h,d.style.zIndex=h-1,i.addEvent(p,"click",function(e){var t=n.get(c.uid);t&&!t.disabled&&t.click(),e.preventDefault()},u.uid)),m=c.can("summon_file_dialog")?p:d,i.addEvent(m,"mouseover",function(){u.trigger("mouseenter")},u.uid),i.addEvent(m,"mouseout",function(){u.trigger("mouseleave")},u.uid),i.addEvent(m,"mousedown",function(){u.trigger("mousedown")},u.uid),i.addEvent(n.get(a.container),"mouseup",function(){u.trigger("mouseup")},u.uid),l.onchange=function g(){if(e=[],a.directory?t.each(this.files,function(t){"."!==t.name&&e.push(t)}):e=[].slice.call(this.files),"IE"!==o.browser)this.value="";else{var n=this.cloneNode(!0);this.parentNode.replaceChild(n,this),n.onchange=g}u.trigger("change")},u.trigger({type:"ready",async:!0}),d=null},getFiles:function(){return e},disable:function(e){var t=this.getRuntime(),i;(i=n.get(t.uid))&&(i.disabled=!!e)},destroy:function(){var t=this.getRuntime(),r=t.getShim(),o=t.getShimContainer();i.removeAllEvents(o,this.uid),i.removeAllEvents(a&&n.get(a.container),this.uid),i.removeAllEvents(a&&n.get(a.browse_button),this.uid),o&&(o.innerHTML=""),r.removeInstance(this.uid),e=a=o=r=null}})}return e.FileInput=a}),i(C,[D,u,f,L,l],function(e,t,n,i,r){function o(){function e(e){for(var n=[],i=0;i<e.length;i++)[].push.apply(n,e[i].extensions.split(/\s*,\s*/));return-1===t.inArray("*",n)?n:[]}function o(e){var n=r.getFileExtension(e.name);return!n||!d.length||-1!==t.inArray(n,d)}function a(e,n){var i=[];t.each(e,function(e){var t=e.webkitGetAsEntry();if(t)if(t.isFile){var n=e.getAsFile();o(n)&&l.push(n)}else i.push(t)}),i.length?s(i,n):n()}function s(e,n){var i=[];t.each(e,function(e){i.push(function(t){u(e,t)})}),t.inSeries(i,function(){n()})}function u(e,t){e.isFile?e.file(function(e){o(e)&&l.push(e),t()},function(){t()}):e.isDirectory?c(e,t):t()}function c(e,t){function n(e){r.readEntries(function(t){t.length?([].push.apply(i,t),n(e)):e()},e)}var i=[],r=e.createReader();n(function(){s(i,t)})}var l=[],d=[],f;t.extend(this,{init:function(n){var r=this,s;f=n,d=e(f.accept),s=f.container,i.addEvent(s,"dragover",function(e){e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="copy"},r.uid),i.addEvent(s,"drop",function(e){e.preventDefault(),e.stopPropagation(),l=[],e.dataTransfer.items&&e.dataTransfer.items[0].webkitGetAsEntry?a(e.dataTransfer.items,function(){r.trigger("drop")}):(t.each(e.dataTransfer.files,function(e){o(e)&&l.push(e)}),r.trigger("drop"))},r.uid),i.addEvent(s,"dragenter",function(e){e.preventDefault(),e.stopPropagation(),r.trigger("dragenter")},r.uid),i.addEvent(s,"dragleave",function(e){e.preventDefault(),e.stopPropagation(),r.trigger("dragleave")},r.uid)},getFiles:function(){return l},destroy:function(){i.removeAllEvents(f&&n.get(f.container),this.uid),l=d=f=null}})}return e.FileDrop=o}),i(F,[D,m,u],function(e,t,n){function i(){function e(e){return t.atob(e.substring(e.indexOf("base64,")+7))}var i,r=!1;n.extend(this,{read:function(e,t){var o=this;i=new window.FileReader,i.addEventListener("progress",function(e){o.trigger(e)}),i.addEventListener("load",function(e){o.trigger(e)}),i.addEventListener("error",function(e){o.trigger(e,i.error)}),i.addEventListener("loadend",function(){i=null}),"function"===n.typeOf(i[e])?(r=!1,i[e](t.getSource())):"readAsBinaryString"===e&&(r=!0,i.readAsDataURL(t.getSource()))},getResult:function(){return i&&i.result?r?e(i.result):i.result:null},abort:function(){i&&i.abort()},destroy:function(){i=null}})}return e.FileReader=i}),i(H,[D,u,l,b,w,y,S,p,d],function(e,t,n,i,r,o,a,s,u){function c(){function e(e,t){var n=this,i,r;i=t.getBlob().getSource(),r=new window.FileReader,r.onload=function(){t.append(t.getBlobName(),new o(null,{type:i.type,data:r.result})),f.send.call(n,e,t)},r.readAsBinaryString(i)}function c(){return!window.XMLHttpRequest||"IE"===u.browser&&u.version<8?function(){for(var e=["Msxml2.XMLHTTP.6.0","Microsoft.XMLHTTP"],t=0;t<e.length;t++)try{return new ActiveXObject(e[t])}catch(n){}}():new window.XMLHttpRequest}function l(e){var t=e.responseXML,n=e.responseText;return"IE"===u.browser&&n&&t&&!t.documentElement&&/[^\/]+\/[^\+]+\+xml/.test(e.getResponseHeader("Content-Type"))&&(t=new window.ActiveXObject("Microsoft.XMLDOM"),t.async=!1,t.validateOnParse=!1,t.loadXML(n)),t&&("IE"===u.browser&&0!==t.parseError||!t.documentElement||"parsererror"===t.documentElement.tagName)?null:t}function d(e){var t="----moxieboundary"+(new Date).getTime(),n="--",i="\r\n",r="",a=this.getRuntime();if(!a.can("send_binary_string"))throw new s.RuntimeError(s.RuntimeError.NOT_SUPPORTED_ERR);return p.setRequestHeader("Content-Type","multipart/form-data; boundary="+t),e.each(function(e,a){r+=e instanceof o?n+t+i+'Content-Disposition: form-data; name="'+a+'"; filename="'+unescape(encodeURIComponent(e.name||"blob"))+'"'+i+"Content-Type: "+(e.type||"application/octet-stream")+i+i+e.getSource()+i:n+t+i+'Content-Disposition: form-data; name="'+a+'"'+i+i+unescape(encodeURIComponent(e))+i}),r+=n+t+n+i}var f=this,p,h;t.extend(this,{send:function(n,r){var s=this,l="Mozilla"===u.browser&&u.version>=4&&u.version<7,f="Android Browser"===u.browser,m=!1;if(h=n.url.replace(/^.+?\/([\w\-\.]+)$/,"$1").toLowerCase(),p=c(),p.open(n.method,n.url,n.async,n.user,n.password),r instanceof o)r.isDetached()&&(m=!0),r=r.getSource();else if(r instanceof a){if(r.hasBlob())if(r.getBlob().isDetached())r=d.call(s,r),m=!0;else if((l||f)&&"blob"===t.typeOf(r.getBlob().getSource())&&window.FileReader)return e.call(s,n,r),void 0;if(r instanceof a){var g=new window.FormData;r.each(function(e,t){e instanceof o?g.append(t,e.getSource()):g.append(t,e)}),r=g}}p.upload?(n.withCredentials&&(p.withCredentials=!0),p.addEventListener("load",function(e){s.trigger(e)}),p.addEventListener("error",function(e){s.trigger(e)}),p.addEventListener("progress",function(e){s.trigger(e)}),p.upload.addEventListener("progress",function(e){s.trigger({type:"UploadProgress",loaded:e.loaded,total:e.total})})):p.onreadystatechange=function v(){switch(p.readyState){case 1:break;case 2:break;case 3:var e,t;try{i.hasSameOrigin(n.url)&&(e=p.getResponseHeader("Content-Length")||0),p.responseText&&(t=p.responseText.length)}catch(r){e=t=0}s.trigger({type:"progress",lengthComputable:!!e,total:parseInt(e,10),loaded:t});break;case 4:p.onreadystatechange=function(){},0===p.status?s.trigger("error"):s.trigger("load")}},t.isEmptyObj(n.headers)||t.each(n.headers,function(e,t){p.setRequestHeader(t,e)}),""!==n.responseType&&"responseType"in p&&(p.responseType="json"!==n.responseType||u.can("return_response_type","json")?n.responseType:"text"),m?p.sendAsBinary?p.sendAsBinary(r):function(){for(var e=new Uint8Array(r.length),t=0;t<r.length;t++)e[t]=255&r.charCodeAt(t);p.send(e.buffer)}():p.send(r),s.trigger("loadstart")},getStatus:function(){try{if(p)return p.status}catch(e){}return 0},getResponse:function(e){var t=this.getRuntime();try{switch(e){case"blob":var i=new r(t.uid,p.response),o=p.getResponseHeader("Content-Disposition");if(o){var a=o.match(/filename=([\'\"'])([^\1]+)\1/);a&&(h=a[2])}return i.name=h,i.type||(i.type=n.getFileMime(h)),i;case"json":return u.can("return_response_type","json")?p.response:200===p.status&&window.JSON?JSON.parse(p.responseText):null;case"document":return l(p);default:return""!==p.responseText?p.responseText:null}}catch(s){return null}},getAllResponseHeaders:function(){try{return p.getAllResponseHeaders()}catch(e){}return""},abort:function(){p&&p.abort()},destroy:function(){f=h=null}})}return e.XMLHttpRequest=c}),i(P,[],function(){return function(){function e(e,t){var n=r?0:-8*(t-1),i=0,a;for(a=0;t>a;a++)i|=o.charCodeAt(e+a)<<Math.abs(n+8*a);return i}function n(e,t,n){n=3===arguments.length?n:o.length-t-1,o=o.substr(0,t)+e+o.substr(n+t)}function i(e,t,i){var o="",a=r?0:-8*(i-1),s;for(s=0;i>s;s++)o+=String.fromCharCode(255&t>>Math.abs(a+8*s));n(o,e,i)}var r=!1,o;return{II:function(e){return e===t?r:(r=e,void 0)},init:function(e){r=!1,o=e},SEGMENT:function(e,t,i){switch(arguments.length){case 1:return o.substr(e,o.length-e-1);case 2:return o.substr(e,t);case 3:n(i,e,t);break;default:return o}},BYTE:function(t){return e(t,1)},SHORT:function(t){return e(t,2)},LONG:function(n,r){return r===t?e(n,4):(i(n,r,4),void 0)},SLONG:function(t){var n=e(t,4);return n>2147483647?n-4294967296:n},STRING:function(t,n){var i="";for(n+=t;n>t;t++)i+=String.fromCharCode(e(t,1));return i}}}}),i(k,[P],function(e){return function t(n){var i=[],r,o,a,s=0;if(r=new e,r.init(n),65496===r.SHORT(0)){for(o=2;o<=n.length;)if(a=r.SHORT(o),a>=65488&&65495>=a)o+=2;else{if(65498===a||65497===a)break;s=r.SHORT(o+2)+2,a>=65505&&65519>=a&&i.push({hex:a,name:"APP"+(15&a),start:o,length:s,segment:r.SEGMENT(o,s)}),o+=s}return r.init(null),{headers:i,restore:function(e){var t,n;for(r.init(e),o=65504==r.SHORT(2)?4+r.SHORT(4):2,n=0,t=i.length;t>n;n++)r.SEGMENT(o,0,i[n].segment),o+=i[n].length;return e=r.SEGMENT(),r.init(null),e},strip:function(e){var n,i,o;for(i=new t(e),n=i.headers,i.purge(),r.init(e),o=n.length;o--;)r.SEGMENT(n[o].start,n[o].length,"");return e=r.SEGMENT(),r.init(null),e},get:function(e){for(var t=[],n=0,r=i.length;r>n;n++)i[n].name===e.toUpperCase()&&t.push(i[n].segment);return t},set:function(e,t){var n=[],r,o,a;for("string"==typeof t?n.push(t):n=t,r=o=0,a=i.length;a>r&&(i[r].name===e.toUpperCase()&&(i[r].segment=n[o],i[r].length=n[o].length,o++),!(o>=n.length));r++);},purge:function(){i=[],r.init(null),r=null}}}}}),i(U,[u,P],function(e,n){return function i(){function i(e,n){var i=a.SHORT(e),r,o,s,u,d,f,p,h,m=[],g={};for(r=0;i>r;r++)if(p=f=e+12*r+2,s=n[a.SHORT(p)],s!==t){switch(u=a.SHORT(p+=2),d=a.LONG(p+=2),p+=4,m=[],u){case 1:case 7:for(d>4&&(p=a.LONG(p)+c.tiffHeader),o=0;d>o;o++)m[o]=a.BYTE(p+o);break;case 2:d>4&&(p=a.LONG(p)+c.tiffHeader),g[s]=a.STRING(p,d-1);continue;case 3:for(d>2&&(p=a.LONG(p)+c.tiffHeader),o=0;d>o;o++)m[o]=a.SHORT(p+2*o);break;case 4:for(d>1&&(p=a.LONG(p)+c.tiffHeader),o=0;d>o;o++)m[o]=a.LONG(p+4*o);break;case 5:for(p=a.LONG(p)+c.tiffHeader,o=0;d>o;o++)m[o]=a.LONG(p+4*o)/a.LONG(p+4*o+4);break;case 9:for(p=a.LONG(p)+c.tiffHeader,o=0;d>o;o++)m[o]=a.SLONG(p+4*o);break;case 10:for(p=a.LONG(p)+c.tiffHeader,o=0;d>o;o++)m[o]=a.SLONG(p+4*o)/a.SLONG(p+4*o+4);break;default:continue}h=1==d?m[0]:m,g[s]=l.hasOwnProperty(s)&&"object"!=typeof h?l[s][h]:h}return g}function r(){var e=c.tiffHeader;return a.II(18761==a.SHORT(e)),42!==a.SHORT(e+=2)?!1:(c.IFD0=c.tiffHeader+a.LONG(e+=2),u=i(c.IFD0,s.tiff),"ExifIFDPointer"in u&&(c.exifIFD=c.tiffHeader+u.ExifIFDPointer,delete u.ExifIFDPointer),"GPSInfoIFDPointer"in u&&(c.gpsIFD=c.tiffHeader+u.GPSInfoIFDPointer,delete u.GPSInfoIFDPointer),!0)}function o(e,t,n){var i,r,o,u=0;if("string"==typeof t){var l=s[e.toLowerCase()];for(var d in l)if(l[d]===t){t=d;break}}i=c[e.toLowerCase()+"IFD"],r=a.SHORT(i);for(var f=0;r>f;f++)if(o=i+12*f+2,a.SHORT(o)==t){u=o+8;break}return u?(a.LONG(u,n),!0):!1}var a,s,u,c={},l;return a=new n,s={tiff:{274:"Orientation",270:"ImageDescription",271:"Make",272:"Model",305:"Software",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer"},exif:{36864:"ExifVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",36867:"DateTimeOriginal",33434:"ExposureTime",33437:"FNumber",34855:"ISOSpeedRatings",37377:"ShutterSpeedValue",37378:"ApertureValue",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37386:"FocalLength",41986:"ExposureMode",41987:"WhiteBalance",41990:"SceneCaptureType",41988:"DigitalZoomRatio",41992:"Contrast",41993:"Saturation",41994:"Sharpness"},gps:{0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude"}},l={ColorSpace:{1:"sRGB",0:"Uncalibrated"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{1:"Daylight",2:"Fliorescent",3:"Tungsten",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 -5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire.",1:"Flash fired.",5:"Strobe return light not detected.",7:"Strobe return light detected.",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},ExposureMode:{0:"Auto exposure",1:"Manual exposure",2:"Auto bracket"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},GPSLatitudeRef:{N:"North latitude",S:"South latitude"},GPSLongitudeRef:{E:"East longitude",W:"West longitude"}},{init:function(e){return c={tiffHeader:10},e!==t&&e.length?(a.init(e),65505===a.SHORT(0)&&"EXIF\0"===a.STRING(4,5).toUpperCase()?r():!1):!1 +},TIFF:function(){return u},EXIF:function(){var t;if(t=i(c.exifIFD,s.exif),t.ExifVersion&&"array"===e.typeOf(t.ExifVersion)){for(var n=0,r="";n<t.ExifVersion.length;n++)r+=String.fromCharCode(t.ExifVersion[n]);t.ExifVersion=r}return t},GPS:function(){var t;return t=i(c.gpsIFD,s.gps),t.GPSVersionID&&"array"===e.typeOf(t.GPSVersionID)&&(t.GPSVersionID=t.GPSVersionID.join(".")),t},setExif:function(e,t){return"PixelXDimension"!==e&&"PixelYDimension"!==e?!1:o("exif",e,t)},getBinary:function(){return a.SEGMENT()},purge:function(){a.init(null),a=u=null,c={}}}}}),i(B,[u,p,k,P,U],function(e,t,n,i,r){function o(o){function a(){for(var e=0,t,n;e<=u.length;){if(t=c.SHORT(e+=2),t>=65472&&65475>=t)return e+=5,{height:c.SHORT(e),width:c.SHORT(e+=2)};n=c.SHORT(e+=2),e+=n-2}return null}function s(){d&&l&&c&&(d.purge(),l.purge(),c.init(null),u=f=l=d=c=null)}var u,c,l,d,f,p;if(u=o,c=new i,c.init(u),65496!==c.SHORT(0))throw new t.ImageError(t.ImageError.WRONG_FORMAT);l=new n(o),d=new r,p=!!d.init(l.get("app1")[0]),f=a.call(this),e.extend(this,{type:"image/jpeg",size:u.length,width:f&&f.width||0,height:f&&f.height||0,setExif:function(t,n){return p?("object"===e.typeOf(t)?e.each(t,function(e,t){d.setExif(t,e)}):d.setExif(t,n),l.set("app1",d.getBinary()),void 0):!1},writeHeaders:function(){return arguments.length?l.restore(arguments[0]):u=l.restore(u)},stripHeaders:function(e){return l.strip(e)},purge:function(){s.call(this)}}),p&&(this.meta={tiff:d.TIFF(),exif:d.EXIF(),gps:d.GPS()})}return o}),i(z,[p,u,P],function(e,t,n){function i(i){function r(){var e,t;return e=a.call(this,8),"IHDR"==e.type?(t=e.start,{width:u.LONG(t),height:u.LONG(t+=4)}):null}function o(){u&&(u.init(null),s=d=c=l=u=null)}function a(e){var t,n,i,r;return t=u.LONG(e),n=u.STRING(e+=4,4),i=e+=4,r=u.LONG(e+t),{length:t,type:n,start:i,CRC:r}}var s,u,c,l,d;s=i,u=new n,u.init(s),function(){var t=0,n=0,i=[35152,20039,3338,6666];for(n=0;n<i.length;n++,t+=2)if(i[n]!=u.SHORT(t))throw new e.ImageError(e.ImageError.WRONG_FORMAT)}(),d=r.call(this),t.extend(this,{type:"image/png",size:s.length,width:d.width,height:d.height,purge:function(){o.call(this)}}),o.call(this)}return i}),i(G,[u,p,B,z],function(e,t,n,i){return function(r){var o=[n,i],a;a=function(){for(var e=0;e<o.length;e++)try{return new o[e](r)}catch(n){}throw new t.ImageError(t.ImageError.WRONG_FORMAT)}(),e.extend(this,{type:"",size:0,width:0,height:0,setExif:function(){},writeHeaders:function(e){return e},stripHeaders:function(e){return e},purge:function(){}}),e.extend(this,a),this.purge=function(){a.purge(),a=null}}}),i(q,[],function(){function e(e,i,r){var o=e.naturalWidth,a=e.naturalHeight,s=r.width,u=r.height,c=r.x||0,l=r.y||0,d=i.getContext("2d");t(e)&&(o/=2,a/=2);var f=1024,p=document.createElement("canvas");p.width=p.height=f;for(var h=p.getContext("2d"),m=n(e,o,a),g=0;a>g;){for(var v=g+f>a?a-g:f,y=0;o>y;){var w=y+f>o?o-y:f;h.clearRect(0,0,f,f),h.drawImage(e,-y,-g);var E=y*s/o+c<<0,_=Math.ceil(w*s/o),x=g*u/a/m+l<<0,R=Math.ceil(v*u/a/m);d.drawImage(p,0,0,w,v,E,x,_,R),y+=f}g+=f}p=h=null}function t(e){var t=e.naturalWidth,n=e.naturalHeight;if(t*n>1048576){var i=document.createElement("canvas");i.width=i.height=1;var r=i.getContext("2d");return r.drawImage(e,-t+1,0),0===r.getImageData(0,0,1,1).data[3]}return!1}function n(e,t,n){var i=document.createElement("canvas");i.width=1,i.height=n;var r=i.getContext("2d");r.drawImage(e,0,0);for(var o=r.getImageData(0,0,1,n).data,a=0,s=n,u=n;u>a;){var c=o[4*(u-1)+3];0===c?s=u:a=u,u=s+a>>1}i=null;var l=u/n;return 0===l?1:l}return{isSubsampled:t,renderTo:e}}),i(X,[D,u,p,m,w,G,q,l,d],function(e,t,n,i,r,o,a,s,u){function c(){function e(){if(!E&&!y)throw new n.ImageError(n.DOMException.INVALID_STATE_ERR);return E||y}function c(e){return i.atob(e.substring(e.indexOf("base64,")+7))}function l(e,t){return"data:"+(t||"")+";base64,"+i.btoa(e)}function d(e){var t=this;y=new Image,y.onerror=function(){g.call(this),t.trigger("error",new n.ImageError(n.ImageError.WRONG_FORMAT))},y.onload=function(){t.trigger("load")},y.src=/^data:[^;]*;base64,/.test(e)?e:l(e,x.type)}function f(e,t){var i=this,r;return window.FileReader?(r=new FileReader,r.onload=function(){t(this.result)},r.onerror=function(){i.trigger("error",new n.FileException(n.FileException.NOT_READABLE_ERR))},r.readAsDataURL(e),void 0):t(e.getAsDataURL())}function p(n,i,r,o){var a=this,s,u,c=0,l=0,d,f,p,g;if(b=o,g=this.meta&&this.meta.tiff&&this.meta.tiff.Orientation||1,-1!==t.inArray(g,[5,6,7,8])){var v=n;n=i,i=v}return d=e(),u=r?Math.max:Math.min,s=u(n/d.width,i/d.height),s>1&&(!r||o)?(this.trigger("Resize"),void 0):(E||(E=document.createElement("canvas")),f=Math.round(d.width*s),p=Math.round(d.height*s),r?(E.width=n,E.height=i,f>n&&(c=Math.round((f-n)/2)),p>i&&(l=Math.round((p-i)/2))):(E.width=f,E.height=p),b||m(E.width,E.height,g),h.call(this,d,E,-c,-l,f,p),this.width=E.width,this.height=E.height,R=!0,a.trigger("Resize"),void 0)}function h(e,t,n,i,r,o){if("iOS"===u.OS)a.renderTo(e,t,{width:r,height:o,x:n,y:i});else{var s=t.getContext("2d");s.drawImage(e,n,i,r,o)}}function m(e,t,n){switch(n){case 5:case 6:case 7:case 8:E.width=t,E.height=e;break;default:E.width=e,E.height=t}var i=E.getContext("2d");switch(n){case 2:i.translate(e,0),i.scale(-1,1);break;case 3:i.translate(e,t),i.rotate(Math.PI);break;case 4:i.translate(0,t),i.scale(1,-1);break;case 5:i.rotate(.5*Math.PI),i.scale(1,-1);break;case 6:i.rotate(.5*Math.PI),i.translate(0,-t);break;case 7:i.rotate(.5*Math.PI),i.translate(e,-t),i.scale(-1,1);break;case 8:i.rotate(-.5*Math.PI),i.translate(-e,0)}}function g(){w&&(w.purge(),w=null),_=y=E=x=null,R=!1}var v=this,y,w,E,_,x,R=!1,b=!0;t.extend(this,{loadFromBlob:function(e){var t=this,i=t.getRuntime(),r=arguments.length>1?arguments[1]:!0;if(!i.can("access_binary"))throw new n.RuntimeError(n.RuntimeError.NOT_SUPPORTED_ERR);return x=e,e.isDetached()?(_=e.getSource(),d.call(this,_),void 0):(f.call(this,e.getSource(),function(e){r&&(_=c(e)),d.call(t,e)}),void 0)},loadFromImage:function(e,t){this.meta=e.meta,x=new r(null,{name:e.name,size:e.size,type:e.type}),d.call(this,t?_=e.getAsBinaryString():e.getAsDataURL())},getInfo:function(){var t=this.getRuntime(),n;return!w&&_&&t.can("access_image_binary")&&(w=new o(_)),n={width:e().width||0,height:e().height||0,type:x.type||s.getFileMime(x.name),size:_&&_.length||x.size||0,name:x.name||"",meta:w&&w.meta||this.meta||{}}},downsize:function(){p.apply(this,arguments)},getAsCanvas:function(){return E&&(E.id=this.uid+"_canvas"),E},getAsBlob:function(e,t){return e!==this.type&&p.call(this,this.width,this.height,!1),new r(null,{name:x.name||"",type:e,data:v.getAsBinaryString.call(this,e,t)})},getAsDataURL:function(e){var t=arguments[1]||90;if(!R)return y.src;if("image/jpeg"!==e)return E.toDataURL("image/png");try{return E.toDataURL("image/jpeg",t/100)}catch(n){return E.toDataURL("image/jpeg")}},getAsBinaryString:function(e,t){if(!R)return _||(_=c(v.getAsDataURL(e,t))),_;if("image/jpeg"!==e)_=c(v.getAsDataURL(e,t));else{var n;t||(t=90);try{n=E.toDataURL("image/jpeg",t/100)}catch(i){n=E.toDataURL("image/jpeg")}_=c(n),w&&(_=w.stripHeaders(_),b&&(w.meta&&w.meta.exif&&w.setExif({PixelXDimension:this.width,PixelYDimension:this.height}),_=w.writeHeaders(_)),w.purge(),w=null)}return R=!1,_},destroy:function(){v=null,g.call(this),this.getRuntime().getShim().removeInstance(this.uid)}})}return e.Image=c}),i(j,[u,d,f,p,g],function(e,t,n,i,r){function o(){var e;try{e=navigator.plugins["Shockwave Flash"],e=e.description}catch(t){try{e=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(n){e="0.0"}}return e=e.match(/\d+/g),parseFloat(e[0]+"."+e[1])}function a(a){var c=this,l;a=e.extend({swf_url:t.swf_url},a),r.call(this,a,s,{access_binary:function(e){return e&&"browser"===c.mode},access_image_binary:function(e){return e&&"browser"===c.mode},display_media:r.capTrue,do_cors:r.capTrue,drag_and_drop:!1,report_upload_progress:function(){return"client"===c.mode},resize_image:r.capTrue,return_response_headers:!1,return_response_type:function(t){return"json"===t&&window.JSON?!0:!e.arrayDiff(t,["","text","document"])||"browser"===c.mode},return_status_code:function(t){return"browser"===c.mode||!e.arrayDiff(t,[200,404])},select_file:r.capTrue,select_multiple:r.capTrue,send_binary_string:function(e){return e&&"browser"===c.mode},send_browser_cookies:function(e){return e&&"browser"===c.mode},send_custom_headers:function(e){return e&&"browser"===c.mode},send_multipart:r.capTrue,slice_blob:r.capTrue,stream_upload:function(e){return e&&"browser"===c.mode},summon_file_dialog:!1,upload_filesize:function(t){return e.parseSizeStr(t)<=2097152||"client"===c.mode},use_http_method:function(t){return!e.arrayDiff(t,["GET","POST"])}},{access_binary:function(e){return e?"browser":"client"},access_image_binary:function(e){return e?"browser":"client"},report_upload_progress:function(e){return e?"browser":"client"},return_response_type:function(t){return e.arrayDiff(t,["","text","json","document"])?"browser":["client","browser"]},return_status_code:function(t){return e.arrayDiff(t,[200,404])?"browser":["client","browser"]},send_binary_string:function(e){return e?"browser":"client"},send_browser_cookies:function(e){return e?"browser":"client"},send_custom_headers:function(e){return e?"browser":"client"},stream_upload:function(e){return e?"client":"browser"},upload_filesize:function(t){return e.parseSizeStr(t)>=2097152?"client":"browser"}},"client"),o()<10&&(this.mode=!1),e.extend(this,{getShim:function(){return n.get(this.uid)},shimExec:function(e,t){var n=[].slice.call(arguments,2);return c.getShim().exec(this.uid,e,t,n)},init:function(){var n,r,o;o=this.getShimContainer(),e.extend(o.style,{position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),n='<object id="'+this.uid+'" type="application/x-shockwave-flash" data="'+a.swf_url+'" ',"IE"===t.browser&&(n+='classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '),n+='width="100%" height="100%" style="outline:0"><param name="movie" value="'+a.swf_url+'" />'+'<param name="flashvars" value="uid='+escape(this.uid)+"&target="+t.global_event_dispatcher+'" />'+'<param name="wmode" value="transparent" />'+'<param name="allowscriptaccess" value="always" />'+"</object>","IE"===t.browser?(r=document.createElement("div"),o.appendChild(r),r.outerHTML=n,r=o=null):o.innerHTML=n,l=setTimeout(function(){c&&!c.initialized&&c.trigger("Error",new i.RuntimeError(i.RuntimeError.NOT_INIT_ERR))},5e3)},destroy:function(e){return function(){e.call(c),clearTimeout(l),a=l=e=c=null}}(this.destroy)},u)}var s="flash",u={};return r.addConstructor(s,a),u}),i(V,[j,y],function(e,t){var n={slice:function(e,n,i,r){var o=this.getRuntime();return 0>n?n=Math.max(e.size+n,0):n>0&&(n=Math.min(n,e.size)),0>i?i=Math.max(e.size+i,0):i>0&&(i=Math.min(i,e.size)),e=o.shimExec.call(this,"Blob","slice",n,i,r||""),e&&(e=new t(o.uid,e)),e}};return e.Blob=n}),i(W,[j],function(e){var t={init:function(e){this.getRuntime().shimExec.call(this,"FileInput","init",{name:e.name,accept:e.accept,multiple:e.multiple}),this.trigger("ready")}};return e.FileInput=t}),i(Y,[j,m],function(e,t){function n(e,n){switch(n){case"readAsText":return t.atob(e,"utf8");case"readAsBinaryString":return t.atob(e);case"readAsDataURL":return e}return null}var i="",r={read:function(e,t){var r=this,o=r.getRuntime();return"readAsDataURL"===e&&(i="data:"+(t.type||"")+";base64,"),r.bind("Progress",function(t,r){r&&(i+=n(r,e))}),o.shimExec.call(this,"FileReader","readAsBase64",t.uid)},getResult:function(){return i},destroy:function(){i=null}};return e.FileReader=r}),i($,[j,m],function(e,t){function n(e,n){switch(n){case"readAsText":return t.atob(e,"utf8");case"readAsBinaryString":return t.atob(e);case"readAsDataURL":return e}return null}var i={read:function(e,t){var i,r=this.getRuntime();return(i=r.shimExec.call(this,"FileReaderSync","readAsBase64",t.uid))?("readAsDataURL"===e&&(i="data:"+(t.type||"")+";base64,"+i),n(i,e,t.type)):null}};return e.FileReaderSync=i}),i(J,[j,u,y,w,T,S,O],function(e,t,n,i,r,o,a){var s={send:function(e,i){function r(){e.transport=l.mode,l.shimExec.call(c,"XMLHttpRequest","send",e,i)}function s(e,t){l.shimExec.call(c,"XMLHttpRequest","appendBlob",e,t.uid),i=null,r()}function u(e,t){var n=new a;n.bind("TransportingComplete",function(){t(this.result)}),n.transport(e.getSource(),e.type,{ruid:l.uid})}var c=this,l=c.getRuntime();if(t.isEmptyObj(e.headers)||t.each(e.headers,function(e,t){l.shimExec.call(c,"XMLHttpRequest","setRequestHeader",t,e.toString())}),i instanceof o){var d;if(i.each(function(e,t){e instanceof n?d=t:l.shimExec.call(c,"XMLHttpRequest","append",t,e)}),i.hasBlob()){var f=i.getBlob();f.isDetached()?u(f,function(e){f.destroy(),s(d,e)}):s(d,f)}else i=null,r()}else i instanceof n?i.isDetached()?u(i,function(e){i.destroy(),i=e.uid,r()}):(i=i.uid,r()):r()},getResponse:function(e){var n,o,a=this.getRuntime();if(o=a.shimExec.call(this,"XMLHttpRequest","getResponseAsBlob")){if(o=new i(a.uid,o),"blob"===e)return o;try{if(n=new r,~t.inArray(e,["","text"]))return n.readAsText(o);if("json"===e&&window.JSON)return JSON.parse(n.readAsText(o))}finally{o.destroy()}}return null},abort:function(e){var t=this.getRuntime();t.shimExec.call(this,"XMLHttpRequest","abort"),this.dispatchEvent("readystatechange"),this.dispatchEvent("abort")}};return e.XMLHttpRequest=s}),i(Z,[j,y],function(e,t){var n={getAsBlob:function(e){var n=this.getRuntime(),i=n.shimExec.call(this,"Transporter","getAsBlob",e);return i?new t(n.uid,i):null}};return e.Transporter=n}),i(K,[j,u,O,y,T],function(e,t,n,i,r){var o={loadFromBlob:function(e){function t(e){r.shimExec.call(i,"Image","loadFromBlob",e.uid),i=r=null}var i=this,r=i.getRuntime();if(e.isDetached()){var o=new n;o.bind("TransportingComplete",function(){t(o.result.getSource())}),o.transport(e.getSource(),e.type,{ruid:r.uid})}else t(e.getSource())},loadFromImage:function(e){var t=this.getRuntime();return t.shimExec.call(this,"Image","loadFromImage",e.uid)},getAsBlob:function(e,t){var n=this.getRuntime(),r=n.shimExec.call(this,"Image","getAsBlob",e,t);return r?new i(n.uid,r):null},getAsDataURL:function(){var e=this.getRuntime(),t=e.Image.getAsBlob.apply(this,arguments),n;return t?(n=new r,n.readAsDataURL(t)):null}};return e.Image=o}),i(Q,[u,d,f,p,g],function(e,t,n,i,r){function o(e){var t=!1,n=null,i,r,o,a,s,u=0;try{try{n=new ActiveXObject("AgControl.AgControl"),n.IsVersionSupported(e)&&(t=!0),n=null}catch(c){var l=navigator.plugins["Silverlight Plug-In"];if(l){for(i=l.description,"1.0.30226.2"===i&&(i="2.0.30226.2"),r=i.split(".");r.length>3;)r.pop();for(;r.length<4;)r.push(0);for(o=e.split(".");o.length>4;)o.pop();do a=parseInt(o[u],10),s=parseInt(r[u],10),u++;while(u<o.length&&a===s);s>=a&&!isNaN(a)&&(t=!0)}}}catch(d){t=!1}return t}function a(a){var c=this,l;a=e.extend({xap_url:t.xap_url},a),r.call(this,a,s,{access_binary:r.capTrue,access_image_binary:r.capTrue,display_media:r.capTrue,do_cors:r.capTrue,drag_and_drop:!1,report_upload_progress:r.capTrue,resize_image:r.capTrue,return_response_headers:function(e){return e&&"client"===c.mode},return_response_type:function(e){return"json"!==e?!0:!!window.JSON},return_status_code:function(t){return"client"===c.mode||!e.arrayDiff(t,[200,404])},select_file:r.capTrue,select_multiple:r.capTrue,send_binary_string:r.capTrue,send_browser_cookies:function(e){return e&&"browser"===c.mode},send_custom_headers:function(e){return e&&"client"===c.mode},send_multipart:r.capTrue,slice_blob:r.capTrue,stream_upload:!0,summon_file_dialog:!1,upload_filesize:r.capTrue,use_http_method:function(t){return"client"===c.mode||!e.arrayDiff(t,["GET","POST"])}},{return_response_headers:function(e){return e?"client":"browser"},return_status_code:function(t){return e.arrayDiff(t,[200,404])?"client":["client","browser"]},send_browser_cookies:function(e){return e?"browser":"client"},send_custom_headers:function(e){return e?"client":"browser"},use_http_method:function(t){return e.arrayDiff(t,["GET","POST"])?"client":["client","browser"]}}),o("2.0.31005.0")&&"Opera"!==t.browser||(this.mode=!1),e.extend(this,{getShim:function(){return n.get(this.uid).content.Moxie},shimExec:function(e,t){var n=[].slice.call(arguments,2);return c.getShim().exec(this.uid,e,t,n)},init:function(){var e;e=this.getShimContainer(),e.innerHTML='<object id="'+this.uid+'" data="data:application/x-silverlight," type="application/x-silverlight-2" width="100%" height="100%" style="outline:none;">'+'<param name="source" value="'+a.xap_url+'"/>'+'<param name="background" value="Transparent"/>'+'<param name="windowless" value="true"/>'+'<param name="enablehtmlaccess" value="true"/>'+'<param name="initParams" value="uid='+this.uid+",target="+t.global_event_dispatcher+'"/>'+"</object>",l=setTimeout(function(){c&&!c.initialized&&c.trigger("Error",new i.RuntimeError(i.RuntimeError.NOT_INIT_ERR))},"Windows"!==t.OS?1e4:5e3)},destroy:function(e){return function(){e.call(c),clearTimeout(l),a=l=e=c=null}}(this.destroy)},u)}var s="silverlight",u={};return r.addConstructor(s,a),u}),i(et,[Q,u,V],function(e,t,n){return e.Blob=t.extend({},n)}),i(tt,[Q],function(e){var t={init:function(e){function t(e){for(var t="",n=0;n<e.length;n++)t+=(""!==t?"|":"")+e[n].title+" | *."+e[n].extensions.replace(/,/g,";*.");return t}this.getRuntime().shimExec.call(this,"FileInput","init",t(e.accept),e.name,e.multiple),this.trigger("ready")}};return e.FileInput=t}),i(nt,[Q,f,L],function(e,t,n){var i={init:function(){var e=this,i=e.getRuntime(),r;return r=i.getShimContainer(),n.addEvent(r,"dragover",function(e){e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect="copy"},e.uid),n.addEvent(r,"dragenter",function(e){e.preventDefault();var n=t.get(i.uid).dragEnter(e);n&&e.stopPropagation()},e.uid),n.addEvent(r,"drop",function(e){e.preventDefault();var n=t.get(i.uid).dragDrop(e);n&&e.stopPropagation()},e.uid),i.shimExec.call(this,"FileDrop","init")}};return e.FileDrop=i}),i(it,[Q,u,Y],function(e,t,n){return e.FileReader=t.extend({},n)}),i(rt,[Q,u,$],function(e,t,n){return e.FileReaderSync=t.extend({},n)}),i(ot,[Q,u,J],function(e,t,n){return e.XMLHttpRequest=t.extend({},n)}),i(at,[Q,u,Z],function(e,t,n){return e.Transporter=t.extend({},n)}),i(st,[Q,u,K],function(e,t,n){return e.Image=t.extend({},n,{getInfo:function(){var e=this.getRuntime(),n=["tiff","exif","gps"],i={meta:{}},r=e.shimExec.call(this,"Image","getInfo");return r.meta&&t.each(n,function(e){var t=r.meta[e],n,o,a,s;if(t&&t.keys)for(i.meta[e]={},o=0,a=t.keys.length;a>o;o++)n=t.keys[o],s=t[n],s&&(/^(\d|[1-9]\d+)$/.test(s)?s=parseInt(s,10):/^\d*\.\d+$/.test(s)&&(s=parseFloat(s)),i.meta[e][n]=s)}),i.width=parseInt(r.width,10),i.height=parseInt(r.height,10),i.size=parseInt(r.size,10),i.type=r.type,i.name=r.name,i}})}),i(ut,[u,p,g,d],function(e,t,n,i){function r(t){var r=this,s=n.capTest,u=n.capTrue;n.call(this,t,o,{access_binary:s(window.FileReader||window.File&&File.getAsDataURL),access_image_binary:!1,display_media:s(a.Image&&(i.can("create_canvas")||i.can("use_data_uri_over32kb"))),do_cors:!1,drag_and_drop:!1,filter_by_extension:s(function(){return"Chrome"===i.browser&&i.version>=28||"IE"===i.browser&&i.version>=10}()),resize_image:function(){return a.Image&&r.can("access_binary")&&i.can("create_canvas")},report_upload_progress:!1,return_response_headers:!1,return_response_type:function(t){return"json"===t&&window.JSON?!0:!!~e.inArray(t,["text","document",""])},return_status_code:function(t){return!e.arrayDiff(t,[200,404])},select_file:function(){return i.can("use_fileinput")},select_multiple:!1,send_binary_string:!1,send_custom_headers:!1,send_multipart:!0,slice_blob:!1,stream_upload:function(){return r.can("select_file")},summon_file_dialog:s(function(){return"Firefox"===i.browser&&i.version>=4||"Opera"===i.browser&&i.version>=12||!!~e.inArray(i.browser,["Chrome","Safari"])}()),upload_filesize:u,use_http_method:function(t){return!e.arrayDiff(t,["GET","POST"])}}),e.extend(this,{init:function(){this.trigger("Init")},destroy:function(e){return function(){e.call(r),e=r=null}}(this.destroy)}),e.extend(this.getShim(),a)}var o="html4",a={};return n.addConstructor(o,r),a}),i(ct,[ut,u,f,L,l,d],function(e,t,n,i,r,o){function a(){function e(){var r=this,l=r.getRuntime(),d,f,p,h,m,g;g=t.guid("uid_"),d=l.getShimContainer(),a&&(p=n.get(a+"_form"),p&&t.extend(p.style,{top:"100%"})),h=document.createElement("form"),h.setAttribute("id",g+"_form"),h.setAttribute("method","post"),h.setAttribute("enctype","multipart/form-data"),h.setAttribute("encoding","multipart/form-data"),t.extend(h.style,{overflow:"hidden",position:"absolute",top:0,left:0,width:"100%",height:"100%"}),m=document.createElement("input"),m.setAttribute("id",g),m.setAttribute("type","file"),m.setAttribute("name",c.name||"Filedata"),m.setAttribute("accept",u.join(",")),t.extend(m.style,{fontSize:"999px",opacity:0}),h.appendChild(m),d.appendChild(h),t.extend(m.style,{position:"absolute",top:0,left:0,width:"100%",height:"100%"}),"IE"===o.browser&&o.version<10&&t.extend(m.style,{filter:"progid:DXImageTransform.Microsoft.Alpha(opacity=0)"}),m.onchange=function(){var t;this.value&&(t=this.files?this.files[0]:{name:this.value},s=[t],this.onchange=function(){},e.call(r),r.bind("change",function i(){var e=n.get(g),t=n.get(g+"_form"),o;r.unbind("change",i),r.files.length&&e&&t&&(o=r.files[0],e.setAttribute("id",o.uid),t.setAttribute("id",o.uid+"_form"),t.setAttribute("target",o.uid+"_iframe")),e=t=null},998),m=h=null,r.trigger("change"))},l.can("summon_file_dialog")&&(f=n.get(c.browse_button),i.removeEvent(f,"click",r.uid),i.addEvent(f,"click",function(e){m&&!m.disabled&&m.click(),e.preventDefault()},r.uid)),a=g,d=p=f=null}var a,s=[],u=[],c;t.extend(this,{init:function(t){var o=this,a=o.getRuntime(),s;c=t,u=t.accept.mimes||r.extList2mimes(t.accept,a.can("filter_by_extension")),s=a.getShimContainer(),function(){var e,r,u;e=n.get(t.browse_button),a.can("summon_file_dialog")&&("static"===n.getStyle(e,"position")&&(e.style.position="relative"),r=parseInt(n.getStyle(e,"z-index"),10)||1,e.style.zIndex=r,s.style.zIndex=r-1),u=a.can("summon_file_dialog")?e:s,i.addEvent(u,"mouseover",function(){o.trigger("mouseenter")},o.uid),i.addEvent(u,"mouseout",function(){o.trigger("mouseleave")},o.uid),i.addEvent(u,"mousedown",function(){o.trigger("mousedown")},o.uid),i.addEvent(n.get(t.container),"mouseup",function(){o.trigger("mouseup")},o.uid),e=null}(),e.call(this),s=null,o.trigger({type:"ready",async:!0})},getFiles:function(){return s},disable:function(e){var t;(t=n.get(a))&&(t.disabled=!!e)},destroy:function(){var e=this.getRuntime(),t=e.getShim(),r=e.getShimContainer();i.removeAllEvents(r,this.uid),i.removeAllEvents(c&&n.get(c.container),this.uid),i.removeAllEvents(c&&n.get(c.browse_button),this.uid),r&&(r.innerHTML=""),t.removeInstance(this.uid),a=s=u=c=r=t=null}})}return e.FileInput=a}),i(lt,[ut,F],function(e,t){return e.FileReader=t}),i(dt,[ut,u,f,b,p,L,y,S],function(e,t,n,i,r,o,a,s){function u(){function e(e){var t=this,i,r,a,s,u=!1;if(l){if(i=l.id.replace(/_iframe$/,""),r=n.get(i+"_form")){for(a=r.getElementsByTagName("input"),s=a.length;s--;)switch(a[s].getAttribute("type")){case"hidden":a[s].parentNode.removeChild(a[s]);break;case"file":u=!0}a=[],u||r.parentNode.removeChild(r),r=null}setTimeout(function(){o.removeEvent(l,"load",t.uid),l.parentNode&&l.parentNode.removeChild(l);var n=t.getRuntime().getShimContainer();n.children.length||n.parentNode.removeChild(n),n=l=null,e()},1)}}var u,c,l;t.extend(this,{send:function(d,f){function p(){var n=m.getShimContainer()||document.body,r=document.createElement("div");r.innerHTML='<iframe id="'+g+'_iframe" name="'+g+'_iframe" src="javascript:""" style="display:none"></iframe>',l=r.firstChild,n.appendChild(l),o.addEvent(l,"load",function(){var n;try{n=l.contentWindow.document||l.contentDocument||window.frames[l.id].document,/^4(0[0-9]|1[0-7]|2[2346])\s/.test(n.title)?u=n.title.replace(/^(\d+).*$/,"$1"):(u=200,c=t.trim(n.body.innerHTML),h.trigger({type:"progress",loaded:c.length,total:c.length}),w&&h.trigger({type:"uploadprogress",loaded:w.size||1025,total:w.size||1025}))}catch(r){if(!i.hasSameOrigin(d.url))return e.call(h,function(){h.trigger("error")}),void 0;u=404}e.call(h,function(){h.trigger("load")})},h.uid)}var h=this,m=h.getRuntime(),g,v,y,w;if(u=c=null,f instanceof s&&f.hasBlob()){if(w=f.getBlob(),g=w.uid,y=n.get(g),v=n.get(g+"_form"),!v)throw new r.DOMException(r.DOMException.NOT_FOUND_ERR)}else g=t.guid("uid_"),v=document.createElement("form"),v.setAttribute("id",g+"_form"),v.setAttribute("method",d.method),v.setAttribute("enctype","multipart/form-data"),v.setAttribute("encoding","multipart/form-data"),v.setAttribute("target",g+"_iframe"),m.getShimContainer().appendChild(v);f instanceof s&&f.each(function(e,n){if(e instanceof a)y&&y.setAttribute("name",n);else{var i=document.createElement("input");t.extend(i,{type:"hidden",name:n,value:e}),y?v.insertBefore(i,y):v.appendChild(i)}}),v.setAttribute("action",d.url),p(),v.submit(),h.trigger("loadstart")},getStatus:function(){return u},getResponse:function(e){if("json"===e&&"string"===t.typeOf(c)&&window.JSON)try{return JSON.parse(c.replace(/^\s*<pre[^>]*>/,"").replace(/<\/pre>\s*$/,""))}catch(n){return null}return c},abort:function(){var t=this;l&&l.contentWindow&&(l.contentWindow.stop?l.contentWindow.stop():l.contentWindow.document.execCommand?l.contentWindow.document.execCommand("Stop"):l.src="about:blank"),e.call(this,function(){t.dispatchEvent("abort")})}})}return e.XMLHttpRequest=u}),i(ft,[ut,X],function(e,t){return e.Image=t}),a([u,c,l,d,f,p,h,m,g,v,y,w,E,_,x,R,b,T,S,A,O,I,L])}(this);;(function(){"use strict";var e={},t=moxie.core.utils.Basic.inArray;return function n(r){var i,s;for(i in r)s=typeof r[i],s==="object"&&!~t(i,["Exceptions","Env","Mime"])?n(r[i]):s==="function"&&(e[i]=r[i])}(window.moxie),e.Env=window.moxie.core.utils.Env,e.Mime=window.moxie.core.utils.Mime,e.Exceptions=window.moxie.core.Exceptions,window.mOxie=e,window.o||(window.o=e),e})();  /**   * Plupload - multi-runtime File Uploader - * v2.0.0beta + * v2.1.1   *   * Copyright 2013, Moxiecode Systems AB   * Released under GPL License. @@ -23,6 +23,6 @@ e.extend(this,y,{uid:e.guid("uid_"),upload:new f,open:function(o,a,s,u,c){var l;   * License: http://www.plupload.com/license   * Contributing: http://www.plupload.com/contributing   * - * Date: 2012-11-30 + * Date: 2014-01-16   */ -;(function(e,t,n){function s(e){function r(e,t,r){var i={chunks:"slice_blob",resize:"send_binary_string",jpgresize:"send_binary_string",pngresize:"send_binary_string",progress:"report_upload_progress",multi_selection:"select_multiple",max_file_size:"access_binary",dragdrop:"drag_and_drop",drop_element:"drag_and_drop",headers:"send_custom_headers",canSendBinary:"send_binary",triggerDialog:"summon_file_dialog"};i[e]?n[i[e]]=t:r||(n[e]=t)}var t=e.required_features,n={};return typeof t=="string"?o.each(t.split(/\s*,\s*/),function(e){r(e,!0)}):typeof t=="object"?o.each(t,function(e,t){r(t,e)}):t===!0&&(e.multipart||(n.send_binary_string=!0),e.chunk_size>0&&(n.slice_blob=!0),o.each(e,function(e,t){r(t,!!e,!0)})),n}var r=e.setTimeout,i={},o={VERSION:"2.0.0beta",STOPPED:1,STARTED:2,QUEUED:1,UPLOADING:2,FAILED:4,DONE:5,GENERIC_ERROR:-100,HTTP_ERROR:-200,IO_ERROR:-300,SECURITY_ERROR:-400,INIT_ERROR:-500,FILE_SIZE_ERROR:-600,FILE_EXTENSION_ERROR:-601,FILE_DUPLICATE_ERROR:-602,IMAGE_FORMAT_ERROR:-700,IMAGE_MEMORY_ERROR:-701,IMAGE_DIMENSIONS_ERROR:-702,mimeTypes:t.mimes,ua:t.ua,typeOf:t.typeOf,extend:t.extend,guid:t.guid,each:t.each,getPos:t.getPos,getSize:t.getSize,xmlEncode:function(e){var t={"<":"lt",">":"gt","&":"amp",'"':"quot","'":"#39"},n=/[<>&\"\']/g;return e?(""+e).replace(n,function(e){return t[e]?"&"+t[e]+";":e}):e},toArray:t.toArray,inArray:t.inArray,addI18n:t.addI18n,translate:t.translate,isEmptyObj:t.isEmptyObj,hasClass:t.hasClass,addClass:t.addClass,removeClass:t.removeClass,getStyle:t.getStyle,addEvent:t.addEvent,removeEvent:t.removeEvent,removeAllEvents:t.removeAllEvents,cleanName:function(e){var t,n;n=[/[\300-\306]/g,"A",/[\340-\346]/g,"a",/\307/g,"C",/\347/g,"c",/[\310-\313]/g,"E",/[\350-\353]/g,"e",/[\314-\317]/g,"I",/[\354-\357]/g,"i",/\321/g,"N",/\361/g,"n",/[\322-\330]/g,"O",/[\362-\370]/g,"o",/[\331-\334]/g,"U",/[\371-\374]/g,"u"];for(t=0;t<n.length;t+=2)e=e.replace(n[t],n[t+1]);return e=e.replace(/\s+/g,"_"),e=e.replace(/[^a-z0-9_\-\.]+/gi,""),e},buildUrl:function(e,t){var n="";return o.each(t,function(e,t){n+=(n?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(e)}),n&&(e+=(e.indexOf("?")>0?"&":"?")+n),e},formatSize:function(e){return e===n||/\D/.test(e)?o.translate("N/A"):e>1099511627776?Math.round(e/1099511627776,1)+" "+o.translate("tb"):e>1073741824?Math.round(e/1073741824,1)+" "+o.translate("gb"):e>1048576?Math.round(e/1048576,1)+" "+o.translate("mb"):e>1024?Math.round(e/1024,1)+" "+o.translate("kb"):e+" "+o.translate("b")},parseSize:t.parseSizeStr,predictRuntime:function(e,t){var n,r;return t&&(e.runtimes=t),n=new o.Uploader(e),r=n.runtime,n.destroy(),r},addFileFilter:function(e,t){i[e]=t}};o.addFileFilter("mime_types",function(){function n(e){var t=[];return o.each(e,function(e){o.each(e.extensions.split(/,/),function(e){/^\s*\*\s*$/.test(e)?t.push("\\.*"):t.push("\\."+e.replace(new RegExp("["+"/^$.*+?|()[]{}\\".replace(/./g,"\\$&")+"]","g"),"\\$&"))})}),new RegExp("("+t.join("|")+")$","i")}var e,t;return function(r,i,s){if(!t||r!=e)t=n(r),e=[].slice.call(r);t.test(i.name)?s(!0):(this.trigger("Error",{code:o.FILE_EXTENSION_ERROR,message:o.translate("File extension error."),file:i}),s(!1))}}()),o.addFileFilter("max_file_size",function(e,t,n){var r;t.size!==r&&e&&t.size>e?(this.trigger("Error",{code:o.FILE_SIZE_ERROR,message:o.translate("File size error."),file:t}),n(!1)):n(!0)}),o.addFileFilter("prevent_duplicates",function(e,t,n){if(e){var r=this.files.length;while(r--)if(t.name===this.files[r].name&&t.size===this.files[r].size){this.trigger("Error",{code:o.FILE_DUPLICATE_ERROR,message:o.translate("Duplicate file error."),file:t}),n(!1);return}}n(!0)}),o.Uploader=function(e){function m(){var e,t=0,n;if(this.state==o.STARTED){for(n=0;n<u.length;n++)!e&&u[n].status==o.QUEUED?(e=u[n],this.trigger("BeforeUpload",e)&&(e.status=o.UPLOADING,this.trigger("UploadFile",e))):t++;t==u.length&&(this.state!==o.STOPPED&&(this.state=o.STOPPED,this.trigger("StateChanged")),this.trigger("UploadComplete",u))}}function g(e){e.percent=e.size>0?Math.ceil(e.loaded/e.size*100):100,y()}function y(){var e,t;c.reset();for(e=0;e<u.length;e++)t=u[e],t.size!==n?(c.size+=t.origSize,c.loaded+=t.loaded*t.origSize/t.size):c.size=n,t.status==o.DONE?c.uploaded++:t.status==o.FAILED?c.failed++:c.queued++;c.size===n?c.percent=u.length>0?Math.ceil(c.uploaded/u.length*100):0:(c.bytesPerSec=Math.ceil(c.loaded/((+(new Date)-l||1)/1e3)),c.percent=c.size>0?Math.ceil(c.loaded/c.size*100):0)}function b(){var n=this,r=0,i={accept:e.filters.mime_types,runtime_order:e.runtimes,required_caps:f,swf_url:e.flash_swf_url,xap_url:e.silverlight_xap_url};o.each(e.runtimes.split(/\s*,\s*/),function(t){e[t]&&(i[t]=e[t])}),t.inSeries([function(s){e.browse_button?(p=new t.FileInput(o.extend({},i,{name:e.file_data_name,multiple:e.multi_selection,container:e.container,browse_button:e.browse_button})),p.onready=function(){var e=t.Runtime.getInfo(this.ruid);t.extend(n.features,{chunks:e.can("slice_blob"),multipart:e.can("send_multipart"),multi_selection:e.can("select_multiple")}),r++,s()},p.onchange=function(){n.addFile(this.files)},p.bind("mouseenter mouseleave mousedown mouseup",function(n){if(!h){var r=t.get(e.browse_button);r&&(e.browse_button_hover&&("mouseenter"===n.type?t.addClass(r,e.browse_button_hover):"mouseleave"===n.type&&t.removeClass(r,e.browse_button_hover)),e.browse_button_active&&("mousedown"===n.type?t.addClass(r,e.browse_button_active):"mouseup"===n.type&&t.removeClass(r,e.browse_button_active)),r=null)}}),p.bind("error runtimeerror",function(){p=null,s()}),p.init()):s()},function(s){e.drop_element?(d=new t.FileDrop(o.extend({},i,{drop_zone:e.drop_element})),d.onready=function(){var e=t.Runtime.getInfo(this.ruid);n.features.dragdrop=e.can("drag_and_drop"),r++,s()},d.ondrop=function(){n.addFile(this.files)},d.bind("error runtimeerror",function(){d=null,s()}),d.init()):s()}],function(){typeof e.init=="function"?e.init(n):o.each(e.init,function(e,t){n.bind(t,e)}),r?n.trigger("PostInit"):n.trigger("Error",{code:o.INIT_ERROR,message:o.translate("Init error.")})})}function w(e,n){if(e.ruid){var r=t.Runtime.getInfo(e.ruid);if(r)return r.can(n)}return!1}function E(e,n,r){var i=new t.Image;try{i.onload=function(){i.downsize(n.width,n.height,n.crop,n.preserve_headers)},i.onresize=function(){r(this.getAsBlob(e.type,n.quality)),this.destroy()},i.onerror=function(){r(e)},i.load(e)}catch(s){r(e)}}var u=[],a={},f={},l,c,h=!1,p,d,v;c=new o.QueueProgress,e=o.extend({runtimes:t.Runtime.order,max_retries:0,multipart:!0,multi_selection:!0,file_data_name:"file",flash_swf_url:"js/Moxie.swf",silverlight_xap_url:"js/Moxie.xap",send_chunk_number:!0},e),e.resize&&(e.resize=o.extend({preserve_headers:!0,crop:!1},e.resize)),o.typeOf(e.filters)==="array"&&(e.filters={mime_types:e.filters}),e.filters=o.extend({mime_types:[],prevent_duplicates:!!e.prevent_duplicates,max_file_size:e.max_file_size},e.filters),e.filters.max_file_size=o.parseSize(e.filters.max_file_size)||0,e.chunk_size=o.parseSize(e.chunk_size)||0,e.required_features=f=s(o.extend({},e)),o.extend(this,{id:o.guid(),state:o.STOPPED,features:{},runtime:t.Runtime.thatCan(f,e.runtimes),files:u,settings:e,total:c,init:function(){var n=this;e.browse_button=t.get(e.browse_button),e.drop_element=t.get(e.drop_element),typeof e.preinit=="function"?e.preinit(n):o.each(e.preinit,function(e,t){n.bind(t,e)});if(!e.browse_button||!e.url){this.trigger("Error",{code:o.INIT_ERROR,message:o.translate("Init error.")});return}n.bind("FilesAdded",function(e,t){[].push.apply(u,t),r(function(){n.trigger("QueueChanged"),n.refresh()},1)}),n.bind("CancelUpload",function(){v&&v.abort()}),e.unique_names&&n.bind("BeforeUpload",function(e,t){var n=t.name.match(/\.([^.]+)$/),r="part";n&&(r=n[1]),t.target_name=t.id+"."+r}),n.bind("UploadFile",function(n,i){function p(){l-->0?r(d,1):(i.loaded=h,n.trigger("Error",{code:o.HTTP_ERROR,message:o.translate("HTTP Error."),file:i,response:v.responseText,status:v.status,responseHeaders:v.getAllResponseHeaders()}))}function d(){var l,m,g,y;if(i.status==o.DONE||i.status==o.FAILED||n.state==o.STOPPED)return;g={name:i.target_name||i.name},a&&u.chunks&&c.size>a?(y=Math.min(a,c.size-h),l=c.slice(h,h+y)):(y=c.size,l=c),a&&u.chunks&&(e.send_chunk_number?(g.chunk=Math.ceil(h/a),g.chunks=Math.ceil(c.size/a)):(g.offset=h,g.total=c.size)),v=new t.XMLHttpRequest,v.upload&&(v.upload.onprogress=function(e){i.loaded=Math.min(i.size,h+e.loaded),n.trigger("UploadProgress",i)}),v.onload=function(){if(v.status>=400){p();return}y<c.size?(l.destroy(),h+=y,i.loaded=Math.min(h,c.size),n.trigger("ChunkUploaded",i,{offset:i.loaded,total:c.size,response:v.responseText,status:v.status,responseHeaders:v.getAllResponseHeaders()}),t.Env.browser==="Android Browser"&&n.trigger("UploadProgress",i)):i.loaded=i.size,l=m=null,!h||h>=c.size?(i.size!=i.origSize&&(c.destroy(),c=null),n.trigger("UploadProgress",i),i.status=o.DONE,n.trigger("FileUploaded",i,{response:v.responseText,status:v.status,responseHeaders:v.getAllResponseHeaders()})):r(d,1)},v.onerror=function(){p()},v.onloadend=function(){this.destroy(),v=null},n.settings.multipart&&u.multipart?(g.name=i.target_name||i.name,v.open("post",s,!0),o.each(n.settings.headers,function(e,t){v.setRequestHeader(t,e)}),m=new t.FormData,o.each(o.extend(g,n.settings.multipart_params),function(e,t){m.append(t,e)}),m.append(n.settings.file_data_name,l),v.send(m,{runtime_order:n.settings.runtimes,required_caps:f,swf_url:n.settings.flash_swf_url,xap_url:n.settings.silverlight_xap_url})):(s=o.buildUrl(n.settings.url,o.extend(g,n.settings.multipart_params)),v.open("post",s,!0),v.setRequestHeader("Content-Type","application/octet-stream"),o.each(n.settings.headers,function(e,t){v.setRequestHeader(t,e)}),v.send(l,{runtime_order:n.settings.runtimes,required_caps:f,swf_url:n.settings.flash_swf_url,xap_url:n.settings.silverlight_xap_url}))}var s=n.settings.url,u=n.features,a=e.chunk_size,l=e.max_retries,c,h=0;i.loaded&&(h=i.loaded=a*Math.floor(i.loaded/a)),c=i.getSource(),!t.isEmptyObj(n.settings.resize)&&w(c,"send_binary_string")&&!!~t.inArray(c.type,["image/jpeg","image/png"])?E.call(this,c,n.settings.resize,function(e){c=e,i.size=e.size,d()}):d()}),n.bind("UploadProgress",function(e,t){g(t)}),n.bind("StateChanged",function(e){if(e.state==o.STARTED)l=+(new Date);else if(e.state==o.STOPPED)for(var t=e.files.length-1;t>=0;t--)e.files[t].status==o.UPLOADING&&(e.files[t].status=o.QUEUED,y())}),n.bind("QueueChanged",y),n.bind("Error",function(e,t){t.file&&(t.file.status=o.FAILED,g(t.file),e.state==o.STARTED&&r(function(){m.call(n)},1))}),n.bind("FileUploaded",function(){y(),r(function(){m.call(n)},1)}),n.trigger("Init",{runtime:this.runtime}),b.call(this)},refresh:function(){p&&p.trigger("Refresh"),this.trigger("Refresh")},start:function(){this.state!=o.STARTED&&(this.state=o.STARTED,this.trigger("StateChanged"),m.call(this))},stop:function(){this.state!=o.STOPPED&&(this.state=o.STOPPED,this.trigger("StateChanged"),this.trigger("CancelUpload"))},disableBrowse:function(){h=arguments[0]!==n?arguments[0]:!0,p&&p.disable(h),this.trigger("DisableBrowse",h)},getFile:function(e){var t;for(t=u.length-1;t>=0;t--)if(u[t].id===e)return u[t]},addFile:function(e,n){function f(){var e=d||p;return e?e.getRuntime().uid:!1}function l(e,n){var s=[];t.each(r.settings.filters,function(t,n){i[n]&&s.push(function(s){i[n].call(r,t,e,function(e){s(!e)})})}),t.inSeries(s,n)}function c(e){var r=t.typeOf(e);if(e instanceof t.File){if(!e.ruid&&!e.isDetached()){if(!a)return!1;e.ruid=a,e.connectRuntime(a)}c(new o.File(e))}else e instanceof t.Blob?(c(e.getSource()),e.destroy()):e instanceof o.File?(n&&(e.name=n),s.push(function(t){l(e,function(n){n||u.push(e),t()})})):t.inArray(r,["file","blob"])!==-1?c(new t.File(null,e)):r==="node"&&t.typeOf(e.files)==="filelist"?t.each(e.files,c):r==="array"&&(n=null,t.each(e,c))}var r=this,s=[],u=[],a;a=f(),c(e),s.length&&t.inSeries(s,function(){u.length&&r.trigger("FilesAdded",u)})},removeFile:function(e){var t=typeof e=="string"?e:e.id;for(var n=u.length-1;n>=0;n--)if(u[n].id===t)return this.splice(n,1)[0]},splice:function(e,t){var r=u.splice(e===n?0:e,t===n?u.length:t);return this.trigger("FilesRemoved",r),this.trigger("QueueChanged"),o.each(r,function(e){e.destroy()}),r},trigger:function(e){var t=a[e.toLowerCase()],n,r;if(t){r=Array.prototype.slice.call(arguments),r[0]=this;for(n=0;n<t.length;n++)if(t[n].func.apply(t[n].scope,r)===!1)return!1}return!0},hasEventListener:function(e){return!!a[e.toLowerCase()]},bind:function(e,t,n){var r;e=e.toLowerCase(),r=a[e]||[],r.push({func:t,scope:n||this}),a[e]=r},unbind:function(e){e=e.toLowerCase();var t=a[e],r,i=arguments[1];if(t){if(i!==n){for(r=t.length-1;r>=0;r--)if(t[r].func===i){t.splice(r,1);break}}else t=[];t.length||delete a[e]}},unbindAll:function(){var e=this;o.each(a,function(t,n){e.unbind(n)})},destroy:function(){this.stop(),o.each(u,function(e){e.destroy()}),u=[],p&&(p.destroy(),p=null),d&&(d.destroy(),d=null),f={},l=c=h=v=null,this.trigger("Destroy"),this.unbindAll(),a={}}})},o.File=function(){function n(n){o.extend(this,{id:o.guid(),name:n.name||n.fileName,type:n.type||"",size:n.size||n.fileSize,origSize:n.size||n.fileSize,loaded:0,percent:0,status:o.QUEUED,lastModifiedDate:n.lastModifiedDate||(new Date).toLocaleString(),getNative:function(){var e=this.getSource().getSource();return t.inArray(t.typeOf(e),["blob","file"])!==-1?e:null},getSource:function(){return e[this.id]?e[this.id]:null},destroy:function(){var t=this.getSource();t&&(t.destroy(),delete e[this.id])}}),e[this.id]=n}var e={};return n}(),o.QueueProgress=function(){var e=this;e.size=0,e.loaded=0,e.uploaded=0,e.failed=0,e.queued=0,e.percent=0,e.bytesPerSec=0,e.reset=function(){e.size=e.loaded=e.uploaded=e.failed=e.queued=e.percent=e.bytesPerSec=0}},e.plupload=o})(window,mOxie);
\ No newline at end of file +;(function(e,t,n){function s(e){function r(e,t,r){var i={chunks:"slice_blob",jpgresize:"send_binary_string",pngresize:"send_binary_string",progress:"report_upload_progress",multi_selection:"select_multiple",dragdrop:"drag_and_drop",drop_element:"drag_and_drop",headers:"send_custom_headers",canSendBinary:"send_binary",triggerDialog:"summon_file_dialog"};i[e]?n[i[e]]=t:r||(n[e]=t)}var t=e.required_features,n={};return typeof t=="string"?o.each(t.split(/\s*,\s*/),function(e){r(e,!0)}):typeof t=="object"?o.each(t,function(e,t){r(t,e)}):t===!0&&(e.multipart||(n.send_binary_string=!0),e.chunk_size>0&&(n.slice_blob=!0),e.resize.enabled&&(n.send_binary_string=!0),o.each(e,function(e,t){r(t,!!e,!0)})),n}var r=e.setTimeout,i={},o={VERSION:"2.1.1",STOPPED:1,STARTED:2,QUEUED:1,UPLOADING:2,FAILED:4,DONE:5,GENERIC_ERROR:-100,HTTP_ERROR:-200,IO_ERROR:-300,SECURITY_ERROR:-400,INIT_ERROR:-500,FILE_SIZE_ERROR:-600,FILE_EXTENSION_ERROR:-601,FILE_DUPLICATE_ERROR:-602,IMAGE_FORMAT_ERROR:-700,IMAGE_MEMORY_ERROR:-701,IMAGE_DIMENSIONS_ERROR:-702,mimeTypes:t.mimes,ua:t.ua,typeOf:t.typeOf,extend:t.extend,guid:t.guid,get:function(n){var r=[],i;t.typeOf(n)!=="array"&&(n=[n]);var s=n.length;while(s--)i=t.get(n[s]),i&&r.push(i);return r.length?r:null},each:t.each,getPos:t.getPos,getSize:t.getSize,xmlEncode:function(e){var t={"<":"lt",">":"gt","&":"amp",'"':"quot","'":"#39"},n=/[<>&\"\']/g;return e?(""+e).replace(n,function(e){return t[e]?"&"+t[e]+";":e}):e},toArray:t.toArray,inArray:t.inArray,addI18n:t.addI18n,translate:t.translate,isEmptyObj:t.isEmptyObj,hasClass:t.hasClass,addClass:t.addClass,removeClass:t.removeClass,getStyle:t.getStyle,addEvent:t.addEvent,removeEvent:t.removeEvent,removeAllEvents:t.removeAllEvents,cleanName:function(e){var t,n;n=[/[\300-\306]/g,"A",/[\340-\346]/g,"a",/\307/g,"C",/\347/g,"c",/[\310-\313]/g,"E",/[\350-\353]/g,"e",/[\314-\317]/g,"I",/[\354-\357]/g,"i",/\321/g,"N",/\361/g,"n",/[\322-\330]/g,"O",/[\362-\370]/g,"o",/[\331-\334]/g,"U",/[\371-\374]/g,"u"];for(t=0;t<n.length;t+=2)e=e.replace(n[t],n[t+1]);return e=e.replace(/\s+/g,"_"),e=e.replace(/[^a-z0-9_\-\.]+/gi,""),e},buildUrl:function(e,t){var n="";return o.each(t,function(e,t){n+=(n?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(e)}),n&&(e+=(e.indexOf("?")>0?"&":"?")+n),e},formatSize:function(e){function t(e,t){return Math.round(e*Math.pow(10,t))/Math.pow(10,t)}if(e===n||/\D/.test(e))return o.translate("N/A");var r=Math.pow(1024,4);return e>r?t(e/r,1)+" "+o.translate("tb"):e>(r/=1024)?t(e/r,1)+" "+o.translate("gb"):e>(r/=1024)?t(e/r,1)+" "+o.translate("mb"):e>1024?Math.round(e/1024)+" "+o.translate("kb"):e+" "+o.translate("b")},parseSize:t.parseSizeStr,predictRuntime:function(e,n){var r,i;return r=new o.Uploader(e),i=t.Runtime.thatCan(r.getOption().required_features,n||e.runtimes),r.destroy(),i},addFileFilter:function(e,t){i[e]=t}};o.addFileFilter("mime_types",function(e,t,n){e.length&&!e.regexp.test(t.name)?(this.trigger("Error",{code:o.FILE_EXTENSION_ERROR,message:o.translate("File extension error."),file:t}),n(!1)):n(!0)}),o.addFileFilter("max_file_size",function(e,t,n){var r;e=o.parseSize(e),t.size!==r&&e&&t.size>e?(this.trigger("Error",{code:o.FILE_SIZE_ERROR,message:o.translate("File size error."),file:t}),n(!1)):n(!0)}),o.addFileFilter("prevent_duplicates",function(e,t,n){if(e){var r=this.files.length;while(r--)if(t.name===this.files[r].name&&t.size===this.files[r].size){this.trigger("Error",{code:o.FILE_DUPLICATE_ERROR,message:o.translate("Duplicate file error."),file:t}),n(!1);return}}n(!0)}),o.Uploader=function(e){function g(){var e,t=0,n;if(this.state==o.STARTED){for(n=0;n<f.length;n++)!e&&f[n].status==o.QUEUED?(e=f[n],this.trigger("BeforeUpload",e)&&(e.status=o.UPLOADING,this.trigger("UploadFile",e))):t++;t==f.length&&(this.state!==o.STOPPED&&(this.state=o.STOPPED,this.trigger("StateChanged")),this.trigger("UploadComplete",f))}}function y(e){e.percent=e.size>0?Math.ceil(e.loaded/e.size*100):100,b()}function b(){var e,t;d.reset();for(e=0;e<f.length;e++)t=f[e],t.size!==n?(d.size+=t.origSize,d.loaded+=t.loaded*t.origSize/t.size):d.size=n,t.status==o.DONE?d.uploaded++:t.status==o.FAILED?d.failed++:d.queued++;d.size===n?d.percent=f.length>0?Math.ceil(d.uploaded/f.length*100):0:(d.bytesPerSec=Math.ceil(d.loaded/((+(new Date)-p||1)/1e3)),d.percent=d.size>0?Math.ceil(d.loaded/d.size*100):0)}function w(){var e=c[0]||h[0];return e?e.getRuntime().uid:!1}function E(e,n){if(e.ruid){var r=t.Runtime.getInfo(e.ruid);if(r)return r.can(n)}return!1}function S(){this.bind("FilesAdded",C),this.bind("CancelUpload",M),this.bind("BeforeUpload",k),this.bind("UploadFile",L),this.bind("UploadProgress",A),this.bind("StateChanged",O),this.bind("QueueChanged",b),this.bind("Error",D),this.bind("FileUploaded",_),this.bind("Destroy",P)}function x(e,n){var r=this,i=0,s=[],u={accept:e.filters.mime_types,runtime_order:e.runtimes,required_caps:e.required_features,preferred_caps:l,swf_url:e.flash_swf_url,xap_url:e.silverlight_xap_url};o.each(e.runtimes.split(/\s*,\s*/),function(t){e[t]&&(u[t]=e[t])}),e.browse_button&&o.each(e.browse_button,function(n){s.push(function(s){var a=new t.FileInput(o.extend({},u,{name:e.file_data_name,multiple:e.multi_selection,container:e.container,browse_button:n}));a.onready=function(){var e=t.Runtime.getInfo(this.ruid);t.extend(r.features,{chunks:e.can("slice_blob"),multipart:e.can("send_multipart"),multi_selection:e.can("select_multiple")}),i++,c.push(this),s()},a.onchange=function(){r.addFile(this.files)},a.bind("mouseenter mouseleave mousedown mouseup",function(r){v||(e.browse_button_hover&&("mouseenter"===r.type?t.addClass(n,e.browse_button_hover):"mouseleave"===r.type&&t.removeClass(n,e.browse_button_hover)),e.browse_button_active&&("mousedown"===r.type?t.addClass(n,e.browse_button_active):"mouseup"===r.type&&t.removeClass(n,e.browse_button_active)))}),a.bind("error runtimeerror",function(){a=null,s()}),a.init()})}),e.drop_element&&o.each(e.drop_element,function(e){s.push(function(n){var s=new t.FileDrop(o.extend({},u,{drop_zone:e}));s.onready=function(){var e=t.Runtime.getInfo(this.ruid);r.features.dragdrop=e.can("drag_and_drop"),i++,h.push(this),n()},s.ondrop=function(){r.addFile(this.files)},s.bind("error runtimeerror",function(){s=null,n()}),s.init()})}),t.inSeries(s,function(){typeof n=="function"&&n(i)})}function T(e,n,r){var i=new t.Image;try{i.onload=function(){i.downsize(n.width,n.height,n.crop,n.preserve_headers)},i.onresize=function(){r(this.getAsBlob(e.type,n.quality)),this.destroy()},i.onerror=function(){r(e)},i.load(e)}catch(s){r(e)}}function N(e,n,r){function f(e,t,n){var r=a[e];switch(e){case"max_file_size":e==="max_file_size"&&(a.max_file_size=a.filters.max_file_size=t);break;case"chunk_size":if(t=o.parseSize(t))a[e]=t;break;case"filters":o.typeOf(t)==="array"&&(t={mime_types:t}),n?o.extend(a.filters,t):a.filters=t,t.mime_types&&(a.filters.mime_types.regexp=function(e){var t=[];return o.each(e,function(e){o.each(e.extensions.split(/,/),function(e){/^\s*\*\s*$/.test(e)?t.push("\\.*"):t.push("\\."+e.replace(new RegExp("["+"/^$.*+?|()[]{}\\".replace(/./g,"\\$&")+"]","g"),"\\$&"))})}),new RegExp("("+t.join("|")+")$","i")}(a.filters.mime_types));break;case"resize":n?o.extend(a.resize,t,{enabled:!0}):a.resize=t;break;case"prevent_duplicates":a.prevent_duplicates=a.filters.prevent_duplicates=!!t;break;case"browse_button":case"drop_element":t=o.get(t);case"container":case"runtimes":case"multi_selection":case"flash_swf_url":case"silverlight_xap_url":a[e]=t,n||(u=!0);break;default:a[e]=t}n||i.trigger("OptionChanged",e,t,r)}var i=this,u=!1;typeof e=="object"?o.each(e,function(e,t){f(t,e,r)}):f(e,n,r),r?(a.required_features=s(o.extend({},a)),l=s(o.extend({},a,{required_features:!0}))):u&&(i.trigger("Destroy"),x.call(i,a,function(e){e?(i.runtime=t.Runtime.getInfo(w()).type,i.trigger("Init",{runtime:i.runtime}),i.trigger("PostInit")):i.trigger("Error",{code:o.INIT_ERROR,message:o.translate("Init error.")})}))}function C(e,t){[].push.apply(f,t),e.trigger("QueueChanged"),e.refresh()}function k(e,t){if(a.unique_names){var n=t.name.match(/\.([^.]+)$/),r="part";n&&(r=n[1]),t.target_name=t.id+"."+r}}function L(e,n){function h(){u-->0?r(p,1e3):(n.loaded=f,e.trigger("Error",{code:o.HTTP_ERROR,message:o.translate("HTTP Error."),file:n,response:m.responseText,status:m.status,responseHeaders:m.getAllResponseHeaders()}))}function p(){var d,v,g,y;if(n.status==o.DONE||n.status==o.FAILED||e.state==o.STOPPED)return;g={name:n.target_name||n.name},s&&a.chunks&&c.size>s?(y=Math.min(s,c.size-f),d=c.slice(f,f+y)):(y=c.size,d=c),s&&a.chunks&&(e.settings.send_chunk_number?(g.chunk=Math.ceil(f/s),g.chunks=Math.ceil(c.size/s)):(g.offset=f,g.total=c.size)),m=new t.XMLHttpRequest,m.upload&&(m.upload.onprogress=function(t){n.loaded=Math.min(n.size,f+t.loaded),e.trigger("UploadProgress",n)}),m.onload=function(){if(m.status>=400){h();return}u=e.settings.max_retries,y<c.size?(d.destroy(),f+=y,n.loaded=Math.min(f,c.size),e.trigger("ChunkUploaded",n,{offset:n.loaded,total:c.size,response:m.responseText,status:m.status,responseHeaders:m.getAllResponseHeaders()}),t.Env.browser==="Android Browser"&&e.trigger("UploadProgress",n)):n.loaded=n.size,d=v=null,!f||f>=c.size?(n.size!=n.origSize&&(c.destroy(),c=null),e.trigger("UploadProgress",n),n.status=o.DONE,e.trigger("FileUploaded",n,{response:m.responseText,status:m.status,responseHeaders:m.getAllResponseHeaders()})):r(p,1)},m.onerror=function(){h()},m.onloadend=function(){this.destroy(),m=null},e.settings.multipart&&a.multipart?(g.name=n.target_name||n.name,m.open("post",i,!0),o.each(e.settings.headers,function(e,t){m.setRequestHeader(t,e)}),v=new t.FormData,o.each(o.extend(g,e.settings.multipart_params),function(e,t){v.append(t,e)}),v.append(e.settings.file_data_name,d),m.send(v,{runtime_order:e.settings.runtimes,required_caps:e.settings.required_features,preferred_caps:l,swf_url:e.settings.flash_swf_url,xap_url:e.settings.silverlight_xap_url})):(i=o.buildUrl(e.settings.url,o.extend(g,e.settings.multipart_params)),m.open("post",i,!0),m.setRequestHeader("Content-Type","application/octet-stream"),o.each(e.settings.headers,function(e,t){m.setRequestHeader(t,e)}),m.send(d,{runtime_order:e.settings.runtimes,required_caps:e.settings.required_features,preferred_caps:l,swf_url:e.settings.flash_swf_url,xap_url:e.settings.silverlight_xap_url}))}var i=e.settings.url,s=e.settings.chunk_size,u=e.settings.max_retries,a=e.features,f=0,c;n.loaded&&(f=n.loaded=s*Math.floor(n.loaded/s)),c=n.getSource(),e.settings.resize.enabled&&E(c,"send_binary_string")&&!!~t.inArray(c.type,["image/jpeg","image/png"])?T.call(this,c,e.settings.resize,function(e){c=e,n.size=e.size,p()}):p()}function A(e,t){y(t)}function O(e){if(e.state==o.STARTED)p=+(new Date);else if(e.state==o.STOPPED)for(var t=e.files.length-1;t>=0;t--)e.files[t].status==o.UPLOADING&&(e.files[t].status=o.QUEUED,b())}function M(){m&&m.abort()}function _(e){b(),r(function(){g.call(e)},1)}function D(e,t){t.file&&(t.file.status=o.FAILED,y(t.file),e.state==o.STARTED&&(e.trigger("CancelUpload"),r(function(){g.call(e)},1)))}function P(e){e.stop(),o.each(f,function(e){e.destroy()}),f=[],c.length&&(o.each(c,function(e){e.destroy()}),c=[]),h.length&&(o.each(h,function(e){e.destroy()}),h=[]),l={},v=!1,p=m=null,d.reset()}var u=o.guid(),a,f=[],l={},c=[],h=[],p,d,v=!1,m;a={runtimes:t.Runtime.order,max_retries:0,chunk_size:0,multipart:!0,multi_selection:!0,file_data_name:"file",flash_swf_url:"js/Moxie.swf",silverlight_xap_url:"js/Moxie.xap",filters:{mime_types:[],prevent_duplicates:!1,max_file_size:0},resize:{enabled:!1,preserve_headers:!0,crop:!1},send_chunk_number:!0},N.call(this,e,null,!0),d=new o.QueueProgress,o.extend(this,{id:u,uid:u,state:o.STOPPED,features:{},runtime:null,files:f,settings:a,total:d,init:function(){var e=this;typeof a.preinit=="function"?a.preinit(e):o.each(a.preinit,function(t,n){e.bind(n,t)});if(!a.browse_button||!a.url){this.trigger("Error",{code:o.INIT_ERROR,message:o.translate("Init error.")});return}S.call(this),x.call(this,a,function(n){typeof a.init=="function"?a.init(e):o.each(a.init,function(t,n){e.bind(n,t)}),n?(e.runtime=t.Runtime.getInfo(w()).type,e.trigger("Init",{runtime:e.runtime}),e.trigger("PostInit")):e.trigger("Error",{code:o.INIT_ERROR,message:o.translate("Init error.")})})},setOption:function(e,t){N.call(this,e,t,!this.runtime)},getOption:function(e){return e?a[e]:a},refresh:function(){c.length&&o.each(c,function(e){e.trigger("Refresh")}),this.trigger("Refresh")},start:function(){this.state!=o.STARTED&&(this.state=o.STARTED,this.trigger("StateChanged"),g.call(this))},stop:function(){this.state!=o.STOPPED&&(this.state=o.STOPPED,this.trigger("StateChanged"),this.trigger("CancelUpload"))},disableBrowse:function(){v=arguments[0]!==n?arguments[0]:!0,c.length&&o.each(c,function(e){e.disable(v)}),this.trigger("DisableBrowse",v)},getFile:function(e){var t;for(t=f.length-1;t>=0;t--)if(f[t].id===e)return f[t]},addFile:function(e,n){function l(e,n){var r=[];t.each(s.settings.filters,function(t,n){i[n]&&r.push(function(r){i[n].call(s,t,e,function(e){r(!e)})})}),t.inSeries(r,n)}function c(e){var i=t.typeOf(e);if(e instanceof t.File){if(!e.ruid&&!e.isDetached()){if(!f)return!1;e.ruid=f,e.connectRuntime(f)}c(new o.File(e))}else e instanceof t.Blob?(c(e.getSource()),e.destroy()):e instanceof o.File?(n&&(e.name=n),u.push(function(t){l(e,function(n){n||(a.push(e),s.trigger("FileFiltered",e)),r(t,1)})})):t.inArray(i,["file","blob"])!==-1?c(new t.File(null,e)):i==="node"&&t.typeOf(e.files)==="filelist"?t.each(e.files,c):i==="array"&&(n=null,t.each(e,c))}var s=this,u=[],a=[],f;f=w(),c(e),u.length&&t.inSeries(u,function(){a.length&&s.trigger("FilesAdded",a)})},removeFile:function(e){var t=typeof e=="string"?e:e.id;for(var n=f.length-1;n>=0;n--)if(f[n].id===t)return this.splice(n,1)[0]},splice:function(e,t){var r=f.splice(e===n?0:e,t===n?f.length:t),i=!1;return this.state==o.STARTED&&(i=!0,this.stop()),this.trigger("FilesRemoved",r),o.each(r,function(e){e.destroy()}),this.trigger("QueueChanged"),this.refresh(),i&&this.start(),r},bind:function(e,t,n){var r=this;o.Uploader.prototype.bind.call(this,e,function(){var e=[].slice.call(arguments);return e.splice(0,1,r),t.apply(this,e)},0,n)},destroy:function(){this.trigger("Destroy"),a=d=null,this.unbindAll()}})},o.Uploader.prototype=t.EventTarget.instance,o.File=function(){function n(n){o.extend(this,{id:o.guid(),name:n.name||n.fileName,type:n.type||"",size:n.size||n.fileSize,origSize:n.size||n.fileSize,loaded:0,percent:0,status:o.QUEUED,lastModifiedDate:n.lastModifiedDate||(new Date).toLocaleString(),getNative:function(){var e=this.getSource().getSource();return t.inArray(t.typeOf(e),["blob","file"])!==-1?e:null},getSource:function(){return e[this.id]?e[this.id]:null},destroy:function(){var t=this.getSource();t&&(t.destroy(),delete e[this.id])}}),e[this.id]=n}var e={};return n}(),o.QueueProgress=function(){var e=this;e.size=0,e.loaded=0,e.uploaded=0,e.failed=0,e.queued=0,e.percent=0,e.bytesPerSec=0,e.reset=function(){e.size=e.loaded=e.uploaded=e.failed=e.queued=e.percent=e.bytesPerSec=0}},e.plupload=o})(window,mOxie);
\ No newline at end of file diff --git a/phpBB/config/auth_providers.yml b/phpBB/config/auth_providers.yml index e1c289334e..dac8b9d252 100644 --- a/phpBB/config/auth_providers.yml +++ b/phpBB/config/auth_providers.yml @@ -10,6 +10,7 @@ services:          arguments:              - @dbal.conn              - @config +            - @passwords.manager              - @request              - @user              - %core.root_path% @@ -21,6 +22,7 @@ services:          arguments:              - @dbal.conn              - @config +            - @passwords.manager              - @request              - @user              - %core.root_path% @@ -32,6 +34,7 @@ services:          arguments:              - @dbal.conn              - @config +            - @passwords.manager              - @user          tags:              - { name: auth.provider } @@ -40,6 +43,7 @@ services:          arguments:              - @dbal.conn              - @config +            - @passwords.manager              - @request              - @user              - %tables.auth_provider_oauth_token_storage% diff --git a/phpBB/config/console.yml b/phpBB/config/console.yml index c85a9d19ed..a4aae75e40 100644 --- a/phpBB/config/console.yml +++ b/phpBB/config/console.yml @@ -1,4 +1,39 @@  services: +    console.command.config.delete: +        class: phpbb\console\command\config\delete +        arguments: +            - @config +        tags: +            - { name: console.command } + +    console.command.config.increment: +        class: phpbb\console\command\config\increment +        arguments: +            - @config +        tags: +            - { name: console.command } + +    console.command.config.get: +        class: phpbb\console\command\config\get +        arguments: +            - @config +        tags: +            - { name: console.command } + +    console.command.config.set: +        class: phpbb\console\command\config\set +        arguments: +            - @config +        tags: +            - { name: console.command } + +    console.command.config.set_atomic: +        class: phpbb\console\command\config\set_atomic +        arguments: +            - @config +        tags: +            - { name: console.command } +      console.command.extension.disable:          class: phpbb\console\command\extension\disable          arguments: diff --git a/phpBB/config/passwords.yml b/phpBB/config/passwords.yml new file mode 100644 index 0000000000..9e249a2c12 --- /dev/null +++ b/phpBB/config/passwords.yml @@ -0,0 +1,62 @@ +parameters: +    passwords.algorithms: +        - passwords.driver.bcrypt_2y +        - passwords.driver.bcrypt +        - passwords.driver.salted_md5 +        - passwords.driver.phpass + +services: +    passwords.driver.bcrypt: +        class: phpbb\passwords\driver\bcrypt +        arguments: +            - @config +            - @passwords.driver_helper +        tags: +            - { name: passwords.driver } + +    passwords.driver.bcrypt_2y: +        class: phpbb\passwords\driver\bcrypt_2y +        arguments: +            - @config +            - @passwords.driver_helper +        tags: +            - { name: passwords.driver } + +    passwords.driver.salted_md5: +        class: phpbb\passwords\driver\salted_md5 +        arguments: +            - @config +            - @passwords.driver_helper +        tags: +            - { name: passwords.driver } + +    passwords.driver.phpass: +        class: phpbb\passwords\driver\phpass +        arguments: +            - @config +            - @passwords.driver_helper +        tags: +            - { name: passwords.driver } + +    passwords.driver_collection: +        class: phpbb\di\service_collection +        arguments: +            - @service_container +        tags: +            - { name: service_collection, tag: passwords.driver } + +    passwords.driver_helper: +        class: phpbb\passwords\driver\helper +        arguments: +            - @config + +    passwords.manager: +        class: phpbb\passwords\manager +        arguments: +            - @config +            - @passwords.driver_collection +            - @passwords.helper +            - %passwords.algorithms% + +    passwords.helper: +        class: phpbb\passwords\helper diff --git a/phpBB/config/profilefields.yml b/phpBB/config/profilefields.yml new file mode 100644 index 0000000000..5a861a4b56 --- /dev/null +++ b/phpBB/config/profilefields.yml @@ -0,0 +1,82 @@ +services: +    profilefields.manager: +        class: phpbb\profilefields\manager +        arguments: +            - @auth +            - @dbal.conn +            - @request +            - @template +            - @profilefields.type_collection +            - @user +            - %tables.profile_fields% +            - %tables.profile_fields_language% +            - %tables.profile_fields_data% + +    profilefields.lang_helper: +        class: phpbb\profilefields\lang_helper +        arguments: +            - @dbal.conn +            - %tables.profile_fields_options_language% + +    profilefields.type_collection: +        class: phpbb\di\service_collection +        arguments: +            - @service_container +        tags: +            - { name: service_collection, tag: profilefield.type } + +    profilefields.type.bool: +        class: phpbb\profilefields\type\type_bool +        arguments: +            - @profilefields.lang_helper +            - @request +            - @template +            - @user +        tags: +            - { name: profilefield.type } + +    profilefields.type.date: +        class: phpbb\profilefields\type\type_date +        arguments: +            - @request +            - @template +            - @user +        tags: +            - { name: profilefield.type } + +    profilefields.type.dropdown: +        class: phpbb\profilefields\type\type_dropdown +        arguments: +            - @profilefields.lang_helper +            - @request +            - @template +            - @user +        tags: +            - { name: profilefield.type } + +    profilefields.type.int: +        class: phpbb\profilefields\type\type_int +        arguments: +            - @request +            - @template +            - @user +        tags: +            - { name: profilefield.type } + +    profilefields.type.string: +        class: phpbb\profilefields\type\type_string +        arguments: +            - @request +            - @template +            - @user +        tags: +            - { name: profilefield.type } + +    profilefields.type.text: +        class: phpbb\profilefields\type\type_text +        arguments: +            - @request +            - @template +            - @user +        tags: +            - { name: profilefield.type } diff --git a/phpBB/config/services.yml b/phpBB/config/services.yml index c11184d6b6..735626810f 100644 --- a/phpBB/config/services.yml +++ b/phpBB/config/services.yml @@ -8,6 +8,8 @@ imports:      - { resource: auth_providers.yml }      - { resource: console.yml }      - { resource: mimetype_guessers.yml } +    - { resource: passwords.yml } +    - { resource: profilefields.yml }  services:      acl.permissions: diff --git a/phpBB/config/tables.yml b/phpBB/config/tables.yml index 0d364eb6b0..e4f7bda89b 100644 --- a/phpBB/config/tables.yml +++ b/phpBB/config/tables.yml @@ -10,6 +10,10 @@ parameters:      tables.modules: %core.table_prefix%modules      tables.notification_types: %core.table_prefix%notification_types      tables.notifications: %core.table_prefix%notifications +    tables.profile_fields: %core.table_prefix%profile_fields +    tables.profile_fields_data: %core.table_prefix%profile_fields_data +    tables.profile_fields_options_language: %core.table_prefix%profile_fields_lang +    tables.profile_fields_language: %core.table_prefix%profile_lang      tables.posts: %core.table_prefix%posts      tables.topics: %core.table_prefix%topics      tables.user_notifications: %core.table_prefix%user_notifications diff --git a/phpBB/docs/AUTHORS b/phpBB/docs/AUTHORS index d70a1940ff..5ca52c19c0 100644 --- a/phpBB/docs/AUTHORS +++ b/phpBB/docs/AUTHORS @@ -23,10 +23,11 @@ involved in phpBB.  phpBB Lead Developer:  naderman (Nils Adermann)  phpBB Developers:      bantu (Andreas Fischer) -                       EXreaction (Nathan Guse)                         dhruv.goel92 (Dhruv Goel) +                       EXreaction (Nathan Guse)                         imkingdavid (David King)                         nickvergessen (Joas Schilling) +                       prototech (Cesar Gallegos)  Contributions by:      leviatan21 (Gabriel Vazquez)                         Raimon (Raimon Meuldijk) diff --git a/phpBB/docs/events.md b/phpBB/docs/events.md index f1df662f76..2d5f6cc990 100644 --- a/phpBB/docs/events.md +++ b/phpBB/docs/events.md @@ -211,6 +211,7 @@ overall_header_navigation_append  ===  * Locations:      + styles/prosilver/template/overall_header.html +    + styles/subsilver2/template/overall_header.html  * Since: 3.1.0-a1  * Purpose: Add links after the navigation links in the header @@ -218,6 +219,7 @@ overall_header_navigation_prepend  ===  * Locations:      + styles/prosilver/template/overall_header.html +    + styles/subsilver2/template/overall_header.html  * Since: 3.1.0-a1  * Purpose: Add links before the navigation links in the header diff --git a/phpBB/includes/acp/acp_forums.php b/phpBB/includes/acp/acp_forums.php index 029f4b23c9..a1af8c489d 100644 --- a/phpBB/includes/acp/acp_forums.php +++ b/phpBB/includes/acp/acp_forums.php @@ -926,7 +926,7 @@ class acp_forums  	*/  	function update_forum_data(&$forum_data)  	{ -		global $db, $user, $cache, $phpbb_root_path, $phpbb_dispatcher; +		global $db, $user, $cache, $phpbb_root_path, $phpbb_container, $phpbb_dispatcher;  		$errors = array(); @@ -1030,7 +1030,10 @@ class acp_forums  		}  		else  		{ -			$forum_data_sql['forum_password'] = phpbb_hash($forum_data_sql['forum_password']); +			// Instantiate passwords manager +			$passwords_manager = $phpbb_container->get('passwords.manager'); + +			$forum_data_sql['forum_password'] = $passwords_manager->hash($forum_data_sql['forum_password']);  		}  		unset($forum_data_sql['forum_password_unset']); diff --git a/phpBB/includes/acp/acp_icons.php b/phpBB/includes/acp/acp_icons.php index f9295e2b47..20b1f56182 100644 --- a/phpBB/includes/acp/acp_icons.php +++ b/phpBB/includes/acp/acp_icons.php @@ -832,9 +832,10 @@ class acp_icons  					WHERE {$fields}_order = $switch_order_id  						AND {$fields}_id <> $icon_id";  				$db->sql_query($sql); +				$move_executed = (bool) $db->sql_affectedrows();  				// Only update the other entry too if the previous entry got updated -				if ($db->sql_affectedrows()) +				if ($move_executed)  				{  					$sql = "UPDATE $table  						SET {$fields}_order = $switch_order_id @@ -846,6 +847,14 @@ class acp_icons  				$cache->destroy('_icons');  				$cache->destroy('sql', $table); +				if ($request->is_ajax()) +				{ +					$json_response = new \phpbb\json_response; +					$json_response->send(array( +						'success'	=> $move_executed, +					)); +				} +  			break;  		} diff --git a/phpBB/includes/acp/acp_modules.php b/phpBB/includes/acp/acp_modules.php index 100e33044b..c124377ba9 100644 --- a/phpBB/includes/acp/acp_modules.php +++ b/phpBB/includes/acp/acp_modules.php @@ -170,6 +170,14 @@ class acp_modules  					$this->remove_cache_file();  				} +				if ($request->is_ajax()) +				{ +					$json_response = new \phpbb\json_response; +					$json_response->send(array( +						'success'	=> ($move_module_name !== false), +					)); +				} +  			break;  			case 'quickadd': diff --git a/phpBB/includes/acp/acp_permission_roles.php b/phpBB/includes/acp/acp_permission_roles.php index 17e48d6576..aca45575d3 100644 --- a/phpBB/includes/acp/acp_permission_roles.php +++ b/phpBB/includes/acp/acp_permission_roles.php @@ -27,6 +27,7 @@ class acp_permission_roles  	{  		global $db, $user, $auth, $template, $cache, $phpbb_container;  		global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; +		global $request;  		include_once($phpbb_root_path . 'includes/functions_user.' . $phpEx);  		include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx); @@ -46,6 +47,11 @@ class acp_permission_roles  		$form_name = 'acp_permissions';  		add_form_key($form_name); +		if (!$role_id && in_array($action, array('remove', 'edit', 'move_up', 'move_down'))) +		{ +			trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); +		} +  		switch ($mode)  		{  			case 'admin_roles': @@ -85,11 +91,6 @@ class acp_permission_roles  			{  				case 'remove': -					if (!$role_id) -					{ -						trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); -					} -  					$sql = 'SELECT *  						FROM ' . ACL_ROLES_TABLE . '  						WHERE role_id = ' . $role_id; @@ -123,10 +124,6 @@ class acp_permission_roles  				break;  				case 'edit': -					if (!$role_id) -					{ -						trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); -					}  					// Get role we edit  					$sql = 'SELECT * @@ -273,12 +270,7 @@ class acp_permission_roles  			case 'edit':  				if ($action == 'edit') -				{ -					if (!$role_id) -					{ -						trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); -					} -					 +				{					  					$sql = 'SELECT *  						FROM ' . ACL_ROLES_TABLE . '  						WHERE role_id = ' . $role_id; @@ -366,7 +358,18 @@ class acp_permission_roles  			case 'move_up':  			case 'move_down': -				$order = request_var('order', 0); +				$sql = 'SELECT role_order +					FROM ' . ACL_ROLES_TABLE . " +					WHERE role_id = $role_id"; +				$result = $db->sql_query($sql); +				$order = $db->sql_fetchfield('role_order'); +				$db->sql_freeresult($result); + +				if ($order === false || ($order == 0 && $action == 'move_up')) +				{ +					break; +				} +				$order = (int) $order;  				$order_total = $order * 2 + (($action == 'move_up') ? -1 : 1);  				$sql = 'UPDATE ' . ACL_ROLES_TABLE . ' @@ -375,6 +378,14 @@ class acp_permission_roles  						AND role_order IN ($order, " . (($action == 'move_up') ? $order - 1 : $order + 1) . ')';  				$db->sql_query($sql); +				if ($request->is_ajax()) +				{ +					$json_response = new \phpbb\json_response; +					$json_response->send(array( +						'success'	=> (bool) $db->sql_affectedrows(), +					)); +				} +  			break;  		} @@ -421,8 +432,8 @@ class acp_permission_roles  				'U_EDIT'			=> $this->u_action . '&action=edit&role_id=' . $row['role_id'],  				'U_REMOVE'			=> $this->u_action . '&action=remove&role_id=' . $row['role_id'], -				'U_MOVE_UP'			=> $this->u_action . '&action=move_up&order=' . $row['role_order'], -				'U_MOVE_DOWN'		=> $this->u_action . '&action=move_down&order=' . $row['role_order'], +				'U_MOVE_UP'			=> $this->u_action . '&action=move_up&role_id=' . $row['role_id'], +				'U_MOVE_DOWN'		=> $this->u_action . '&action=move_down&role_id=' . $row['role_id'],  				'U_DISPLAY_ITEMS'	=> ($row['role_id'] == $display_item) ? '' : $this->u_action . '&display_item=' . $row['role_id'] . '#assigned_to')  			); diff --git a/phpBB/includes/acp/acp_profile.php b/phpBB/includes/acp/acp_profile.php index 4e8145009f..2fa1f38bd3 100644 --- a/phpBB/includes/acp/acp_profile.php +++ b/phpBB/includes/acp/acp_profile.php @@ -24,37 +24,34 @@ class acp_profile  	var $edit_lang_id;  	var $lang_defs; +	protected $type_collection;  	function main($id, $mode)  	{  		global $config, $db, $user, $auth, $template, $cache;  		global $phpbb_root_path, $phpbb_admin_path, $phpEx, $table_prefix; -		global $request; +		global $request, $phpbb_container;  		include($phpbb_root_path . 'includes/functions_posting.' . $phpEx);  		include($phpbb_root_path . 'includes/functions_user.' . $phpEx); -		include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx);  		$user->add_lang(array('ucp', 'acp/profile'));  		$this->tpl_name = 'acp_profile';  		$this->page_title = 'ACP_CUSTOM_PROFILE_FIELDS'; +		$field_id = $request->variable('field_id', 0);  		$action = (isset($_POST['create'])) ? 'create' : request_var('action', '');  		$error = array();  		$s_hidden_fields = ''; -		// Define some default values for each field type -		$default_values = array( -			FIELD_STRING	=> array('field_length' => 10, 'field_minlen' => 0, 'field_maxlen' => 20, 'field_validation' => '.*', 'field_novalue' => '', 'field_default_value' => ''), -			FIELD_TEXT		=> array('field_length' => '5|80', 'field_minlen' => 0, 'field_maxlen' => 1000, 'field_validation' => '.*', 'field_novalue' => '', 'field_default_value' => ''), -			FIELD_INT		=> array('field_length' => 5, 'field_minlen' => 0, 'field_maxlen' => 100, 'field_validation' => '', 'field_novalue' => 0, 'field_default_value' => 0), -			FIELD_DATE		=> array('field_length' => 10, 'field_minlen' => 10, 'field_maxlen' => 10, 'field_validation' => '', 'field_novalue' => ' 0- 0-   0', 'field_default_value' => ' 0- 0-   0'), -			FIELD_BOOL		=> array('field_length' => 1, 'field_minlen' => 0, 'field_maxlen' => 0, 'field_validation' => '', 'field_novalue' => 0, 'field_default_value' => 0), -			FIELD_DROPDOWN	=> array('field_length' => 0, 'field_minlen' => 0, 'field_maxlen' => 5, 'field_validation' => '', 'field_novalue' => 0, 'field_default_value' => 0), -		); +		if (!$field_id && in_array($action, array('delete','activate', 'deactivate', 'move_up', 'move_down', 'edit'))) +		{ +			trigger_error($user->lang['NO_FIELD_ID'] . adm_back_link($this->u_action), E_USER_WARNING); +		} -		$cp = new custom_profile_admin(); +		$cp = $phpbb_container->get('profilefields.manager'); +		$this->type_collection = $phpbb_container->get('profilefields.type_collection');  		// Build Language array  		// Based on this, we decide which elements need to be edited later and which language items are missing @@ -88,22 +85,16 @@ class acp_profile  		// Have some fields been defined?  		if (isset($this->lang_defs['entry']))  		{ -			foreach ($this->lang_defs['entry'] as $field_id => $field_ary) +			foreach ($this->lang_defs['entry'] as $field_ident => $field_ary)  			{  				// Fill an array with the languages that are missing for each field -				$this->lang_defs['diff'][$field_id] = array_diff(array_values($this->lang_defs['iso']), $field_ary); +				$this->lang_defs['diff'][$field_ident] = array_diff(array_values($this->lang_defs['iso']), $field_ary);  			}  		}  		switch ($action)  		{  			case 'delete': -				$field_id = request_var('field_id', 0); - -				if (!$field_id) -				{ -					trigger_error($user->lang['NO_FIELD_ID'] . adm_back_link($this->u_action), E_USER_WARNING); -				}  				if (confirm_box(true))  				{ @@ -210,12 +201,6 @@ class acp_profile  			break;  			case 'activate': -				$field_id = request_var('field_id', 0); - -				if (!$field_id) -				{ -					trigger_error($user->lang['NO_FIELD_ID'] . adm_back_link($this->u_action), E_USER_WARNING); -				}  				$sql = 'SELECT lang_id  					FROM ' . LANG_TABLE . " @@ -256,12 +241,6 @@ class acp_profile  			break;  			case 'deactivate': -				$field_id = request_var('field_id', 0); - -				if (!$field_id) -				{ -					trigger_error($user->lang['NO_FIELD_ID'] . adm_back_link($this->u_action), E_USER_WARNING); -				}  				$sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . "  					SET field_active = 0 @@ -291,7 +270,19 @@ class acp_profile  			case 'move_up':  			case 'move_down': -				$field_order = request_var('order', 0); + +				$sql = 'SELECT field_order +					FROM ' . PROFILE_FIELDS_TABLE . " +					WHERE field_id = $field_id"; +				$result = $db->sql_query($sql); +				$field_order = $db->sql_fetchfield('field_order'); +				$db->sql_freeresult($result); + +				if ($field_order === false || ($field_order == 0 && $action == 'move_up')) +				{ +					break; +				} +				$field_order = (int) $field_order;  				$order_total = $field_order * 2 + (($action == 'move_up') ? -1 : 1);  				$sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . " @@ -299,12 +290,19 @@ class acp_profile  					WHERE field_order IN ($field_order, " . (($action == 'move_up') ? $field_order - 1 : $field_order + 1) . ')';  				$db->sql_query($sql); +				if ($request->is_ajax()) +				{ +					$json_response = new \phpbb\json_response; +					$json_response->send(array( +						'success'	=> (bool) $db->sql_affectedrows(), +					)); +				} +  			break;  			case 'create':  			case 'edit': -				$field_id = request_var('field_id', 0);  				$step = request_var('step', 1);  				$submit = (isset($_REQUEST['next']) || isset($_REQUEST['prev'])) ? true : false; @@ -316,11 +314,6 @@ class acp_profile  				// We are editing... we need to grab basic things  				if ($action == 'edit')  				{ -					if (!$field_id) -					{ -						trigger_error($user->lang['NO_FIELD_ID'] . adm_back_link($this->u_action), E_USER_WARNING); -					} -  					$sql = 'SELECT l.*, f.*  						FROM ' . PROFILE_LANG_TABLE . ' l, ' . PROFILE_FIELDS_TABLE . ' f  						WHERE l.lang_id = ' . $this->edit_lang_id . " @@ -350,6 +343,7 @@ class acp_profile  						$this->edit_lang_id = $field_row['lang_id'];  					}  					$field_type = $field_row['field_type']; +					$profile_field = $this->type_collection[$field_type];  					// Get language entries  					$sql = 'SELECT * @@ -373,14 +367,15 @@ class acp_profile  					// We are adding a new field, define basic params  					$lang_options = $field_row = array(); -					$field_type = request_var('field_type', 0); +					$field_type = request_var('field_type', ''); -					if (!$field_type) +					if (!isset($this->type_collection[$field_type]))  					{  						trigger_error($user->lang['NO_FIELD_TYPE'] . adm_back_link($this->u_action), E_USER_WARNING);  					} -					$field_row = array_merge($default_values[$field_type], array( +					$profile_field = $this->type_collection[$field_type]; +					$field_row = array_merge($profile_field->get_default_option_values(), array(  						'field_ident'		=> str_replace(' ', '_', utf8_clean_string(request_var('field_ident', '', true))),  						'field_required'	=> 0,  						'field_show_novalue'=> 0, @@ -405,23 +400,6 @@ class acp_profile  					3	=> array('l_lang_name', 'l_lang_explain', 'l_lang_default_value', 'l_lang_options')  				); -				// Text-based fields require the lang_default_value to be excluded -				if ($field_type == FIELD_STRING || $field_type == FIELD_TEXT) -				{ -					$exclude[1][] = 'lang_default_value'; -				} - -				// option-specific fields require lang_options to be excluded -				if ($field_type == FIELD_BOOL || $field_type == FIELD_DROPDOWN) -				{ -					$exclude[1][] = 'lang_options'; -				} - -				$cp->vars['field_ident']		= ($action == 'create' && $step == 1) ? utf8_clean_string(request_var('field_ident', $field_row['field_ident'], true)) : request_var('field_ident', $field_row['field_ident']); -				$cp->vars['lang_name']			= utf8_normalize_nfc(request_var('lang_name', $field_row['lang_name'], true)); -				$cp->vars['lang_explain']		= utf8_normalize_nfc(request_var('lang_explain', $field_row['lang_explain'], true)); -				$cp->vars['lang_default_value']	= utf8_normalize_nfc(request_var('lang_default_value', $field_row['lang_default_value'], true)); -  				// Visibility Options...  				$visibility_ary = array(  					'field_required', @@ -433,6 +411,13 @@ class acp_profile  					'field_hide',  				); +				$options = $profile_field->prepare_options_form($exclude, $visibility_ary); + +				$cp->vars['field_ident']		= ($action == 'create' && $step == 1) ? utf8_clean_string(request_var('field_ident', $field_row['field_ident'], true)) : request_var('field_ident', $field_row['field_ident']); +				$cp->vars['lang_name']			= utf8_normalize_nfc(request_var('lang_name', $field_row['lang_name'], true)); +				$cp->vars['lang_explain']		= utf8_normalize_nfc(request_var('lang_explain', $field_row['lang_explain'], true)); +				$cp->vars['lang_default_value']	= utf8_normalize_nfc(request_var('lang_default_value', $field_row['lang_default_value'], true)); +  				foreach ($visibility_ary as $val)  				{  					$cp->vars[$val] = ($submit || $save) ? request_var($val, 0) : $field_row[$val]; @@ -440,16 +425,6 @@ class acp_profile  				$cp->vars['field_no_view'] = request_var('field_no_view', (int) $field_row['field_no_view']); -				// A boolean field expects an array as the lang options -				if ($field_type == FIELD_BOOL) -				{ -					$options = utf8_normalize_nfc(request_var('lang_options', array(''), true)); -				} -				else -				{ -					$options = utf8_normalize_nfc(request_var('lang_options', '', true)); -				} -  				// If the user has submitted a form with options (i.e. dropdown field)  				if ($options)  				{ @@ -477,93 +452,9 @@ class acp_profile  				{  					$var = utf8_normalize_nfc(request_var($key, $field_row[$key], true)); -					// Manipulate the intended variables a little bit if needed -					if ($field_type == FIELD_DROPDOWN && $key == 'field_maxlen') -					{ -						// Get the number of options if this key is 'field_maxlen' -						$var = sizeof(explode("\n", utf8_normalize_nfc(request_var('lang_options', '', true)))); -					} -					else if ($field_type == FIELD_TEXT && $key == 'field_length') -					{ -						if (isset($_REQUEST['rows'])) -						{ -							$cp->vars['rows'] = request_var('rows', 0); -							$cp->vars['columns'] = request_var('columns', 0); -							$var = $cp->vars['rows'] . '|' . $cp->vars['columns']; -						} -						else -						{ -							$row_col = explode('|', $var); -							$cp->vars['rows'] = $row_col[0]; -							$cp->vars['columns'] = $row_col[1]; -						} -					} -					else if ($field_type == FIELD_DATE && $key == 'field_default_value') -					{ -						$always_now = request_var('always_now', -1); - -						if ($always_now == 1 || ($always_now === -1 && $var == 'now')) -						{ -							$now = getdate(); - -							$cp->vars['field_default_value_day'] = $now['mday']; -							$cp->vars['field_default_value_month'] = $now['mon']; -							$cp->vars['field_default_value_year'] = $now['year']; -							$var = 'now'; -							$request->overwrite('field_default_value', $var, \phpbb\request\request_interface::POST); -						} -						else -						{ -							if (isset($_REQUEST['field_default_value_day'])) -							{ -								$cp->vars['field_default_value_day'] = request_var('field_default_value_day', 0); -								$cp->vars['field_default_value_month'] = request_var('field_default_value_month', 0); -								$cp->vars['field_default_value_year'] = request_var('field_default_value_year', 0); -								$var = sprintf('%2d-%2d-%4d', $cp->vars['field_default_value_day'], $cp->vars['field_default_value_month'], $cp->vars['field_default_value_year']); -								$request->overwrite('field_default_value', $var, \phpbb\request\request_interface::POST); -							} -							else -							{ -								list($cp->vars['field_default_value_day'], $cp->vars['field_default_value_month'], $cp->vars['field_default_value_year']) = explode('-', $var); -							} -						} -					} -					else if ($field_type == FIELD_BOOL && $key == 'field_default_value') -					{ -						// 'field_length' == 1 defines radio buttons. Possible values are 1 or 2 only. -						// 'field_length' == 2 defines checkbox. Possible values are 0 or 1 only. -						// If we switch the type on step 2, we have to adjust field value. -						// 1 is a common value for the checkbox and radio buttons. - -						// Adjust unchecked checkbox value. -						// If we return or save settings from 2nd/3rd page -						// and the checkbox is unchecked, set the value to 0. -						if (isset($_REQUEST['step']) && !isset($_REQUEST[$key])) -						{ -							$var = 0; -						} - -						// If we switch to the checkbox type but former radio buttons value was 2, -						// which is not the case for the checkbox, set it to 0 (unchecked). -						if ($cp->vars['field_length'] == 2 && $var == 2) -						{ -							$var = 0; -						} -						// If we switch to the radio buttons but the former checkbox value was 0, -						// which is not the case for the radio buttons, set it to 0. -						else if ($cp->vars['field_length'] == 1 && $var == 0) -						{ -							$var = 2; -						} -					} -					else if ($field_type == FIELD_INT && $key == 'field_default_value') -					{ -						// Permit an empty string -						if ($action == 'create' && request_var('field_default_value', '') === '') -						{ -							$var = ''; -						} -					} +					$field_data = $cp->vars; +					$var = $profile_field->get_excluded_options($key, $action, $var, $field_data, 2); +					$cp->vars = $field_data;  					$cp->vars[$key] = $var;  				} @@ -612,18 +503,10 @@ class acp_profile  					{  						$cp->vars[$key] = $$key;  					} -					else if ($key == 'l_lang_options' && $field_type == FIELD_BOOL) -					{ -						$cp->vars[$key] = utf8_normalize_nfc(request_var($key, array(0 => array('')), true)); -					} -					else if ($key == 'l_lang_options' && is_array($cp->vars[$key])) -					{ -						foreach ($cp->vars[$key] as $lang_id => $options) -						{ -							$cp->vars[$key][$lang_id] = explode("\n", $options); -						} -					} +					$field_data = $cp->vars; +					$var = $profile_field->get_excluded_options($key, $action, $var, $field_data, 3); +					$cp->vars = $field_data;  				}  				// Check for general issues in every step @@ -650,15 +533,7 @@ class acp_profile  						$error[] = $user->lang['EMPTY_USER_FIELD_NAME'];  					} -					if ($field_type == FIELD_DROPDOWN && !sizeof($cp->vars['lang_options'])) -					{ -						$error[] = $user->lang['NO_FIELD_ENTRIES']; -					} - -					if ($field_type == FIELD_BOOL && (empty($cp->vars['lang_options'][0]) || empty($cp->vars['lang_options'][1]))) -					{ -						$error[] = $user->lang['NO_FIELD_ENTRIES']; -					} +					$error = $profile_field->validate_options_on_submit($error, $cp->vars);  					// Check for already existing field ident  					if ($action != 'edit') @@ -695,54 +570,16 @@ class acp_profile  					$_new_key_ary = array(); +					$field_data = $cp->vars;  					foreach ($key_ary as $key)  					{ -						if ($field_type == FIELD_TEXT && $key == 'field_length' && isset($_REQUEST['rows'])) -						{ -							$cp->vars['rows'] = request_var('rows', 0); -							$cp->vars['columns'] = request_var('columns', 0); -							$_new_key_ary[$key] = $cp->vars['rows'] . '|' . $cp->vars['columns']; -						} -						else if ($field_type == FIELD_DATE && $key == 'field_default_value') -						{ -							$always_now = request_var('always_now', 0); - -							if ($always_now) -							{ -								$_new_key_ary[$key] = 'now'; -							} -							else if (isset($_REQUEST['field_default_value_day'])) -							{ -								$cp->vars['field_default_value_day'] = request_var('field_default_value_day', 0); -								$cp->vars['field_default_value_month'] = request_var('field_default_value_month', 0); -								$cp->vars['field_default_value_year'] = request_var('field_default_value_year', 0); -								$_new_key_ary[$key]  = sprintf('%2d-%2d-%4d', $cp->vars['field_default_value_day'], $cp->vars['field_default_value_month'], $cp->vars['field_default_value_year']); -							} -						} -						else if ($field_type == FIELD_BOOL && $key == 'l_lang_options' && isset($_REQUEST['l_lang_options'])) -						{ -							$_new_key_ary[$key] = utf8_normalize_nfc(request_var($key, array(array('')), true)); -						} -						else if ($field_type == FIELD_BOOL && $key == 'field_default_value') +						$var = $profile_field->prepare_hidden_fields($step, $key, $action, $field_data); +						if ($var !== null)  						{ -							$_new_key_ary[$key] =  request_var($key, $cp->vars[$key]); -						} -						else -						{ -							if (!isset($_REQUEST[$key])) -							{ -								$var = false; -							} -							else if ($key == 'field_ident' && isset($cp->vars[$key])) -							{ -								$_new_key_ary[$key]= $cp->vars[$key]; -							} -							else -							{ -								$_new_key_ary[$key] = ($field_type == FIELD_BOOL && $key == 'lang_options') ? utf8_normalize_nfc(request_var($key, array(''), true)) : utf8_normalize_nfc(request_var($key, '', true)); -							} +							$_new_key_ary[$key] = $profile_field->prepare_hidden_fields($step, $key, $action, $field_data);  						}  					} +					$cp->vars = $field_data;  					$s_hidden_fields .= build_hidden_fields($_new_key_ary);  				} @@ -776,9 +613,7 @@ class acp_profile  				{  					// Create basic options - only small differences between field types  					case 1: - -						// Build common create options -						$template->assign_vars(array( +						$template_vars = array(  							'S_STEP_ONE'		=> true,  							'S_FIELD_REQUIRED'	=> ($cp->vars['field_required']) ? true : false,  							'S_FIELD_SHOW_NOVALUE'=> ($cp->vars['field_show_novalue']) ? true : false, @@ -790,53 +625,18 @@ class acp_profile  							'S_FIELD_NO_VIEW'	=> ($cp->vars['field_no_view']) ? true : false,  							'L_LANG_SPECIFIC'	=> sprintf($user->lang['LANG_SPECIFIC_OPTIONS'], $config['default_lang']), -							'FIELD_TYPE'		=> $user->lang['FIELD_' . strtoupper($cp->profile_types[$field_type])], +							'FIELD_TYPE'		=> $profile_field->get_name(),  							'FIELD_IDENT'		=> $cp->vars['field_ident'],  							'LANG_NAME'			=> $cp->vars['lang_name'], -							'LANG_EXPLAIN'		=> $cp->vars['lang_explain']) +							'LANG_EXPLAIN'		=> $cp->vars['lang_explain'],  						); -						// String and Text needs to set default values here... -						if ($field_type == FIELD_STRING || $field_type == FIELD_TEXT) -						{ -							$template->assign_vars(array( -								'S_TEXT'		=> ($field_type == FIELD_TEXT) ? true : false, -								'S_STRING'		=> ($field_type == FIELD_STRING) ? true : false, - -								'L_DEFAULT_VALUE_EXPLAIN'	=> $user->lang[strtoupper($cp->profile_types[$field_type]) . '_DEFAULT_VALUE_EXPLAIN'], -								'LANG_DEFAULT_VALUE'		=> $cp->vars['lang_default_value']) -							); -						} - -						if ($field_type == FIELD_BOOL || $field_type == FIELD_DROPDOWN) -						{ -							// Initialize these array elements if we are creating a new field -							if (!sizeof($cp->vars['lang_options'])) -							{ -								if ($field_type == FIELD_BOOL) -								{ -									// No options have been defined for a boolean field. -									$cp->vars['lang_options'][0] = ''; -									$cp->vars['lang_options'][1] = ''; -								} -								else -								{ -									// No options have been defined for the dropdown menu -									$cp->vars['lang_options'] = array(); -								} -							} - -							$template->assign_vars(array( -								'S_BOOL'		=> ($field_type == FIELD_BOOL) ? true : false, -								'S_DROPDOWN'	=> ($field_type == FIELD_DROPDOWN) ? true : false, - -								'L_LANG_OPTIONS_EXPLAIN'	=> $user->lang[strtoupper($cp->profile_types[$field_type]) . '_ENTRIES_EXPLAIN'], -								'LANG_OPTIONS'				=> ($field_type == FIELD_DROPDOWN) ? implode("\n", $cp->vars['lang_options']) : '', -								'FIRST_LANG_OPTION'			=> ($field_type == FIELD_BOOL) ? $cp->vars['lang_options'][0] : '', -								'SECOND_LANG_OPTION'		=> ($field_type == FIELD_BOOL) ? $cp->vars['lang_options'][1] : '') -							); -						} +						$field_data = $cp->vars; +						$profile_field->display_options($template_vars, $field_data); +						$cp->vars = $field_data; +						// Build common create options +						$template->assign_vars($template_vars);  					break;  					case 2: @@ -847,8 +647,7 @@ class acp_profile  						);  						// Build options based on profile type -						$function = 'get_' . $cp->profile_types[$field_type] . '_options'; -						$options = $cp->$function(); +						$options = $profile_field->get_options($this->lang_defs['iso'][$config['default_lang']], $cp->vars);  						foreach ($options as $num => $option_ary)  						{ @@ -910,17 +709,18 @@ class acp_profile  				$s_one_need_edit = true;  			} +			$profile_field = $this->type_collection[$row['field_type']];  			$template->assign_block_vars('fields', array(  				'FIELD_IDENT'		=> $row['field_ident'], -				'FIELD_TYPE'		=> $user->lang['FIELD_' . strtoupper($cp->profile_types[$row['field_type']])], +				'FIELD_TYPE'		=> $profile_field->get_name(),  				'L_ACTIVATE_DEACTIVATE'		=> $user->lang[$active_lang],  				'U_ACTIVATE_DEACTIVATE'		=> $this->u_action . "&action=$active_value&field_id=$id",  				'U_EDIT'					=> $this->u_action . "&action=edit&field_id=$id",  				'U_TRANSLATE'				=> $this->u_action . "&action=edit&field_id=$id&step=3",  				'U_DELETE'					=> $this->u_action . "&action=delete&field_id=$id", -				'U_MOVE_UP'					=> $this->u_action . "&action=move_up&order={$row['field_order']}", -				'U_MOVE_DOWN'				=> $this->u_action . "&action=move_down&order={$row['field_order']}", +				'U_MOVE_UP'					=> $this->u_action . "&action=move_up&field_id=$id", +				'U_MOVE_DOWN'				=> $this->u_action . "&action=move_down&field_id=$id",  				'S_NEED_EDIT'				=> $s_need_edit)  			); @@ -934,15 +734,15 @@ class acp_profile  		}  		$s_select_type = ''; -		foreach ($cp->profile_types as $key => $value) +		foreach ($this->type_collection as $key => $profile_field)  		{ -			$s_select_type .= '<option value="' . $key . '">' . $user->lang['FIELD_' . strtoupper($value)] . '</option>'; +			$s_select_type .= '<option value="' . $key . '">' . $profile_field->get_name() . '</option>';  		}  		$template->assign_vars(array(  			'U_ACTION'			=> $this->u_action, -			'S_TYPE_OPTIONS'	=> $s_select_type) -		); +			'S_TYPE_OPTIONS'	=> $s_select_type, +		));  	}  	/** @@ -950,7 +750,7 @@ class acp_profile  	*/  	function build_language_options(&$cp, $field_type, $action = 'create')  	{ -		global $user, $config, $db; +		global $user, $config, $db, $phpbb_container;  		$default_lang_id = (!empty($this->edit_lang_id)) ? $this->edit_lang_id : $this->lang_defs['iso'][$config['default_lang']]; @@ -967,31 +767,8 @@ class acp_profile  		}  		$db->sql_freeresult($result); -		$options = array(); -		$options['lang_name'] = 'string'; -		if ($cp->vars['lang_explain']) -		{ -			$options['lang_explain'] = 'text'; -		} - -		switch ($field_type) -		{ -			case FIELD_BOOL: -				$options['lang_options'] = 'two_options'; -			break; - -			case FIELD_DROPDOWN: -				$options['lang_options'] = 'optionfield'; -			break; - -			case FIELD_TEXT: -			case FIELD_STRING: -				if (strlen($cp->vars['lang_default_value'])) -				{ -					$options['lang_default_value'] = ($field_type == FIELD_STRING) ? 'string' : 'text'; -				} -			break; -		} +		$profile_field = $this->type_collection[$field_type]; +		$options = $profile_field->get_language_options($cp->vars);  		$lang_options = array(); @@ -1070,7 +847,7 @@ class acp_profile  	*/  	function save_profile_field(&$cp, $field_type, $action = 'create')  	{ -		global $db, $config, $user; +		global $db, $config, $user, $phpbb_container;  		$field_id = request_var('field_id', 0); @@ -1131,10 +908,16 @@ class acp_profile  			$db->sql_query($sql);  		} +		$profile_field = $this->type_collection[$field_type]; +  		if ($action == 'create')  		{  			$field_ident = 'pf_' . $field_ident; -			$profile_sql[] = $this->add_field_ident($field_ident, $field_type); + +			$db_tools = $phpbb_container->get('dbal.tools'); + +			list($sql_type, $null) = $db_tools->get_column_type($profile_field->get_database_column_type()); +			$profile_sql[] = $this->add_field_ident($field_ident, $sql_type);  		}  		$sql_ary = array( @@ -1188,23 +971,7 @@ class acp_profile  			}  		} -		// These are always arrays because the key is the language id... -		$cp->vars['l_lang_name']			= utf8_normalize_nfc(request_var('l_lang_name', array(0 => ''), true)); -		$cp->vars['l_lang_explain']			= utf8_normalize_nfc(request_var('l_lang_explain', array(0 => ''), true)); -		$cp->vars['l_lang_default_value']	= utf8_normalize_nfc(request_var('l_lang_default_value', array(0 => ''), true)); - -		if ($field_type != FIELD_BOOL) -		{ -			$cp->vars['l_lang_options']			= utf8_normalize_nfc(request_var('l_lang_options', array(0 => ''), true)); -		} -		else -		{ -			/** -			* @todo check if this line is correct... -			$cp->vars['l_lang_default_value']	= request_var('l_lang_default_value', array(0 => array('')), true); -			*/ -			$cp->vars['l_lang_options']	= utf8_normalize_nfc(request_var('l_lang_options', array(0 => array('')), true)); -		} +		$cp->vars = $profile_field->get_language_options_input($cp->vars);  		if ($cp->vars['lang_options'])  		{ @@ -1224,7 +991,7 @@ class acp_profile  			foreach ($cp->vars['lang_options'] as $option_id => $value)  			{  				$sql_ary = array( -					'field_type'	=> (int) $field_type, +					'field_type'	=> $field_type,  					'lang_value'	=> $value  				); @@ -1279,7 +1046,7 @@ class acp_profile  							'field_id'		=> (int) $field_id,  							'lang_id'		=> (int) $lang_id,  							'option_id'		=> (int) $option_id, -							'field_type'	=> (int) $field_type, +							'field_type'	=> $field_type,  							'lang_value'	=> $value  						);  					} @@ -1409,7 +1176,7 @@ class acp_profile  	/**  	* Return sql statement for adding a new field ident (profile field) to the profile fields data table  	*/ -	function add_field_ident($field_ident, $field_type) +	function add_field_ident($field_ident, $sql_type)  	{  		global $db; @@ -1418,73 +1185,11 @@ class acp_profile  			case 'mysql':  			case 'mysql4':  			case 'mysqli': - -				// We are defining the biggest common value, because of the possibility to edit the min/max values of each field. -				$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . " ADD `$field_ident` "; - -				switch ($field_type) -				{ -					case FIELD_STRING: -						$sql .= ' VARCHAR(255) '; -					break; - -					case FIELD_DATE: -						$sql .= 'VARCHAR(10) '; -					break; - -					case FIELD_TEXT: -						$sql .= "TEXT"; -		//						ADD {$field_ident}_bbcode_uid VARCHAR(5) NOT NULL, -		//						ADD {$field_ident}_bbcode_bitfield INT(11) UNSIGNED"; -					break; - -					case FIELD_BOOL: -						$sql .= 'TINYINT(2) '; -					break; - -					case FIELD_DROPDOWN: -						$sql .= 'MEDIUMINT(8) '; -					break; - -					case FIELD_INT: -						$sql .= 'BIGINT(20) '; -					break; -				} +				$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . " ADD `$field_ident` " . $sql_type;  			break;  			case 'sqlite': - -				switch ($field_type) -				{ -					case FIELD_STRING: -						$type = ' VARCHAR(255) '; -					break; - -					case FIELD_DATE: -						$type = 'VARCHAR(10) '; -					break; - -					case FIELD_TEXT: -						$type = "TEXT(65535)"; -		//						ADD {$field_ident}_bbcode_uid VARCHAR(5) NOT NULL, -		//						ADD {$field_ident}_bbcode_bitfield INT(11) UNSIGNED"; -					break; - -					case FIELD_BOOL: -						$type = 'TINYINT(2) '; -					break; - -					case FIELD_DROPDOWN: -						$type = 'MEDIUMINT(8) '; -					break; - -					case FIELD_INT: -						$type = 'BIGINT(20) '; -					break; -				} - -				// We are defining the biggest common value, because of the possibility to edit the min/max values of each field.  				if (version_compare(sqlite_libversion(), '3.0') == -1)  				{  					$sql = "SELECT sql @@ -1519,7 +1224,7 @@ class acp_profile  					$columns = implode(',', $column_list); -					$new_table_cols = $field_ident . ' ' . $type . ',' . $new_table_cols; +					$new_table_cols = $field_ident . ' ' . $sql_type . ',' . $new_table_cols;  					// create a new table and fill it up. destroy the temp one  					$db->sql_query('CREATE TABLE ' . PROFILE_FIELDS_DATA_TABLE . ' (' . $new_table_cols . ');'); @@ -1528,7 +1233,7 @@ class acp_profile  				}  				else  				{ -					$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . " ADD $field_ident [$type]"; +					$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . " ADD $field_ident [$sql_type]";  				}  			break; @@ -1536,140 +1241,22 @@ class acp_profile  			case 'mssql':  			case 'mssql_odbc':  			case 'mssqlnative': - -				// We are defining the biggest common value, because of the possibility to edit the min/max values of each field. -				$sql = 'ALTER TABLE [' . PROFILE_FIELDS_DATA_TABLE . "] ADD [$field_ident] "; - -				switch ($field_type) -				{ -					case FIELD_STRING: -						$sql .= ' [VARCHAR] (255) '; -					break; - -					case FIELD_DATE: -						$sql .= '[VARCHAR] (10) '; -					break; - -					case FIELD_TEXT: -						$sql .= "[TEXT]"; -		//						ADD {$field_ident}_bbcode_uid [VARCHAR] (5) NOT NULL, -		//						ADD {$field_ident}_bbcode_bitfield [INT] UNSIGNED"; -					break; - -					case FIELD_BOOL: -					case FIELD_DROPDOWN: -						$sql .= '[INT] '; -					break; - -					case FIELD_INT: -						$sql .= '[FLOAT] '; -					break; -				} +				$sql = 'ALTER TABLE [' . PROFILE_FIELDS_DATA_TABLE . "] ADD [$field_ident] " . $sql_type;  			break;  			case 'postgres': - -				// We are defining the biggest common value, because of the possibility to edit the min/max values of each field. -				$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . " ADD COLUMN \"$field_ident\" "; - -				switch ($field_type) -				{ -					case FIELD_STRING: -						$sql .= ' VARCHAR(255) '; -					break; - -					case FIELD_DATE: -						$sql .= 'VARCHAR(10) '; -					break; - -					case FIELD_TEXT: -						$sql .= "TEXT"; -		//						ADD {$field_ident}_bbcode_uid VARCHAR(5) NOT NULL, -		//						ADD {$field_ident}_bbcode_bitfield INT4 UNSIGNED"; -					break; - -					case FIELD_BOOL: -						$sql .= 'INT2 '; -					break; - -					case FIELD_DROPDOWN: -						$sql .= 'INT4 '; -					break; - -					case FIELD_INT: -						$sql .= 'INT8 '; -					break; -				} +				$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . " ADD COLUMN \"$field_ident\" " . $sql_type;  			break;  			case 'firebird': - -				// We are defining the biggest common value, because of the possibility to edit the min/max values of each field. -				$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . ' ADD "' . strtoupper($field_ident) . '" '; - -				switch ($field_type) -				{ -					case FIELD_STRING: -						$sql .= ' VARCHAR(255) '; -					break; - -					case FIELD_DATE: -						$sql .= 'VARCHAR(10) '; -					break; - -					case FIELD_TEXT: -						$sql .= "BLOB SUB_TYPE TEXT"; -		//						ADD {$field_ident}_bbcode_uid VARCHAR(5) NOT NULL, -		//						ADD {$field_ident}_bbcode_bitfield INTEGER UNSIGNED"; -					break; - -					case FIELD_BOOL: -					case FIELD_DROPDOWN: -						$sql .= 'INTEGER '; -					break; - -					case FIELD_INT: -						$sql .= 'DOUBLE PRECISION '; -					break; -				} +				$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . ' ADD "' . strtoupper($field_ident) . '" ' . $sql_type;  			break;  			case 'oracle': - -				// We are defining the biggest common value, because of the possibility to edit the min/max values of each field. -				$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . " ADD $field_ident "; - -				switch ($field_type) -				{ -					case FIELD_STRING: -						$sql .= ' VARCHAR2(255) '; -					break; - -					case FIELD_DATE: -						$sql .= 'VARCHAR2(10) '; -					break; - -					case FIELD_TEXT: -						$sql .= "CLOB"; -		//						ADD {$field_ident}_bbcode_uid VARCHAR2(5) NOT NULL, -		//						ADD {$field_ident}_bbcode_bitfield NUMBER(11) UNSIGNED"; -					break; - -					case FIELD_BOOL: -						$sql .= 'NUMBER(2) '; -					break; - -					case FIELD_DROPDOWN: -						$sql .= 'NUMBER(8) '; -					break; - -					case FIELD_INT: -						$sql .= 'NUMBER(20) '; -					break; -				} +				$sql = 'ALTER TABLE ' . PROFILE_FIELDS_DATA_TABLE . " ADD $field_ident " . $sql_type;  			break;  		} diff --git a/phpBB/includes/acp/acp_reasons.php b/phpBB/includes/acp/acp_reasons.php index 71e9108c2c..569bb73ab0 100644 --- a/phpBB/includes/acp/acp_reasons.php +++ b/phpBB/includes/acp/acp_reasons.php @@ -26,6 +26,7 @@ class acp_reasons  	{  		global $db, $user, $auth, $template, $cache;  		global $config, $phpbb_root_path, $phpbb_admin_path, $phpEx; +		global $request;  		$user->add_lang(array('mcp', 'acp/posting')); @@ -280,7 +281,18 @@ class acp_reasons  			case 'move_up':  			case 'move_down': -				$order = request_var('order', 0); +				$sql = 'SELECT reason_order +					FROM ' . REPORTS_REASONS_TABLE . " +					WHERE reason_id = $reason_id"; +				$result = $db->sql_query($sql); +				$order = $db->sql_fetchfield('reason_order'); +				$db->sql_freeresult($result); + +				if ($order === false || ($order == 0 && $action == 'move_up')) +				{ +					break; +				} +				$order = (int) $order;  				$order_total = $order * 2 + (($action == 'move_up') ? -1 : 1);  				$sql = 'UPDATE ' . REPORTS_REASONS_TABLE . ' @@ -288,6 +300,13 @@ class acp_reasons  					WHERE reason_order IN (' . $order . ', ' . (($action == 'move_up') ? $order - 1 : $order + 1) . ')';  				$db->sql_query($sql); +				if ($request->is_ajax()) +				{ +					$json_response = new \phpbb\json_response; +					$json_response->send(array( +						'success'	=> (bool) $db->sql_affectedrows(), +					)); +				}  			break;  		} @@ -363,8 +382,8 @@ class acp_reasons  				'U_EDIT'		=> $this->u_action . '&action=edit&id=' . $row['reason_id'],  				'U_DELETE'		=> (!$other_reason) ? $this->u_action . '&action=delete&id=' . $row['reason_id'] : '', -				'U_MOVE_UP'		=> $this->u_action . '&action=move_up&order=' . $row['reason_order'], -				'U_MOVE_DOWN'	=> $this->u_action . '&action=move_down&order=' . $row['reason_order']) +				'U_MOVE_UP'		=> $this->u_action . '&action=move_up&id=' . $row['reason_id'], +				'U_MOVE_DOWN'	=> $this->u_action . '&action=move_down&id=' . $row['reason_id'])  			);  		}  		$db->sql_freeresult($result); diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index b24adfc586..bf11e4f8fe 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -824,9 +824,12 @@ class acp_users  						$error[] = 'FORM_INVALID';  					} +					// Instantiate passwords manager +					$passwords_manager = $phpbb_container->get('passwords.manager'); +  					// Which updates do we need to do?  					$update_username = ($user_row['username'] != $data['username']) ? $data['username'] : false; -					$update_password = ($data['new_password'] && !phpbb_check_hash($data['new_password'], $user_row['user_password'])) ? true : false; +					$update_password = $data['new_password'] && !$passwords_manager->check($data['new_password'], $user_row['user_password']);  					$update_email = ($data['email'] != $user_row['user_email']) ? $data['email'] : false;  					if (!sizeof($error)) @@ -910,7 +913,7 @@ class acp_users  						if ($update_password)  						{  							$sql_ary += array( -								'user_password'		=> phpbb_hash($data['new_password']), +								'user_password'		=> $passwords_manager->hash($data['new_password']),  								'user_passchg'		=> time(),  								'user_pass_convert'	=> 0,  							); @@ -1343,9 +1346,8 @@ class acp_users  			case 'profile':  				include($phpbb_root_path . 'includes/functions_user.' . $phpEx); -				include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); -				$cp = new custom_profile(); +				$cp = $phpbb_container->get('profilefields.manager');  				$cp_data = $cp_error = array(); diff --git a/phpBB/includes/db/schema_data.php b/phpBB/includes/db/schema_data.php index 69d39e0f8c..2c1070f459 100644 --- a/phpBB/includes/db/schema_data.php +++ b/phpBB/includes/db/schema_data.php @@ -318,7 +318,7 @@ $schema_data['phpbb_forums'] = array(  		'forum_desc_options'	=> array('UINT:11', 7),  		'forum_desc_uid'		=> array('VCHAR:8', ''),  		'forum_link'			=> array('VCHAR_UNI', ''), -		'forum_password'		=> array('VCHAR_UNI:40', ''), +		'forum_password'		=> array('VCHAR_UNI', ''),  		'forum_style'			=> array('UINT', 0),  		'forum_image'			=> array('VCHAR', ''),  		'forum_rules'			=> array('TEXT_UNI', ''), @@ -758,7 +758,7 @@ $schema_data['phpbb_profile_fields'] = array(  	'COLUMNS'		=> array(  		'field_id'				=> array('UINT', NULL, 'auto_increment'),  		'field_name'			=> array('VCHAR_UNI', ''), -		'field_type'			=> array('TINT:4', 0), +		'field_type'			=> array('VCHAR:100', ''),  		'field_ident'			=> array('VCHAR:20', ''),  		'field_length'			=> array('VCHAR:20', ''),  		'field_minlen'			=> array('VCHAR', ''), @@ -796,7 +796,7 @@ $schema_data['phpbb_profile_fields_lang'] = array(  		'field_id'				=> array('UINT', 0),  		'lang_id'				=> array('UINT', 0),  		'option_id'				=> array('UINT', 0), -		'field_type'			=> array('TINT:4', 0), +		'field_type'			=> array('VCHAR:100', ''),  		'lang_value'			=> array('VCHAR_UNI', ''),  	),  	'PRIMARY_KEY'	=> array('field_id', 'lang_id', 'option_id'), @@ -1112,7 +1112,7 @@ $schema_data['phpbb_users'] = array(  		'user_regdate'				=> array('TIMESTAMP', 0),  		'username'					=> array('VCHAR_CI', ''),  		'username_clean'			=> array('VCHAR_CI', ''), -		'user_password'				=> array('VCHAR_UNI:40', ''), +		'user_password'				=> array('VCHAR_UNI', ''),  		'user_passchg'				=> array('TIMESTAMP', 0),  		'user_pass_convert'			=> array('BOOL', 0),  		'user_email'				=> array('VCHAR_UNI:100', ''), diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 0d6c7be117..689a682de3 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -368,73 +368,27 @@ function still_on_time($extra_time = 15)  }  /** +* Hash the password  * -* @version Version 0.1 / slightly modified for phpBB 3.1.x (using $H$ as hash type identifier) -* -* Portable PHP password hashing framework. -* -* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in -* the public domain. -* -* There's absolutely no warranty. -* -* The homepage URL for this framework is: -* -*	http://www.openwall.com/phpass/ -* -* Please be sure to update the Version line if you edit this file in any way. -* It is suggested that you leave the main version number intact, but indicate -* your project name (after the slash) and add your own revision information. -* -* Please do not change the "private" password hashing method implemented in -* here, thereby making your hashes incompatible.  However, if you must, please -* change the hash type identifier (the "$P$") to something different. -* -* Obviously, since this code is in the public domain, the above are not -* requirements (there can be none), but merely suggestions. +* @deprecated 3.1.0-a2 (To be removed: 3.3.0)  * +* @param string $password Password to be hashed  * -* Hash the password +* @return string|bool Password hash or false if something went wrong during hashing  */  function phpbb_hash($password)  { -	$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; - -	$random_state = unique_id(); -	$random = ''; -	$count = 6; - -	if (($fh = @fopen('/dev/urandom', 'rb'))) -	{ -		$random = fread($fh, $count); -		fclose($fh); -	} - -	if (strlen($random) < $count) -	{ -		$random = ''; +	global $phpbb_container; -		for ($i = 0; $i < $count; $i += 16) -		{ -			$random_state = md5(unique_id() . $random_state); -			$random .= pack('H*', md5($random_state)); -		} -		$random = substr($random, 0, $count); -	} - -	$hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64); - -	if (strlen($hash) == 34) -	{ -		return $hash; -	} - -	return md5($password); +	$passwords_manager = $phpbb_container->get('passwords.manager'); +	return $passwords_manager->hash($password);  }  /**  * Check for correct password  * +* @deprecated 3.1.0-a2 (To be removed: 3.3.0) +*  * @param string $password The password in plain text  * @param string $hash The stored password hash  * @@ -442,130 +396,10 @@ function phpbb_hash($password)  */  function phpbb_check_hash($password, $hash)  { -	if (strlen($password) > 4096) -	{ -		// If the password is too huge, we will simply reject it -		// and not let the server try to hash it. -		return false; -	} - -	$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; -	if (strlen($hash) == 34) -	{ -		return (_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false; -	} - -	return (md5($password) === $hash) ? true : false; -} - -/** -* Generate salt for hash generation -*/ -function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6) -{ -	if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) -	{ -		$iteration_count_log2 = 8; -	} - -	$output = '$H$'; -	$output .= $itoa64[min($iteration_count_log2 + 5, 30)]; -	$output .= _hash_encode64($input, 6, $itoa64); - -	return $output; -} - -/** -* Encode hash -*/ -function _hash_encode64($input, $count, &$itoa64) -{ -	$output = ''; -	$i = 0; - -	do -	{ -		$value = ord($input[$i++]); -		$output .= $itoa64[$value & 0x3f]; - -		if ($i < $count) -		{ -			$value |= ord($input[$i]) << 8; -		} - -		$output .= $itoa64[($value >> 6) & 0x3f]; - -		if ($i++ >= $count) -		{ -			break; -		} - -		if ($i < $count) -		{ -			$value |= ord($input[$i]) << 16; -		} - -		$output .= $itoa64[($value >> 12) & 0x3f]; - -		if ($i++ >= $count) -		{ -			break; -		} - -		$output .= $itoa64[($value >> 18) & 0x3f]; -	} -	while ($i < $count); - -	return $output; -} - -/** -* The crypt function/replacement -*/ -function _hash_crypt_private($password, $setting, &$itoa64) -{ -	$output = '*'; - -	// Check for correct hash -	if (substr($setting, 0, 3) != '$H$' && substr($setting, 0, 3) != '$P$') -	{ -		return $output; -	} - -	$count_log2 = strpos($itoa64, $setting[3]); +	global $phpbb_container; -	if ($count_log2 < 7 || $count_log2 > 30) -	{ -		return $output; -	} - -	$count = 1 << $count_log2; -	$salt = substr($setting, 4, 8); - -	if (strlen($salt) != 8) -	{ -		return $output; -	} - -	/** -	* We're kind of forced to use MD5 here since it's the only -	* cryptographic primitive available in all versions of PHP -	* currently in use.  To implement our own low-level crypto -	* in PHP would result in much worse performance and -	* consequently in lower iteration counts and hashes that are -	* quicker to crack (by non-PHP code). -	*/ -	$hash = md5($salt . $password, true); -	do -	{ -		$hash = md5($hash . $password, true); -	} -	while (--$count); - -	$output = substr($setting, 0, 12); -	$output .= _hash_encode64($hash, 16, $itoa64); - -	return $output; +	$passwords_manager = $phpbb_container->get('passwords.manager'); +	return $passwords_manager->check($password, $hash);  }  /** @@ -2434,7 +2268,7 @@ function generate_board_url($without_script_path = false)  */  function redirect($url, $return = false, $disable_cd_check = false)  { -	global $db, $cache, $config, $user, $phpbb_root_path; +	global $db, $cache, $config, $user, $phpbb_root_path, $phpbb_filesystem, $phpbb_path_helper, $phpEx;  	$failover_flag = false; @@ -2477,78 +2311,34 @@ function redirect($url, $return = false, $disable_cd_check = false)  		// Relative uri  		$pathinfo = pathinfo($url); -		if (!$disable_cd_check && !file_exists($pathinfo['dirname'] . '/')) +		// Is the uri pointing to the current directory? +		if ($pathinfo['dirname'] == '.')  		{ -			$url = str_replace('../', '', $url); -			$pathinfo = pathinfo($url); +			$url = str_replace('./', '', $url); -			if (!file_exists($pathinfo['dirname'] . '/')) +			// Strip / from the beginning +			if ($url && substr($url, 0, 1) == '/')  			{ -				// fallback to "last known user page" -				// at least this way we know the user does not leave the phpBB root -				$url = generate_board_url() . '/' . $user->page['page']; -				$failover_flag = true; +				$url = substr($url, 1);  			}  		} -		if (!$failover_flag) -		{ -			// Is the uri pointing to the current directory? -			if ($pathinfo['dirname'] == '.') -			{ -				$url = str_replace('./', '', $url); - -				// Strip / from the beginning -				if ($url && substr($url, 0, 1) == '/') -				{ -					$url = substr($url, 1); -				} - -				if ($user->page['page_dir']) -				{ -					$url = generate_board_url() . '/' . $user->page['page_dir'] . '/' . $url; -				} -				else -				{ -					$url = generate_board_url() . '/' . $url; -				} -			} -			else -			{ -				// Used ./ before, but $phpbb_root_path is working better with urls within another root path -				$root_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($phpbb_root_path))); -				$page_dirs = explode('/', str_replace('\\', '/', phpbb_realpath($pathinfo['dirname']))); -				$intersection = array_intersect_assoc($root_dirs, $page_dirs); - -				$root_dirs = array_diff_assoc($root_dirs, $intersection); -				$page_dirs = array_diff_assoc($page_dirs, $intersection); +		$url = $phpbb_path_helper->remove_web_root_path($url); -				$dir = str_repeat('../', sizeof($root_dirs)) . implode('/', $page_dirs); - -				// Strip / from the end -				if ($dir && substr($dir, -1, 1) == '/') -				{ -					$dir = substr($dir, 0, -1); -				} +		if ($user->page['page_dir']) +		{ +			$url = $user->page['page_dir'] . '/' . $url; +		} -				// Strip / from the beginning -				if ($dir && substr($dir, 0, 1) == '/') -				{ -					$dir = substr($dir, 1); -				} +		$url = generate_board_url() . '/' . $url; +	} -				$url = str_replace($pathinfo['dirname'] . '/', '', $url); +	// Clean URL and check if we go outside the forum directory +	$url = $phpbb_path_helper->clean_url($url); -				// Strip / from the beginning -				if (substr($url, 0, 1) == '/') -				{ -					$url = substr($url, 1); -				} - -				$url = (!empty($dir) ? $dir . '/' : '') . $url; -				$url = generate_board_url() . '/' . $url; -			} -		} +	if (!$disable_cd_check && strpos($url, generate_board_url(true)) === false) +	{ +		trigger_error('INSECURE_REDIRECT', E_USER_ERROR);  	}  	// Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2 @@ -3250,9 +3040,9 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa  */  function login_forum_box($forum_data)  { -	global $db, $config, $user, $template, $phpEx; +	global $db, $phpbb_container, $request, $template, $user; -	$password = request_var('password', '', true); +	$password = $request->variable('password', '', true);  	$sql = 'SELECT forum_id  		FROM ' . FORUMS_ACCESS_TABLE . ' @@ -3293,7 +3083,9 @@ function login_forum_box($forum_data)  		}  		$db->sql_freeresult($result); -		if (phpbb_check_hash($password, $forum_data['forum_password'])) +		$passwords_manager = $phpbb_container->get('passwords.manager'); + +		if ($passwords_manager->check($password, $forum_data['forum_password']))  		{  			$sql_ary = array(  				'forum_id'		=> (int) $forum_data['forum_id'], @@ -5099,7 +4891,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  	}  	$hidden_fields_for_jumpbox = phpbb_build_hidden_fields_for_query_params($request, array('f')); - +	$notification_mark_hash = generate_link_hash('mark_all_notifications_read');  	// The following assigns all _common_ variables that may be used at any point in a template.  	$template->assign_vars(array( @@ -5119,6 +4911,7 @@ function page_header($page_title = '', $display_online_list = true, $item_id = 0  		'UNREAD_NOTIFICATIONS_COUNT'	=> ($notifications !== false) ? $notifications['unread_count'] : '',  		'NOTIFICATIONS_COUNT'			=> ($notifications !== false) ? $notifications['unread_count'] : '',  		'U_VIEW_ALL_NOTIFICATIONS'		=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'), +		'U_MARK_ALL_NOTIFICATIONS'		=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_list&mark=all&token=' . $notification_mark_hash),  		'U_NOTIFICATION_SETTINGS'		=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_options'),  		'S_NOTIFICATIONS_DISPLAY'		=> $config['load_notifications'], diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index e1259eba12..53055752f6 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -334,7 +334,7 @@ class p_master  	static function module_auth($module_auth, $forum_id)  	{  		global $auth, $config; -		global $request; +		global $request, $phpbb_extension_manager, $phpbb_dispatcher;  		$module_auth = trim($module_auth); @@ -351,6 +351,30 @@ class p_master  			[(),]                                  |  			[^\s(),]+)/x', $module_auth, $match); +		// Valid tokens for auth and their replacements +		$valid_tokens = array( +			'acl_([a-z0-9_]+)(,\$id)?'		=> '(int) $auth->acl_get(\'\\1\'\\2)', +			'\$id'							=> '(int) $forum_id', +			'aclf_([a-z0-9_]+)'				=> '(int) $auth->acl_getf_global(\'\\1\')', +			'cfg_([a-z0-9_]+)'				=> '(int) $config[\'\\1\']', +			'request_([a-zA-Z0-9_]+)'		=> '$request->variable(\'\\1\', false)', +			'ext_([a-zA-Z0-9_/]+)'			=> 'array_key_exists(\'\\1\', $phpbb_extension_manager->all_enabled())', +		); + +		/** +		* Alter tokens for module authorisation check +		* +		* @event core.module_auth +		* @var	array	valid_tokens		Valid tokens and their auth check +		*									replacements +		* @var	string	module_auth			The module_auth of the current +		* 									module +		* @var	int		forum_id			The current forum_id +		* @since 3.1-A3 +		*/ +		$vars = array('valid_tokens', 'module_auth', 'forum_id'); +		extract($phpbb_dispatcher->trigger_event('core.module_auth', compact($vars))); +  		$tokens = $match[0];  		for ($i = 0, $size = sizeof($tokens); $i < $size; $i++)  		{ @@ -366,7 +390,7 @@ class p_master  				break;  				default: -					if (!preg_match('#(?:acl_([a-z0-9_]+)(,\$id)?)|(?:\$id)|(?:aclf_([a-z0-9_]+))|(?:cfg_([a-z0-9_]+))|(?:request_([a-zA-Z0-9_]+))#', $token)) +					if (!preg_match('#(?:' . implode(array_keys($valid_tokens), ')|(?:') . ')#', $token))  					{  						$token = '';  					} @@ -379,8 +403,17 @@ class p_master  		// Make sure $id separation is working fine  		$module_auth = str_replace(' , ', ',', $module_auth); +		$module_auth = preg_replace( +			// Array keys with # prepended/appended +			array_map(function($value) { +				return '#' . $value . '#'; +			}, array_keys($valid_tokens)), +			array_values($valid_tokens), +			$module_auth +		); +  		$is_auth = false; -		eval('$is_auth = (int) (' . preg_replace(array('#acl_([a-z0-9_]+)(,\$id)?#', '#\$id#', '#aclf_([a-z0-9_]+)#', '#cfg_([a-z0-9_]+)#', '#request_([a-zA-Z0-9_]+)#'), array('(int) $auth->acl_get(\'\\1\'\\2)', '(int) $forum_id', '(int) $auth->acl_getf_global(\'\\1\')', '(int) $config[\'\\1\']', '$request->variable(\'\\1\', false)'), $module_auth) . ');'); +		eval('$is_auth = (int) (' .	$module_auth . ');');  		return $is_auth;  	} diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 172f4403ac..e14e9e27be 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -862,6 +862,7 @@ function posting_gen_attachment_entry($attachment_data, &$filename_data, $show_a  				'ATTACH_ID'			=> $attach_row['attach_id'],  				'S_IS_ORPHAN'		=> $attach_row['is_orphan'],  				'ASSOC_INDEX'		=> $count, +				'FILESIZE'			=> get_formatted_filesize($attach_row['filesize']),  				'U_VIEW_ATTACHMENT'	=> $download_link,  				'S_HIDDEN'			=> $hidden) diff --git a/phpBB/includes/functions_profile_fields.php b/phpBB/includes/functions_profile_fields.php deleted file mode 100644 index 7dd0b0e87d..0000000000 --- a/phpBB/includes/functions_profile_fields.php +++ /dev/null @@ -1,1187 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ -	exit; -} - -/** -* Custom Profile Fields -* @package phpBB3 -*/ -class custom_profile -{ -	var $profile_types = array(FIELD_INT => 'int', FIELD_STRING => 'string', FIELD_TEXT => 'text', FIELD_BOOL => 'bool', FIELD_DROPDOWN => 'dropdown', FIELD_DATE => 'date'); -	var $profile_cache = array(); -	var $options_lang = array(); - -	/** -	* Assign editable fields to template, mode can be profile (for profile change) or register (for registration) -	* Called by ucp_profile and ucp_register -	* @access public -	*/ -	function generate_profile_fields($mode, $lang_id) -	{ -		global $db, $template, $auth; - -		$sql_where = ''; -		switch ($mode) -		{ -			case 'register': -				// If the field is required we show it on the registration page -				$sql_where .= ' AND f.field_show_on_reg = 1'; -			break; - -			case 'profile': -				// Show hidden fields to moderators/admins -				if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) -				{ -					$sql_where .= ' AND f.field_show_profile = 1'; -				} -			break; - -			default: -				trigger_error('Wrong profile mode specified', E_USER_ERROR); -			break; -		} - -		$sql = 'SELECT l.*, f.* -			FROM ' . PROFILE_LANG_TABLE . ' l, ' . PROFILE_FIELDS_TABLE . " f -			WHERE f.field_active = 1 -				$sql_where -				AND l.lang_id = $lang_id -				AND l.field_id = f.field_id -			ORDER BY f.field_order"; -		$result = $db->sql_query($sql); - -		while ($row = $db->sql_fetchrow($result)) -		{ -			// Return templated field -			$tpl_snippet = $this->process_field_row('change', $row); - -			// Some types are multivalue, we can't give them a field_id as we would not know which to pick -			$type = (int) $row['field_type']; - -			$template->assign_block_vars('profile_fields', array( -				'LANG_NAME'		=> $row['lang_name'], -				'LANG_EXPLAIN'	=> $row['lang_explain'], -				'FIELD'			=> $tpl_snippet, -				'FIELD_ID'		=> ($type == FIELD_DATE || ($type == FIELD_BOOL && $row['field_length'] == '1')) ? '' : 'pf_' . $row['field_ident'], -				'S_REQUIRED'	=> ($row['field_required']) ? true : false) -			); -		} -		$db->sql_freeresult($result); -	} - -	/** -	* Validate entered profile field data -	* @access public -	*/ -	function validate_profile_field($field_type, &$field_value, $field_data) -	{ -		switch ($field_type) -		{ -			case FIELD_DATE: -				$field_validate = explode('-', $field_value); - -				$day = (isset($field_validate[0])) ? (int) $field_validate[0] : 0; -				$month = (isset($field_validate[1])) ? (int) $field_validate[1] : 0; -				$year = (isset($field_validate[2])) ? (int) $field_validate[2] : 0; - -				if ((!$day || !$month || !$year) && !$field_data['field_required']) -				{ -					return false; -				} - -				if ((!$day || !$month || !$year) && $field_data['field_required']) -				{ -					return 'FIELD_REQUIRED'; -				} - -				if ($day < 0 || $day > 31 || $month < 0 || $month > 12 || ($year < 1901 && $year > 0) || $year > gmdate('Y', time()) + 50) -				{ -					return 'FIELD_INVALID_DATE'; -				} - -				if (checkdate($month, $day, $year) === false) -				{ -					return 'FIELD_INVALID_DATE'; -				} -			break; - -			case FIELD_BOOL: -				$field_value = (bool) $field_value; - -				if (!$field_value && $field_data['field_required']) -				{ -					return 'FIELD_REQUIRED'; -				} -			break; - -			case FIELD_INT: -				if (trim($field_value) === '' && !$field_data['field_required']) -				{ -					return false; -				} - -				$field_value = (int) $field_value; - -				if ($field_value < $field_data['field_minlen']) -				{ -					return 'FIELD_TOO_SMALL'; -				} -				else if ($field_value > $field_data['field_maxlen']) -				{ -					return 'FIELD_TOO_LARGE'; -				} -			break; - -			case FIELD_DROPDOWN: -				$field_value = (int) $field_value; - -				// retrieve option lang data if necessary -				if (!isset($this->options_lang[$field_data['field_id']]) || !isset($this->options_lang[$field_data['field_id']][$field_data['lang_id']]) || !sizeof($this->options_lang[$file_data['field_id']][$field_data['lang_id']])) -				{ -					$this->get_option_lang($field_data['field_id'], $field_data['lang_id'], FIELD_DROPDOWN, false); -				} - -				if (!isset($this->options_lang[$field_data['field_id']][$field_data['lang_id']][$field_value])) -				{ -					return 'FIELD_INVALID_VALUE'; -				} - -				if ($field_value == $field_data['field_novalue'] && $field_data['field_required']) -				{ -					return 'FIELD_REQUIRED'; -				} -			break; - -			case FIELD_STRING: -			case FIELD_TEXT: -				if (trim($field_value) === '' && !$field_data['field_required']) -				{ -					return false; -				} -				else if (trim($field_value) === '' && $field_data['field_required']) -				{ -					return 'FIELD_REQUIRED'; -				} - -				if ($field_data['field_minlen'] && utf8_strlen($field_value) < $field_data['field_minlen']) -				{ -					return 'FIELD_TOO_SHORT'; -				} -				else if ($field_data['field_maxlen'] && utf8_strlen($field_value) > $field_data['field_maxlen']) -				{ -					return 'FIELD_TOO_LONG'; -				} - -				if (!empty($field_data['field_validation']) && $field_data['field_validation'] != '.*') -				{ -					$field_validate = ($field_type == FIELD_STRING) ? $field_value : bbcode_nl2br($field_value); -					if (!preg_match('#^' . str_replace('\\\\', '\\', $field_data['field_validation']) . '$#i', $field_validate)) -					{ -						return 'FIELD_INVALID_CHARS'; -					} -				} -			break; -		} - -		return false; -	} - -	/** -	* Build profile cache, used for display -	* @access private -	*/ -	function build_cache() -	{ -		global $db, $user, $auth; - -		$this->profile_cache = array(); - -		// Display hidden/no_view fields for admin/moderator -		$sql = 'SELECT l.*, f.* -			FROM ' . PROFILE_LANG_TABLE . ' l, ' . PROFILE_FIELDS_TABLE . ' f -			WHERE l.lang_id = ' . $user->get_iso_lang_id() . ' -				AND f.field_active = 1 ' . -				((!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) ? '	AND f.field_hide = 0 ' : '') . ' -				AND f.field_no_view = 0 -				AND l.field_id = f.field_id -			ORDER BY f.field_order'; -		$result = $db->sql_query($sql); - -		while ($row = $db->sql_fetchrow($result)) -		{ -			$this->profile_cache[$row['field_ident']] = $row; -		} -		$db->sql_freeresult($result); -	} - -	/** -	* Get language entries for options and store them here for later use -	*/ -	function get_option_lang($field_id, $lang_id, $field_type, $preview) -	{ -		global $db; - -		if ($preview) -		{ -			$lang_options = (!is_array($this->vars['lang_options'])) ? explode("\n", $this->vars['lang_options']) : $this->vars['lang_options']; - -			foreach ($lang_options as $num => $var) -			{ -				$this->options_lang[$field_id][$lang_id][($num + 1)] = $var; -			} -		} -		else -		{ -			$sql = 'SELECT option_id, lang_value -				FROM ' . PROFILE_FIELDS_LANG_TABLE . " -					WHERE field_id = $field_id -					AND lang_id = $lang_id -					AND field_type = $field_type -				ORDER BY option_id"; -			$result = $db->sql_query($sql); - -			while ($row = $db->sql_fetchrow($result)) -			{ -				$this->options_lang[$field_id][$lang_id][($row['option_id'] + 1)] = $row['lang_value']; -			} -			$db->sql_freeresult($result); -		} -	} - -	/** -	* Submit profile field for validation -	* @access public -	*/ -	function submit_cp_field($mode, $lang_id, &$cp_data, &$cp_error) -	{ -		global $auth, $db, $user; - -		$sql_where = ''; -		switch ($mode) -		{ -			case 'register': -				// If the field is required we show it on the registration page -				$sql_where .= ' AND f.field_show_on_reg = 1'; -			break; - -			case 'profile': -				// Show hidden fields to moderators/admins -				if (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) -				{ -					$sql_where .= ' AND f.field_show_profile = 1'; -				} -			break; - -			default: -				trigger_error('Wrong profile mode specified', E_USER_ERROR); -			break; -		} - -		$sql = 'SELECT l.*, f.* -			FROM ' . PROFILE_LANG_TABLE . ' l, ' . PROFILE_FIELDS_TABLE . " f -			WHERE l.lang_id = $lang_id -				AND f.field_active = 1 -				$sql_where -				AND l.field_id = f.field_id -			ORDER BY f.field_order"; -		$result = $db->sql_query($sql); - -		while ($row = $db->sql_fetchrow($result)) -		{ -			$cp_data['pf_' . $row['field_ident']] = $this->get_profile_field($row); -			$check_value = $cp_data['pf_' . $row['field_ident']]; - -			if (($cp_result = $this->validate_profile_field($row['field_type'], $check_value, $row)) !== false) -			{ -				// If not and only showing common error messages, use this one -				$error = ''; -				switch ($cp_result) -				{ -					case 'FIELD_INVALID_DATE': -					case 'FIELD_INVALID_VALUE': -					case 'FIELD_REQUIRED': -						$error = $user->lang($cp_result, $row['lang_name']); -					break; - -					case 'FIELD_TOO_SHORT': -					case 'FIELD_TOO_SMALL': -						$error = $user->lang($cp_result, (int) $row['field_minlen'], $row['lang_name']); -					break; - -					case 'FIELD_TOO_LONG': -					case 'FIELD_TOO_LARGE': -						$error = $user->lang($cp_result, (int) $row['field_maxlen'], $row['lang_name']); -					break; - -					case 'FIELD_INVALID_CHARS': -						switch ($row['field_validation']) -						{ -							case '[0-9]+': -								$error = $user->lang($cp_result . '_NUMBERS_ONLY', $row['lang_name']); -							break; - -							case '[\w]+': -								$error = $user->lang($cp_result . '_ALPHA_ONLY', $row['lang_name']); -							break; - -							case '[\w_\+\. \-\[\]]+': -								$error = $user->lang($cp_result . '_SPACERS_ONLY', $row['lang_name']); -							break; -						} -					break; -				} - -				if ($error != '') -				{ -					$cp_error[] = $error; -				} -			} -		} -		$db->sql_freeresult($result); -	} - -	/** -	* Update profile field data directly -	*/ -	function update_profile_field_data($user_id, &$cp_data) -	{ -		global $db; - -		if (!sizeof($cp_data)) -		{ -			return; -		} - -		switch ($db->sql_layer) -		{ -			case 'oracle': -			case 'firebird': -			case 'postgres': -				$right_delim = $left_delim = '"'; -			break; - -			case 'sqlite': -			case 'mssql': -			case 'mssql_odbc': -			case 'mssqlnative': -				$right_delim = ']'; -				$left_delim = '['; -			break; - -			case 'mysql': -			case 'mysql4': -			case 'mysqli': -				$right_delim = $left_delim = '`'; -			break; -		} - -		// use new array for the UPDATE; changes in the key do not affect the original array -		$cp_data_sql = array(); -		foreach ($cp_data as $key => $value) -		{ -			// Firebird is case sensitive with delimiter -			$cp_data_sql[$left_delim . (($db->sql_layer == 'firebird' || $db->sql_layer == 'oracle') ? strtoupper($key) : $key) . $right_delim] = $value; -		} - -		$sql = 'UPDATE ' . PROFILE_FIELDS_DATA_TABLE . ' -			SET ' . $db->sql_build_array('UPDATE', $cp_data_sql) . " -			WHERE user_id = $user_id"; -		$db->sql_query($sql); - -		if (!$db->sql_affectedrows()) -		{ -			$cp_data_sql['user_id'] = (int) $user_id; - -			$db->sql_return_on_error(true); - -			$sql = 'INSERT INTO ' . PROFILE_FIELDS_DATA_TABLE . ' ' . $db->sql_build_array('INSERT', $cp_data_sql); -			$db->sql_query($sql); - -			$db->sql_return_on_error(false); -		} -	} - -	/** -	* Assign fields to template, used for viewprofile, viewtopic and memberlist (if load setting is enabled) -	* This is directly connected to the user -> mode == grab is to grab the user specific fields, mode == show is for assigning the row to the template -	* @access public -	*/ -	function generate_profile_fields_template($mode, $user_id = 0, $profile_row = false) -	{ -		global $db; - -		if ($mode == 'grab') -		{ -			if (!is_array($user_id)) -			{ -				$user_id = array($user_id); -			} - -			if (!sizeof($this->profile_cache)) -			{ -				$this->build_cache(); -			} - -			if (!sizeof($user_id)) -			{ -				return array(); -			} - -			$sql = 'SELECT * -				FROM ' . PROFILE_FIELDS_DATA_TABLE . ' -				WHERE ' . $db->sql_in_set('user_id', array_map('intval', $user_id)); -			$result = $db->sql_query($sql); - -			$field_data = array(); -			while ($row = $db->sql_fetchrow($result)) -			{ -				$field_data[$row['user_id']] = $row; -			} -			$db->sql_freeresult($result); - -			$user_fields = array(); - -			$user_ids = $user_id; - -			// Go through the fields in correct order -			foreach (array_keys($this->profile_cache) as $used_ident) -			{ -				foreach ($field_data as $user_id => $row) -				{ -					$user_fields[$user_id][$used_ident]['value'] = $row['pf_' . $used_ident]; -					$user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident]; -				} - -				foreach ($user_ids as $user_id) -				{ -					if (!isset($user_fields[$user_id][$used_ident]) && $this->profile_cache[$used_ident]['field_show_novalue']) -					{ -						$user_fields[$user_id][$used_ident]['value'] = ''; -						$user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident]; -					} -				} -			} - -			return $user_fields; -		} -		else if ($mode == 'show') -		{ -			// $profile_row == $user_fields[$row['user_id']]; -			$tpl_fields = array(); -			$tpl_fields['row'] = $tpl_fields['blockrow'] = array(); - -			foreach ($profile_row as $ident => $ident_ary) -			{ -				$value = $this->get_profile_value($ident_ary); - -				if ($value === NULL) -				{ -					continue; -				} - -				$tpl_fields['row'] += array( -					'PROFILE_' . strtoupper($ident) . '_VALUE'	=> $value, -					'PROFILE_' . strtoupper($ident) . '_TYPE'	=> $ident_ary['data']['field_type'], -					'PROFILE_' . strtoupper($ident) . '_NAME'	=> $ident_ary['data']['lang_name'], -					'PROFILE_' . strtoupper($ident) . '_EXPLAIN'=> $ident_ary['data']['lang_explain'], - -					'S_PROFILE_' . strtoupper($ident)			=> true -				); - -				$tpl_fields['blockrow'][] = array( -					'PROFILE_FIELD_VALUE'	=> $value, -					'PROFILE_FIELD_TYPE'	=> $ident_ary['data']['field_type'], -					'PROFILE_FIELD_NAME'	=> $ident_ary['data']['lang_name'], -					'PROFILE_FIELD_EXPLAIN'	=> $ident_ary['data']['lang_explain'], - -					'S_PROFILE_' . strtoupper($ident)		=> true -				); -			} - -			return $tpl_fields; -		} -		else -		{ -			trigger_error('Wrong mode for custom profile', E_USER_ERROR); -		} -	} - -	/** -	* Get Profile Value for display -	*/ -	function get_profile_value($ident_ary) -	{ -		$value = $ident_ary['value']; -		$field_type = $ident_ary['data']['field_type']; - -		switch ($this->profile_types[$field_type]) -		{ -			case 'int': -				if ($value === '' && !$ident_ary['data']['field_show_novalue']) -				{ -					return NULL; -				} -				return (int) $value; -			break; - -			case 'string': -			case 'text': -				if (!$value && !$ident_ary['data']['field_show_novalue']) -				{ -					return NULL; -				} - -				$value = make_clickable($value); -				$value = censor_text($value); -				$value = bbcode_nl2br($value); -				return $value; -			break; - -			// case 'datetime': -			case 'date': -				$date = explode('-', $value); -				$day = (isset($date[0])) ? (int) $date[0] : 0; -				$month = (isset($date[1])) ? (int) $date[1] : 0; -				$year = (isset($date[2])) ? (int) $date[2] : 0; - -				if (!$day && !$month && !$year && !$ident_ary['data']['field_show_novalue']) -				{ -					return NULL; -				} -				else if ($day && $month && $year) -				{ -					global $user; -					// Date should display as the same date for every user regardless of timezone - -					return $user->create_datetime() -						->setDate($year, $month, $day) -						->setTime(0, 0, 0) -						->format($user->lang['DATE_FORMAT'], true); -				} - -				return $value; -			break; - -			case 'dropdown': -				$field_id = $ident_ary['data']['field_id']; -				$lang_id = $ident_ary['data']['lang_id']; -				if (!isset($this->options_lang[$field_id][$lang_id])) -				{ -					$this->get_option_lang($field_id, $lang_id, FIELD_DROPDOWN, false); -				} - -				if ($value == $ident_ary['data']['field_novalue'] && !$ident_ary['data']['field_show_novalue']) -				{ -					return NULL; -				} - -				$value = (int) $value; - -				// User not having a value assigned -				if (!isset($this->options_lang[$field_id][$lang_id][$value])) -				{ -					if ($ident_ary['data']['field_show_novalue']) -					{ -						$value = $ident_ary['data']['field_novalue']; -					} -					else -					{ -						return NULL; -					} -				} - -				return $this->options_lang[$field_id][$lang_id][$value]; -			break; - -			case 'bool': -				$field_id = $ident_ary['data']['field_id']; -				$lang_id = $ident_ary['data']['lang_id']; -				if (!isset($this->options_lang[$field_id][$lang_id])) -				{ -					$this->get_option_lang($field_id, $lang_id, FIELD_BOOL, false); -				} - -				if (!$value && $ident_ary['data']['field_show_novalue']) -				{ -					$value = $ident_ary['data']['field_default_value']; -				} - -				if ($ident_ary['data']['field_length'] == 1) -				{ -					return (isset($this->options_lang[$field_id][$lang_id][(int) $value])) ? $this->options_lang[$field_id][$lang_id][(int) $value] : NULL; -				} -				else if (!$value) -				{ -					return NULL; -				} -				else -				{ -					return $this->options_lang[$field_id][$lang_id][(int) ($value) + 1]; -				} -			break; - -			default: -				trigger_error('Unknown profile type', E_USER_ERROR); -			break; -		} -	} - -	/** -	* Get field value for registration/profile -	* @access private -	*/ -	function get_var($field_validation, &$profile_row, $default_value, $preview) -	{ -		global $user; -		global $request; - -		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; -		$user_ident = $profile_row['field_ident']; -		// checkbox - set the value to "true" if it has been set to 1 -		if ($profile_row['field_type'] == FIELD_BOOL && $profile_row['field_length'] == 2) -		{ -			$value = (isset($_REQUEST[$profile_row['field_ident']]) && request_var($profile_row['field_ident'], $default_value) == 1) ? true : ((!isset($user->profile_fields[$user_ident]) || $preview) ? $default_value : $user->profile_fields[$user_ident]); -		} -		else if ($profile_row['field_type'] == FIELD_INT) -		{ -			if (isset($_REQUEST[$profile_row['field_ident']])) -			{ -				$value = ($request->variable($profile_row['field_ident'], '') === '') ? NULL : $request->variable($profile_row['field_ident'], $default_value); -			} -			else -			{ -				if (!$preview && array_key_exists($user_ident, $user->profile_fields) && is_null($user->profile_fields[$user_ident])) -				{ -					$value = NULL; -				} -				else if (!isset($user->profile_fields[$user_ident]) || $preview) -				{ -					$value = $default_value; -				} -				else -				{ -					$value = $user->profile_fields[$user_ident]; -				} -			} - -			return (is_null($value) || $value === '') ? '' : (int) $value; -		} -		else -		{ -			$value = (isset($_REQUEST[$profile_row['field_ident']])) ? request_var($profile_row['field_ident'], $default_value, true) : ((!isset($user->profile_fields[$user_ident]) || $preview) ? $default_value : $user->profile_fields[$user_ident]); - -			if (gettype($value) == 'string') -			{ -				$value = utf8_normalize_nfc($value); -			} -		} - -		switch ($field_validation) -		{ -			case 'int': -				return (int) $value; -			break; -		} - -		return $value; -	} - -	/** -	* Process int-type -	* @access private -	*/ -	function generate_int($profile_row, $preview = false) -	{ -		global $template; - -		$profile_row['field_value'] = $this->get_var('int', $profile_row, $profile_row['field_default_value'], $preview); -		$template->assign_block_vars($this->profile_types[$profile_row['field_type']], array_change_key_case($profile_row, CASE_UPPER)); -	} - -	/** -	* Process date-type -	* @access private -	*/ -	function generate_date($profile_row, $preview = false) -	{ -		global $user, $template; - -		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; -		$user_ident = $profile_row['field_ident']; - -		$now = getdate(); - -		if (!isset($_REQUEST[$profile_row['field_ident'] . '_day'])) -		{ -			if ($profile_row['field_default_value'] == 'now') -			{ -				$profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); -			} -			list($day, $month, $year) = explode('-', ((!isset($user->profile_fields[$user_ident]) || $preview) ? $profile_row['field_default_value'] : $user->profile_fields[$user_ident])); -		} -		else -		{ -			if ($preview && $profile_row['field_default_value'] == 'now') -			{ -				$profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); -				list($day, $month, $year) = explode('-', ((!isset($user->profile_fields[$user_ident]) || $preview) ? $profile_row['field_default_value'] : $user->profile_fields[$user_ident])); -			} -			else -			{ -				$day = request_var($profile_row['field_ident'] . '_day', 0); -				$month = request_var($profile_row['field_ident'] . '_month', 0); -				$year = request_var($profile_row['field_ident'] . '_year', 0); -			} -		} - -		$profile_row['s_day_options'] = '<option value="0"' . ((!$day) ? ' selected="selected"' : '') . '>--</option>'; -		for ($i = 1; $i < 32; $i++) -		{ -			$profile_row['s_day_options'] .= '<option value="' . $i . '"' . (($i == $day) ? ' selected="selected"' : '') . ">$i</option>"; -		} - -		$profile_row['s_month_options'] = '<option value="0"' . ((!$month) ? ' selected="selected"' : '') . '>--</option>'; -		for ($i = 1; $i < 13; $i++) -		{ -			$profile_row['s_month_options'] .= '<option value="' . $i . '"' . (($i == $month) ? ' selected="selected"' : '') . ">$i</option>"; -		} - -		$profile_row['s_year_options'] = '<option value="0"' . ((!$year) ? ' selected="selected"' : '') . '>--</option>'; -		for ($i = $now['year'] - 100; $i <= $now['year'] + 100; $i++) -		{ -			$profile_row['s_year_options'] .= '<option value="' . $i . '"' . (($i == $year) ? ' selected="selected"' : '') . ">$i</option>"; -		} -		unset($now); - -		$profile_row['field_value'] = 0; -		$template->assign_block_vars($this->profile_types[$profile_row['field_type']], array_change_key_case($profile_row, CASE_UPPER)); -	} - -	/** -	* Process bool-type -	* @access private -	*/ -	function generate_bool($profile_row, $preview = false) -	{ -		global $template; - -		$value = $this->get_var('int', $profile_row, $profile_row['field_default_value'], $preview); - -		$profile_row['field_value'] = $value; -		$template->assign_block_vars($this->profile_types[$profile_row['field_type']], array_change_key_case($profile_row, CASE_UPPER)); - -		if ($profile_row['field_length'] == 1) -		{ -			if (!isset($this->options_lang[$profile_row['field_id']][$profile_row['lang_id']]) || !sizeof($this->options_lang[$profile_row['field_id']][$profile_row['lang_id']])) -			{ -				$this->get_option_lang($profile_row['field_id'], $profile_row['lang_id'], FIELD_BOOL, $preview); -			} - -			foreach ($this->options_lang[$profile_row['field_id']][$profile_row['lang_id']] as $option_id => $option_value) -			{ -				$template->assign_block_vars('bool.options', array( -					'OPTION_ID'	=> $option_id, -					'CHECKED'	=> ($value == $option_id) ? ' checked="checked"' : '', -					'VALUE'		=> $option_value) -				); -			} -		} -	} - -	/** -	* Process string-type -	* @access private -	*/ -	function generate_string($profile_row, $preview = false) -	{ -		global $template; - -		$profile_row['field_value'] = $this->get_var('string', $profile_row, $profile_row['lang_default_value'], $preview); -		$template->assign_block_vars($this->profile_types[$profile_row['field_type']], array_change_key_case($profile_row, CASE_UPPER)); -	} - -	/** -	* Process text-type -	* @access private -	*/ -	function generate_text($profile_row, $preview = false) -	{ -		global $template; -		global $user, $phpEx, $phpbb_root_path; - -		$field_length = explode('|', $profile_row['field_length']); -		$profile_row['field_rows'] = $field_length[0]; -		$profile_row['field_cols'] = $field_length[1]; - -		$profile_row['field_value'] = $this->get_var('string', $profile_row, $profile_row['lang_default_value'], $preview); -		$template->assign_block_vars($this->profile_types[$profile_row['field_type']], array_change_key_case($profile_row, CASE_UPPER)); -	} - -	/** -	* Process dropdown-type -	* @access private -	*/ -	function generate_dropdown($profile_row, $preview = false) -	{ -		global $user, $template; - -		$value = $this->get_var('int', $profile_row, $profile_row['field_default_value'], $preview); - -		if (!isset($this->options_lang[$profile_row['field_id']]) || !isset($this->options_lang[$profile_row['field_id']][$profile_row['lang_id']]) || !sizeof($this->options_lang[$profile_row['field_id']][$profile_row['lang_id']])) -		{ -			$this->get_option_lang($profile_row['field_id'], $profile_row['lang_id'], FIELD_DROPDOWN, $preview); -		} - -		$profile_row['field_value'] = $value; -		$template->assign_block_vars($this->profile_types[$profile_row['field_type']], array_change_key_case($profile_row, CASE_UPPER)); - -		foreach ($this->options_lang[$profile_row['field_id']][$profile_row['lang_id']] as $option_id => $option_value) -		{ -			$template->assign_block_vars('dropdown.options', array( -				'OPTION_ID'	=> $option_id, -				'SELECTED'	=> ($value == $option_id) ? ' selected="selected"' : '', -				'VALUE'		=> $option_value) -			); -		} -	} - -	/** -	* Return Templated value/field. Possible values for $mode are: -	* change == user is able to set/enter profile values; preview == just show the value -	* @access private -	*/ -	function process_field_row($mode, $profile_row) -	{ -		global $template; - -		$preview = ($mode == 'preview') ? true : false; - -		// set template filename -		$template->set_filenames(array( -			'cp_body'		=> 'custom_profile_fields.html') -		); - -		// empty previously filled blockvars -		foreach ($this->profile_types as $field_case => $field_type) -		{ -			$template->destroy_block_vars($field_type); -		} - -		// Assign template variables -		$type_func = 'generate_' . $this->profile_types[$profile_row['field_type']]; -		$this->$type_func($profile_row, $preview); - -		// Return templated data -		return $template->assign_display('cp_body'); -	} - -	/** -	* Build Array for user insertion into custom profile fields table -	*/ -	function build_insert_sql_array($cp_data) -	{ -		global $db, $user, $auth; - -		$sql_not_in = array(); -		foreach ($cp_data as $key => $null) -		{ -			$sql_not_in[] = (strncmp($key, 'pf_', 3) === 0) ? substr($key, 3) : $key; -		} - -		$sql = 'SELECT f.field_type, f.field_ident, f.field_default_value, l.lang_default_value -			FROM ' . PROFILE_LANG_TABLE . ' l, ' . PROFILE_FIELDS_TABLE . ' f -			WHERE l.lang_id = ' . $user->get_iso_lang_id() . ' -				' . ((sizeof($sql_not_in)) ? ' AND ' . $db->sql_in_set('f.field_ident', $sql_not_in, true) : '') . ' -				AND l.field_id = f.field_id'; -		$result = $db->sql_query($sql); - -		while ($row = $db->sql_fetchrow($result)) -		{ -			if ($row['field_default_value'] == 'now' && $row['field_type'] == FIELD_DATE) -			{ -				$now = getdate(); -				$row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); -			} -			else if ($row['field_default_value'] === '' && $row['field_type'] == FIELD_INT) -			{ -				// We cannot insert an empty string into an integer column. -				$row['field_default_value'] = NULL; -			} - -			$cp_data['pf_' . $row['field_ident']] = (in_array($row['field_type'], array(FIELD_TEXT, FIELD_STRING))) ? $row['lang_default_value'] : $row['field_default_value']; -		} -		$db->sql_freeresult($result); - -		return $cp_data; -	} - -	/** -	* Get profile field value on submit -	* @access private -	*/ -	function get_profile_field($profile_row) -	{ -		global $phpbb_root_path, $phpEx; -		global $config; -		global $request; - -		$var_name = 'pf_' . $profile_row['field_ident']; - -		switch ($profile_row['field_type']) -		{ -			case FIELD_DATE: - -				if (!isset($_REQUEST[$var_name . '_day'])) -				{ -					if ($profile_row['field_default_value'] == 'now') -					{ -						$now = getdate(); -						$profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); -					} -					list($day, $month, $year) = explode('-', $profile_row['field_default_value']); -				} -				else -				{ -					$day = request_var($var_name . '_day', 0); -					$month = request_var($var_name . '_month', 0); -					$year = request_var($var_name . '_year', 0); -				} - -				$var = sprintf('%2d-%2d-%4d', $day, $month, $year); -			break; - -			case FIELD_BOOL: -				// Checkbox -				if ($profile_row['field_length'] == 2) -				{ -					$var = (isset($_REQUEST[$var_name])) ? 1 : 0; -				} -				else -				{ -					$var = request_var($var_name, (int) $profile_row['field_default_value']); -				} -			break; - -			case FIELD_STRING: -			case FIELD_TEXT: -				$var = utf8_normalize_nfc(request_var($var_name, (string) $profile_row['field_default_value'], true)); -			break; - -			case FIELD_INT: -				if (isset($_REQUEST[$var_name]) && $request->variable($var_name, '') === '') -				{ -					$var = NULL; -				} -				else -				{ -					$var = request_var($var_name, (int) $profile_row['field_default_value']); -				} -			break; - -			case FIELD_DROPDOWN: -				$var = request_var($var_name, (int) $profile_row['field_default_value']); -			break; - -			default: -				$var = request_var($var_name, $profile_row['field_default_value']); -			break; -		} - -		return $var; -	} -} - -/** -* Custom Profile Fields ACP -* @package phpBB3 -*/ -class custom_profile_admin extends custom_profile -{ -	var $vars = array(); - -	/** -	* Return possible validation options -	*/ -	function validate_options() -	{ -		global $user; - -		$validate_ary = array('CHARS_ANY' => '.*', 'NUMBERS_ONLY' => '[0-9]+', 'ALPHA_ONLY' => '[\w]+', 'ALPHA_SPACERS' => '[\w_\+\. \-\[\]]+'); - -		$validate_options = ''; -		foreach ($validate_ary as $lang => $value) -		{ -			$selected = ($this->vars['field_validation'] == $value) ? ' selected="selected"' : ''; -			$validate_options .= '<option value="' . $value . '"' . $selected . '>' . $user->lang[$lang] . '</option>'; -		} - -		return $validate_options; -	} - -	/** -	* Get string options for second step in ACP -	*/ -	function get_string_options() -	{ -		global $user; - -		$options = array( -			0 => array('TITLE' => $user->lang['FIELD_LENGTH'],		'FIELD' => '<input type="number" min="0" name="field_length" size="5" value="' . $this->vars['field_length'] . '" />'), -			1 => array('TITLE' => $user->lang['MIN_FIELD_CHARS'],	'FIELD' => '<input type="number" min="0" name="field_minlen" size="5" value="' . $this->vars['field_minlen'] . '" />'), -			2 => array('TITLE' => $user->lang['MAX_FIELD_CHARS'],	'FIELD' => '<input type="number" min="0" size="5" value="' . $this->vars['field_maxlen'] . '" />'), -			3 => array('TITLE' => $user->lang['FIELD_VALIDATION'],	'FIELD' => '<select name="field_validation">' . $this->validate_options() . '</select>') -		); - -		return $options; -	} - -	/** -	* Get text options for second step in ACP -	*/ -	function get_text_options() -	{ -		global $user; - -		$options = array( -			0 => array('TITLE' => $user->lang['FIELD_LENGTH'],		'FIELD' => '<input type="number" min="0" max="99999" name="rows" size="5" value="' . $this->vars['rows'] . '" /> ' . $user->lang['ROWS'] . '</dd><dd><input type="number" min="0" max="99999" name="columns" size="5" value="' . $this->vars['columns'] . '" /> ' . $user->lang['COLUMNS'] . ' <input type="hidden" name="field_length" value="' . $this->vars['field_length'] . '" />'), -			1 => array('TITLE' => $user->lang['MIN_FIELD_CHARS'],	'FIELD' => '<input type="number" min="0" max="9999999999" name="field_minlen" size="10" value="' . $this->vars['field_minlen'] . '" />'), -			2 => array('TITLE' => $user->lang['MAX_FIELD_CHARS'],	'FIELD' => '<input type="number" min="0" max="9999999999" name="field_maxlen" size="10" value="' . $this->vars['field_maxlen'] . '" />'), -			3 => array('TITLE' => $user->lang['FIELD_VALIDATION'],	'FIELD' => '<select name="field_validation">' . $this->validate_options() . '</select>') -		); - -		return $options; -	} - -	/** -	* Get int options for second step in ACP -	*/ -	function get_int_options() -	{ -		global $user; - -		$options = array( -			0 => array('TITLE' => $user->lang['FIELD_LENGTH'],		'FIELD' => '<input type="number" min="0" max="99999" name="field_length" size="5" value="' . $this->vars['field_length'] . '" />'), -			1 => array('TITLE' => $user->lang['MIN_FIELD_NUMBER'],	'FIELD' => '<input type="number" min="0" max="99999" name="field_minlen" size="5" value="' . $this->vars['field_minlen'] . '" />'), -			2 => array('TITLE' => $user->lang['MAX_FIELD_NUMBER'],	'FIELD' => '<input type="number" min="0" max="99999" name="field_maxlen" size="5" value="' . $this->vars['field_maxlen'] . '" />'), -			3 => array('TITLE' => $user->lang['DEFAULT_VALUE'],		'FIELD' => '<input type="post" name="field_default_value" value="' . $this->vars['field_default_value'] . '" />') -		); - -		return $options; -	} - -	/** -	* Get bool options for second step in ACP -	*/ -	function get_bool_options() -	{ -		global $user, $config, $lang_defs; - -		$default_lang_id = $lang_defs['iso'][$config['default_lang']]; - -		$profile_row = array( -			'var_name'				=> 'field_default_value', -			'field_id'				=> 1, -			'lang_name'				=> $this->vars['lang_name'], -			'lang_explain'			=> $this->vars['lang_explain'], -			'lang_id'				=> $default_lang_id, -			'field_default_value'	=> $this->vars['field_default_value'], -			'field_ident'			=> 'field_default_value', -			'field_type'			=> FIELD_BOOL, -			'field_length'			=> $this->vars['field_length'], -			'lang_options'			=> $this->vars['lang_options'] -		); - -		$options = array( -			0 => array('TITLE' => $user->lang['FIELD_TYPE'], 'EXPLAIN' => $user->lang['BOOL_TYPE_EXPLAIN'], 'FIELD' => '<label><input type="radio" class="radio" name="field_length" value="1"' . (($this->vars['field_length'] == 1) ? ' checked="checked"' : '') . ' onchange="document.getElementById(\'add_profile_field\').submit();" /> ' . $user->lang['RADIO_BUTTONS'] . '</label><label><input type="radio" class="radio" name="field_length" value="2"' . (($this->vars['field_length'] == 2) ? ' checked="checked"' : '') . ' onchange="document.getElementById(\'add_profile_field\').submit();" /> ' . $user->lang['CHECKBOX'] . '</label>'), -			1 => array('TITLE' => $user->lang['DEFAULT_VALUE'], 'FIELD' => $this->process_field_row('preview', $profile_row)) -		); - -		return $options; -	} - -	/** -	* Get dropdown options for second step in ACP -	*/ -	function get_dropdown_options() -	{ -		global $user, $config, $lang_defs; - -		$default_lang_id = $lang_defs['iso'][$config['default_lang']]; - -		$profile_row[0] = array( -			'var_name'				=> 'field_default_value', -			'field_id'				=> 1, -			'lang_name'				=> $this->vars['lang_name'], -			'lang_explain'			=> $this->vars['lang_explain'], -			'lang_id'				=> $default_lang_id, -			'field_default_value'	=> $this->vars['field_default_value'], -			'field_ident'			=> 'field_default_value', -			'field_type'			=> FIELD_DROPDOWN, -			'lang_options'			=> $this->vars['lang_options'] -		); - -		$profile_row[1] = $profile_row[0]; -		$profile_row[1]['var_name'] = 'field_novalue'; -		$profile_row[1]['field_ident'] = 'field_novalue'; -		$profile_row[1]['field_default_value']	= $this->vars['field_novalue']; - -		$options = array( -			0 => array('TITLE' => $user->lang['DEFAULT_VALUE'], 'FIELD' => $this->process_field_row('preview', $profile_row[0])), -			1 => array('TITLE' => $user->lang['NO_VALUE_OPTION'], 'EXPLAIN' => $user->lang['NO_VALUE_OPTION_EXPLAIN'], 'FIELD' => $this->process_field_row('preview', $profile_row[1])) -		); - -		return $options; -	} - -	/** -	* Get date options for second step in ACP -	*/ -	function get_date_options() -	{ -		global $user, $config, $lang_defs; - -		$default_lang_id = $lang_defs['iso'][$config['default_lang']]; - -		$profile_row = array( -			'var_name'				=> 'field_default_value', -			'lang_name'				=> $this->vars['lang_name'], -			'lang_explain'			=> $this->vars['lang_explain'], -			'lang_id'				=> $default_lang_id, -			'field_default_value'	=> $this->vars['field_default_value'], -			'field_ident'			=> 'field_default_value', -			'field_type'			=> FIELD_DATE, -			'field_length'			=> $this->vars['field_length'] -		); - -		$always_now = request_var('always_now', -1); -		if ($always_now == -1) -		{ -			$s_checked = ($this->vars['field_default_value'] == 'now') ? true : false; -		} -		else -		{ -			$s_checked = ($always_now) ? true : false; -		} - -		$options = array( -			0 => array('TITLE' => $user->lang['DEFAULT_VALUE'],	'FIELD' => $this->process_field_row('preview', $profile_row)), -			1 => array('TITLE' => $user->lang['ALWAYS_TODAY'],	'FIELD' => '<label><input type="radio" class="radio" name="always_now" value="1"' . (($s_checked) ? ' checked="checked"' : '') . ' onchange="document.getElementById(\'add_profile_field\').submit();" /> ' . $user->lang['YES'] . '</label><label><input type="radio" class="radio" name="always_now" value="0"' . ((!$s_checked) ? ' checked="checked"' : '') . ' onchange="document.getElementById(\'add_profile_field\').submit();" /> ' . $user->lang['NO'] . '</label>'), -		); - -		return $options; -	} -} diff --git a/phpBB/includes/functions_transfer.php b/phpBB/includes/functions_transfer.php index 07c9171c60..9bec17ca8f 100644 --- a/phpBB/includes/functions_transfer.php +++ b/phpBB/includes/functions_transfer.php @@ -234,7 +234,7 @@ class transfer  	/**  	* Determine methods able to be used  	*/ -	function methods() +	static public function methods()  	{  		$methods = array();  		$disabled_functions = explode(',', @ini_get('disable_functions')); @@ -287,7 +287,7 @@ class ftp extends transfer  	/**  	* Requests data  	*/ -	function data() +	static public function data()  	{  		global $user; @@ -541,7 +541,7 @@ class ftp_fsock extends transfer  	/**  	* Requests data  	*/ -	function data() +	static public function data()  	{  		global $user; diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index dba6d3d6c2..17cdd0ce39 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -162,7 +162,7 @@ function user_update_name($old_name, $new_name)  function user_add($user_row, $cp_data = false)  {  	global $db, $user, $auth, $config, $phpbb_root_path, $phpEx; -	global $phpbb_dispatcher; +	global $phpbb_dispatcher, $phpbb_container;  	if (empty($user_row['username']) || !isset($user_row['group_id']) || !isset($user_row['user_email']) || !isset($user_row['user_type']))  	{ @@ -276,12 +276,7 @@ function user_add($user_row, $cp_data = false)  	{  		$cp_data['user_id'] = (int) $user_id; -		if (!class_exists('custom_profile')) -		{ -			include_once($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); -		} - -		$cp = new custom_profile(); +		$cp = $phpbb_container->get('profilefields.manager');  		$sql = 'INSERT INTO ' . PROFILE_FIELDS_DATA_TABLE . ' ' .  			$db->sql_build_array('INSERT', $cp->build_insert_sql_array($cp_data));  		$db->sql_query($sql); diff --git a/phpBB/includes/mcp/mcp_queue.php b/phpBB/includes/mcp/mcp_queue.php index 3b6897f4e1..9f9c22a5c5 100644 --- a/phpBB/includes/mcp/mcp_queue.php +++ b/phpBB/includes/mcp/mcp_queue.php @@ -654,6 +654,11 @@ class mcp_queue  				// Handle notifications  				foreach ($post_info as $post_id => $post_data)  				{ +					// A single topic approval may also happen here, so handle deleting the respective notification. +					if (!$post_data['topic_posts_approved']) +					{ +						$phpbb_notifications->delete_notifications('topic_in_queue', $post_data['topic_id']); +					}  					$phpbb_notifications->delete_notifications('post_in_queue', $post_id);  					$phpbb_notifications->add_notifications(array( @@ -773,9 +778,12 @@ class mcp_queue  			$notify_poster = ($action == 'approve' && isset($_REQUEST['notify_poster'])) ? true : false;  			$phpbb_content_visibility = $phpbb_container->get('content.visibility'); +			$first_post_ids = array(); +  			foreach ($topic_info as $topic_id => $topic_data)  			{  				$phpbb_content_visibility->set_topic_visibility(ITEM_APPROVED, $topic_id, $topic_data['forum_id'], $user->data['user_id'], time(), ''); +				$first_post_ids[$topic_id] = (int) $topic_data['topic_first_post_id'];  				$topic_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f={$topic_data['forum_id']}&t={$topic_id}"); @@ -799,23 +807,43 @@ class mcp_queue  			// Only send out the mails, when the posts are being approved  			if ($action == 'approve')  			{ +				// Grab the first post text as it's needed for the quote notification. +				$sql = 'SELECT topic_id, post_text +					FROM ' . POSTS_TABLE . ' +					WHERE ' . $db->sql_in_set('post_id', $first_post_ids); +				$result = $db->sql_query($sql); + +				while ($row = $db->sql_fetchrow($result)) +				{ +					$topic_info[$row['topic_id']]['post_text'] = $row['post_text']; +				} +				$db->sql_freeresult($result); +  				// Handle notifications  				$phpbb_notifications = $phpbb_container->get('notification_manager');  				foreach ($topic_info as $topic_id => $topic_data)  				{ -					$phpbb_notifications->delete_notifications('topic_in_queue', $post_data['topic_id']); +					$topic_data = array_merge($topic_data, array( +						'post_id'		=> $topic_data['topic_first_post_id'], +						'post_subject'	=> $topic_data['topic_title'], +						'post_time'		=> $topic_data['topic_time'], +						'poster_id'		=> $topic_data['topic_poster'], +						'username'		=> $topic_data['topic_first_poster_name'], +					)); + +					$phpbb_notifications->delete_notifications('topic_in_queue', $topic_id);  					$phpbb_notifications->add_notifications(array(  						'quote',  						'topic', -					), $post_data); +					), $topic_data); -					$phpbb_notifications->mark_notifications_read('quote', $post_data['post_id'], $user->data['user_id']); -					$phpbb_notifications->mark_notifications_read('topic', $post_data['topic_id'], $user->data['user_id']); +					$phpbb_notifications->mark_notifications_read('quote', $topic_data['post_id'], $user->data['user_id']); +					$phpbb_notifications->mark_notifications_read('topic', $topic_id, $user->data['user_id']);  					if ($notify_poster)  					{ -						$phpbb_notifications->add_notifications('approve_topic', $post_data); +						$phpbb_notifications->add_notifications('approve_topic', $topic_data);  					}  				}  			} diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 02892964f1..954f8a8349 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -616,23 +616,25 @@ function close_report($report_id_list, $mode, $action, $pm = false)  		}  		unset($close_report_posts, $close_report_topics); +		$phpbb_notifications = $phpbb_container->get('notification_manager'); +  		foreach ($reports as $report)  		{  			if ($pm)  			{  				add_log('mod', 0, 0, 'LOG_PM_REPORT_' .  strtoupper($action) . 'D', $post_info[$report['pm_id']]['message_subject']); +				$phpbb_notifications->delete_notifications('report_pm', $report['pm_id']);  			}  			else  			{  				add_log('mod', $post_info[$report['post_id']]['forum_id'], $post_info[$report['post_id']]['topic_id'], 'LOG_REPORT_' .  strtoupper($action) . 'D', $post_info[$report['post_id']]['post_subject']); +				$phpbb_notifications->delete_notifications('report_post', $report['post_id']);  			}  		}  		// Notify reporters  		if (sizeof($notify_reporters))  		{ -			$phpbb_notifications = $phpbb_container->get('notification_manager'); -  			foreach ($notify_reporters as $report_id => $reporter)  			{  				if ($reporter['user_id'] == ANONYMOUS) @@ -649,8 +651,6 @@ function close_report($report_id_list, $mode, $action, $pm = false)  						'closer_id'			=> $user->data['user_id'],  						'from_user_id'		=> $post_info[$post_id]['author_id'],  					))); - -					$phpbb_notifications->delete_notifications('report_pm', $post_id);  				}  				else  				{ @@ -658,8 +658,6 @@ function close_report($report_id_list, $mode, $action, $pm = false)  						'reporter'			=> $reporter['user_id'],  						'closer_id'			=> $user->data['user_id'],  					))); - -					$phpbb_notifications->delete_notifications('report_post', $post_id);  				}  			}  		} diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index b29f587385..ad6743b3a3 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -1461,6 +1461,7 @@ class parse_message extends bbcode_firstpass  						'is_orphan'		=> 1,  						'real_filename'	=> $filedata['real_filename'],  						'attach_comment'=> $this->filename_data['filecomment'], +						'filesize'		=> $filedata['filesize'],  					);  					$this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data); @@ -1572,6 +1573,7 @@ class parse_message extends bbcode_firstpass  							'is_orphan'		=> 1,  							'real_filename'	=> $filedata['real_filename'],  							'attach_comment'=> $this->filename_data['filecomment'], +							'filesize'		=> $filedata['filesize'],  						);  						$this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data); @@ -1580,8 +1582,10 @@ class parse_message extends bbcode_firstpass  						if (isset($this->plupload) && $this->plupload->is_active())  						{ +							$download_url = append_sid("{$phpbb_root_path}download/file.{$phpEx}", 'mode=view&id=' . $new_entry['attach_id']); +  							// Send the client the attachment data to maintain state -							$json_response->send($this->attachment_data); +							$json_response->send(array('data' => $this->attachment_data, 'download_url' => $download_url));  						}  					}  				} @@ -1649,7 +1653,7 @@ class parse_message extends bbcode_firstpass  		if (sizeof($not_orphan))  		{  			// Get the attachment data, based on the poster id... -			$sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment +			$sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize  				FROM ' . ATTACHMENTS_TABLE . '  				WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . '  					AND poster_id = ' . $check_user_id; @@ -1674,7 +1678,7 @@ class parse_message extends bbcode_firstpass  		// Regenerate newly uploaded attachments  		if (sizeof($orphan))  		{ -			$sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment +			$sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize  				FROM ' . ATTACHMENTS_TABLE . '  				WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . '  					AND poster_id = ' . $user->data['user_id'] . ' diff --git a/phpBB/includes/ucp/ucp_notifications.php b/phpBB/includes/ucp/ucp_notifications.php index 79055095bc..c5963a1ebc 100644 --- a/phpBB/includes/ucp/ucp_notifications.php +++ b/phpBB/includes/ucp/ucp_notifications.php @@ -98,7 +98,19 @@ class ucp_notifications  						$phpbb_notifications->mark_notifications_read(false, false, $user->data['user_id'], $form_time);  						meta_refresh(3, $this->u_action); -						$message = $user->lang['NOTIFICATIONS_MARK_ALL_READ_SUCCESS'] . '<br /><br />' . sprintf($user->lang['RETURN_UCP'], '<a href="' . $this->u_action . '">', '</a>'); +						$message = $user->lang['NOTIFICATIONS_MARK_ALL_READ_SUCCESS']; +						 +						if ($request->is_ajax()) +						{ +							$json_response = new \phpbb\json_response(); +							$json_response->send(array( +								'MESSAGE_TITLE'	=> $user->lang['INFORMATION'], +								'MESSAGE_TEXT'	=> $message, +								'success'		=> true, +							)); +						} +						$message .= '<br /><br />' . $user->lang('RETURN_UCP', '<a href="' . $this->u_action . '">', '</a>'); +  						trigger_error($message);  					}  					else diff --git a/phpBB/includes/ucp/ucp_pm_viewmessage.php b/phpBB/includes/ucp/ucp_pm_viewmessage.php index cebbadc7c7..119b84564a 100644 --- a/phpBB/includes/ucp/ucp_pm_viewmessage.php +++ b/phpBB/includes/ucp/ucp_pm_viewmessage.php @@ -20,7 +20,7 @@ if (!defined('IN_PHPBB'))  */  function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)  { -	global $user, $template, $auth, $db, $cache; +	global $user, $template, $auth, $db, $cache, $phpbb_container;  	global $phpbb_root_path, $request, $phpEx, $config, $phpbb_dispatcher;  	$user->add_lang(array('viewtopic', 'memberlist')); @@ -61,11 +61,7 @@ function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row)  	// Load the custom profile fields  	if ($config['load_cpf_pm'])  	{ -		if (!class_exists('custom_profile')) -		{ -			include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); -		} -		$cp = new custom_profile(); +		$cp = $phpbb_container->get('profilefields.manager');  		$profile_fields = $cp->generate_profile_fields_template('grab', $author_id);  	} diff --git a/phpBB/includes/ucp/ucp_profile.php b/phpBB/includes/ucp/ucp_profile.php index 2252b2ea17..6ddc6833b7 100644 --- a/phpBB/includes/ucp/ucp_profile.php +++ b/phpBB/includes/ucp/ucp_profile.php @@ -29,8 +29,7 @@ class ucp_profile  	function main($id, $mode)  	{  		global $cache, $config, $db, $user, $auth, $template, $phpbb_root_path, $phpEx; -		global $request; -		global $phpbb_container; +		global $request, $phpbb_container;  		$user->add_lang('posting'); @@ -82,13 +81,16 @@ class ucp_profile  						$error[] = ($data['password_confirm']) ? 'NEW_PASSWORD_ERROR' : 'NEW_PASSWORD_CONFIRM_EMPTY';  					} +					// Instantiate passwords manager +					$passwords_manager = $phpbb_container->get('passwords.manager'); +  					// Only check the new password against the previous password if there have been no errors -					if (!sizeof($error) && $auth->acl_get('u_chgpasswd') && $data['new_password'] && phpbb_check_hash($data['new_password'], $user->data['user_password'])) +					if (!sizeof($error) && $auth->acl_get('u_chgpasswd') && $data['new_password'] && $passwords_manager->check($data['new_password'], $user->data['user_password']))  					{  						$error[] = 'SAME_PASSWORD_ERROR';  					} -					if (!phpbb_check_hash($data['cur_password'], $user->data['user_password'])) +					if (!$passwords_manager->check($data['cur_password'], $user->data['user_password']))  					{  						$error[] = ($data['cur_password']) ? 'CUR_PASSWORD_ERROR' : 'CUR_PASSWORD_EMPTY';  					} @@ -105,7 +107,7 @@ class ucp_profile  							'username_clean'	=> ($auth->acl_get('u_chgname') && $config['allow_namechange']) ? utf8_clean_string($data['username']) : $user->data['username_clean'],  							'user_email'		=> ($auth->acl_get('u_chgemail')) ? $data['email'] : $user->data['user_email'],  							'user_email_hash'	=> ($auth->acl_get('u_chgemail')) ? phpbb_email_hash($data['email']) : $user->data['user_email_hash'], -							'user_password'		=> ($auth->acl_get('u_chgpasswd') && $data['new_password']) ? phpbb_hash($data['new_password']) : $user->data['user_password'], +							'user_password'		=> ($auth->acl_get('u_chgpasswd') && $data['new_password']) ? $passwords_manager->hash($data['new_password']) : $user->data['user_password'],  							'user_passchg'		=> ($auth->acl_get('u_chgpasswd') && $data['new_password']) ? time() : 0,  						); @@ -114,7 +116,7 @@ class ucp_profile  							add_log('user', $user->data['user_id'], 'LOG_USER_UPDATE_NAME', $user->data['username'], $data['username']);  						} -						if ($auth->acl_get('u_chgpasswd') && $data['new_password'] && !phpbb_check_hash($data['new_password'], $user->data['user_password'])) +						if ($auth->acl_get('u_chgpasswd') && $data['new_password'] && !$passwords_manager->check($data['new_password'], $user->data['user_password']))  						{  							$user->reset_login_keys();  							add_log('user', $user->data['user_id'], 'LOG_USER_NEW_PASSWORD', $data['username']); @@ -257,9 +259,7 @@ class ucp_profile  					trigger_error('NO_AUTH_PROFILEINFO');  				} -				include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); - -				$cp = new custom_profile(); +				$cp = $phpbb_container->get('profilefields.manager');  				$cp_data = $cp_error = array(); diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 1641c6eef1..ff51ca7b3c 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -35,8 +35,6 @@ class ucp_register  			trigger_error('UCP_REGISTER_DISABLE');  		} -		include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); -  		$coppa			= $request->is_set('coppa') ? (int) $request->variable('coppa', false) : false;  		$agreed			= $request->variable('agreed', false);  		$submit			= $request->is_set_post('submit'); @@ -78,7 +76,7 @@ class ucp_register  			}  		} -		$cp = new custom_profile(); +		$cp = $phpbb_container->get('profilefields.manager');  		$error = $cp_data = $cp_error = array();  		$s_hidden_fields = array(); @@ -294,9 +292,12 @@ class ucp_register  					$user_inactive_time = 0;  				} +				// Instantiate passwords manager +				$passwords_manager = $phpbb_container->get('passwords.manager'); +  				$user_row = array(  					'username'				=> $data['username'], -					'user_password'			=> phpbb_hash($data['new_password']), +					'user_password'			=> $passwords_manager->hash($data['new_password']),  					'user_email'			=> $data['email'],  					'group_id'				=> (int) $group_id,  					'user_timezone'			=> $data['tz'], diff --git a/phpBB/includes/ucp/ucp_remind.php b/phpBB/includes/ucp/ucp_remind.php index b3def63896..44395abb44 100644 --- a/phpBB/includes/ucp/ucp_remind.php +++ b/phpBB/includes/ucp/ucp_remind.php @@ -27,7 +27,7 @@ class ucp_remind  	function main($id, $mode)  	{  		global $config, $phpbb_root_path, $phpEx; -		global $db, $user, $auth, $template; +		global $db, $user, $auth, $template, $phpbb_container;;  		if (!$config['allow_password_reset'])  		{ @@ -88,8 +88,11 @@ class ucp_remind  			// For the activation key a random length between 6 and 10 will do.  			$user_actkey = gen_rand_string(mt_rand(6, 10)); +			// Instantiate passwords manager +			$passwords_manager = $phpbb_container->get('passwords.manager'); +  			$sql = 'UPDATE ' . USERS_TABLE . " -				SET user_newpasswd = '" . $db->sql_escape(phpbb_hash($user_password)) . "', user_actkey = '" . $db->sql_escape($user_actkey) . "' +				SET user_newpasswd = '" . $db->sql_escape($passwords_manager->hash($user_password)) . "', user_actkey = '" . $db->sql_escape($user_actkey) . "'  				WHERE user_id = " . $user_row['user_id'];  			$db->sql_query($sql); diff --git a/phpBB/index.php b/phpBB/index.php index 74fc1b9bda..c363781667 100644 --- a/phpBB/index.php +++ b/phpBB/index.php @@ -27,24 +27,44 @@ $user->setup('viewforum');  // Mark notifications read  if (($mark_notification = $request->variable('mark_notification', 0)))  { -	$phpbb_notifications = $phpbb_container->get('notification_manager'); - -	$notification = $phpbb_notifications->load_notifications(array( -		'notification_id'	=> $mark_notification -	)); +	if ($user->data['user_id'] == ANONYMOUS) +	{ +		if ($request->is_ajax()) +		{ +			trigger_error('LOGIN_REQUIRED'); +		} +		login_box('', $user->lang['LOGIN_REQUIRED']); +	} -	if (isset($notification['notifications'][$mark_notification])) +	if (check_link_hash($request->variable('hash', ''), 'mark_notification_read'))  	{ -		$notification = $notification['notifications'][$mark_notification]; +		$phpbb_notifications = $phpbb_container->get('notification_manager'); -		$notification->mark_read(); +		$notification = $phpbb_notifications->load_notifications(array( +			'notification_id'	=> $mark_notification, +		)); -		if (($redirect = $request->variable('redirect', ''))) +		if (isset($notification['notifications'][$mark_notification]))  		{ -			redirect(append_sid($phpbb_root_path . $redirect)); -		} +			$notification = $notification['notifications'][$mark_notification]; + +			$notification->mark_read(); + +			if ($request->is_ajax()) +			{ +				$json_response = new \phpbb\json_response(); +				$json_response->send(array( +					'success'	=> true, +				)); +			} -		redirect($notification->get_url()); +			if (($redirect = $request->variable('redirect', ''))) +			{ +				redirect(append_sid($phpbb_root_path . $redirect)); +			} + +			redirect($notification->get_url()); +		}  	}  } @@ -153,6 +173,7 @@ $template->assign_vars(array(  	'FORUM_UNREAD_LOCKED_IMG'	=> $user->img('forum_unread_locked', 'UNREAD_POSTS_LOCKED'),  	'S_LOGIN_ACTION'			=> append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'), +	'U_SEND_PASSWORD'           => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '',  	'S_DISPLAY_BIRTHDAY_LIST'	=> ($config['load_birthdays']) ? true : false,  	'U_MARK_FORUMS'		=> ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&mark=forums&mark_time=' . time()) : '', diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index b79420ab71..bad51e2fe3 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -203,7 +203,15 @@ $migrations = $finder  $migrator->set_migrations($migrations);  // What is a safe limit of execution time? Half the max execution time should be safe. -$safe_time_limit = (ini_get('max_execution_time') / 2); +//  No more than 15 seconds so the user isn't sitting and waiting for a very long time +$phpbb_ini = new \phpbb\php\ini(); +$safe_time_limit = min(15, ($phpbb_ini->get_int('max_execution_time') / 2)); + +// While we're going to try limit this to half the max execution time, +//  we want to try and take additional measures to prevent hitting the +//  max execution time (if, say, one migration step takes much longer +//  than the max execution time) +@set_time_limit(0);  while (!$migrator->finished())  { diff --git a/phpBB/install/schemas/firebird_schema.sql b/phpBB/install/schemas/firebird_schema.sql index 6c6aca447d..791e4ce6b3 100644 --- a/phpBB/install/schemas/firebird_schema.sql +++ b/phpBB/install/schemas/firebird_schema.sql @@ -361,7 +361,7 @@ CREATE TABLE phpbb_forums (  	forum_desc_options INTEGER DEFAULT 7 NOT NULL,  	forum_desc_uid VARCHAR(8) CHARACTER SET NONE DEFAULT '' NOT NULL,  	forum_link VARCHAR(255) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE, -	forum_password VARCHAR(40) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE, +	forum_password VARCHAR(255) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE,  	forum_style INTEGER DEFAULT 0 NOT NULL,  	forum_image VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL,  	forum_rules BLOB SUB_TYPE TEXT CHARACTER SET UTF8 DEFAULT '' NOT NULL, @@ -908,7 +908,7 @@ CREATE INDEX phpbb_privmsgs_to_usr_flder_id ON phpbb_privmsgs_to(user_id, folder  CREATE TABLE phpbb_profile_fields (  	field_id INTEGER NOT NULL,  	field_name VARCHAR(255) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE, -	field_type INTEGER DEFAULT 0 NOT NULL, +	field_type VARCHAR(100) CHARACTER SET NONE DEFAULT '' NOT NULL,  	field_ident VARCHAR(20) CHARACTER SET NONE DEFAULT '' NOT NULL,  	field_length VARCHAR(20) CHARACTER SET NONE DEFAULT '' NOT NULL,  	field_minlen VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, @@ -957,7 +957,7 @@ CREATE TABLE phpbb_profile_fields_lang (  	field_id INTEGER DEFAULT 0 NOT NULL,  	lang_id INTEGER DEFAULT 0 NOT NULL,  	option_id INTEGER DEFAULT 0 NOT NULL, -	field_type INTEGER DEFAULT 0 NOT NULL, +	field_type VARCHAR(100) CHARACTER SET NONE DEFAULT '' NOT NULL,  	lang_value VARCHAR(255) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE  );; @@ -1365,7 +1365,7 @@ CREATE TABLE phpbb_users (  	user_regdate INTEGER DEFAULT 0 NOT NULL,  	username VARCHAR(255) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE,  	username_clean VARCHAR(255) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE, -	user_password VARCHAR(40) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE, +	user_password VARCHAR(255) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE,  	user_passchg INTEGER DEFAULT 0 NOT NULL,  	user_pass_convert INTEGER DEFAULT 0 NOT NULL,  	user_email VARCHAR(100) CHARACTER SET UTF8 DEFAULT '' NOT NULL COLLATE UNICODE, diff --git a/phpBB/install/schemas/mssql_schema.sql b/phpBB/install/schemas/mssql_schema.sql index 49804bdcc0..21e27c1f21 100644 --- a/phpBB/install/schemas/mssql_schema.sql +++ b/phpBB/install/schemas/mssql_schema.sql @@ -448,7 +448,7 @@ CREATE TABLE [phpbb_forums] (  	[forum_desc_options] [int] DEFAULT (7) NOT NULL ,  	[forum_desc_uid] [varchar] (8) DEFAULT ('') NOT NULL ,  	[forum_link] [varchar] (255) DEFAULT ('') NOT NULL , -	[forum_password] [varchar] (40) DEFAULT ('') NOT NULL , +	[forum_password] [varchar] (255) DEFAULT ('') NOT NULL ,  	[forum_style] [int] DEFAULT (0) NOT NULL ,  	[forum_image] [varchar] (255) DEFAULT ('') NOT NULL ,  	[forum_rules] [varchar] (4000) DEFAULT ('') NOT NULL , @@ -1114,7 +1114,7 @@ GO  CREATE TABLE [phpbb_profile_fields] (  	[field_id] [int] IDENTITY (1, 1) NOT NULL ,  	[field_name] [varchar] (255) DEFAULT ('') NOT NULL , -	[field_type] [int] DEFAULT (0) NOT NULL , +	[field_type] [varchar] (100) DEFAULT ('') NOT NULL ,  	[field_ident] [varchar] (20) DEFAULT ('') NOT NULL ,  	[field_length] [varchar] (20) DEFAULT ('') NOT NULL ,  	[field_minlen] [varchar] (255) DEFAULT ('') NOT NULL , @@ -1172,7 +1172,7 @@ CREATE TABLE [phpbb_profile_fields_lang] (  	[field_id] [int] DEFAULT (0) NOT NULL ,  	[lang_id] [int] DEFAULT (0) NOT NULL ,  	[option_id] [int] DEFAULT (0) NOT NULL , -	[field_type] [int] DEFAULT (0) NOT NULL , +	[field_type] [varchar] (100) DEFAULT ('') NOT NULL ,  	[lang_value] [varchar] (255) DEFAULT ('') NOT NULL   ) ON [PRIMARY]  GO @@ -1681,7 +1681,7 @@ CREATE TABLE [phpbb_users] (  	[user_regdate] [int] DEFAULT (0) NOT NULL ,  	[username] [varchar] (255) DEFAULT ('') NOT NULL ,  	[username_clean] [varchar] (255) DEFAULT ('') NOT NULL , -	[user_password] [varchar] (40) DEFAULT ('') NOT NULL , +	[user_password] [varchar] (255) DEFAULT ('') NOT NULL ,  	[user_passchg] [int] DEFAULT (0) NOT NULL ,  	[user_pass_convert] [int] DEFAULT (0) NOT NULL ,  	[user_email] [varchar] (100) DEFAULT ('') NOT NULL , diff --git a/phpBB/install/schemas/mysql_40_schema.sql b/phpBB/install/schemas/mysql_40_schema.sql index e287e7dde1..37991ea174 100644 --- a/phpBB/install/schemas/mysql_40_schema.sql +++ b/phpBB/install/schemas/mysql_40_schema.sql @@ -246,7 +246,7 @@ CREATE TABLE phpbb_forums (  	forum_desc_options int(11) UNSIGNED DEFAULT '7' NOT NULL,  	forum_desc_uid varbinary(8) DEFAULT '' NOT NULL,  	forum_link blob NOT NULL, -	forum_password varbinary(120) DEFAULT '' NOT NULL, +	forum_password blob NOT NULL,  	forum_style mediumint(8) UNSIGNED DEFAULT '0' NOT NULL,  	forum_image varbinary(255) DEFAULT '' NOT NULL,  	forum_rules blob NOT NULL, @@ -645,7 +645,7 @@ CREATE TABLE phpbb_privmsgs_to (  CREATE TABLE phpbb_profile_fields (  	field_id mediumint(8) UNSIGNED NOT NULL auto_increment,  	field_name blob NOT NULL, -	field_type tinyint(4) DEFAULT '0' NOT NULL, +	field_type varbinary(100) DEFAULT '' NOT NULL,  	field_ident varbinary(20) DEFAULT '' NOT NULL,  	field_length varbinary(20) DEFAULT '' NOT NULL,  	field_minlen varbinary(255) DEFAULT '' NOT NULL, @@ -681,7 +681,7 @@ CREATE TABLE phpbb_profile_fields_lang (  	field_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL,  	lang_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL,  	option_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, -	field_type tinyint(4) DEFAULT '0' NOT NULL, +	field_type varbinary(100) DEFAULT '' NOT NULL,  	lang_value blob NOT NULL,  	PRIMARY KEY (field_id, lang_id, option_id)  ); @@ -974,7 +974,7 @@ CREATE TABLE phpbb_users (  	user_regdate int(11) UNSIGNED DEFAULT '0' NOT NULL,  	username blob NOT NULL,  	username_clean blob NOT NULL, -	user_password varbinary(120) DEFAULT '' NOT NULL, +	user_password blob NOT NULL,  	user_passchg int(11) UNSIGNED DEFAULT '0' NOT NULL,  	user_pass_convert tinyint(1) UNSIGNED DEFAULT '0' NOT NULL,  	user_email blob NOT NULL, diff --git a/phpBB/install/schemas/mysql_41_schema.sql b/phpBB/install/schemas/mysql_41_schema.sql index 870fbd05e4..a88bab9c3c 100644 --- a/phpBB/install/schemas/mysql_41_schema.sql +++ b/phpBB/install/schemas/mysql_41_schema.sql @@ -246,7 +246,7 @@ CREATE TABLE phpbb_forums (  	forum_desc_options int(11) UNSIGNED DEFAULT '7' NOT NULL,  	forum_desc_uid varchar(8) DEFAULT '' NOT NULL,  	forum_link varchar(255) DEFAULT '' NOT NULL, -	forum_password varchar(40) DEFAULT '' NOT NULL, +	forum_password varchar(255) DEFAULT '' NOT NULL,  	forum_style mediumint(8) UNSIGNED DEFAULT '0' NOT NULL,  	forum_image varchar(255) DEFAULT '' NOT NULL,  	forum_rules text NOT NULL, @@ -645,7 +645,7 @@ CREATE TABLE phpbb_privmsgs_to (  CREATE TABLE phpbb_profile_fields (  	field_id mediumint(8) UNSIGNED NOT NULL auto_increment,  	field_name varchar(255) DEFAULT '' NOT NULL, -	field_type tinyint(4) DEFAULT '0' NOT NULL, +	field_type varchar(100) DEFAULT '' NOT NULL,  	field_ident varchar(20) DEFAULT '' NOT NULL,  	field_length varchar(20) DEFAULT '' NOT NULL,  	field_minlen varchar(255) DEFAULT '' NOT NULL, @@ -681,7 +681,7 @@ CREATE TABLE phpbb_profile_fields_lang (  	field_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL,  	lang_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL,  	option_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, -	field_type tinyint(4) DEFAULT '0' NOT NULL, +	field_type varchar(100) DEFAULT '' NOT NULL,  	lang_value varchar(255) DEFAULT '' NOT NULL,  	PRIMARY KEY (field_id, lang_id, option_id)  ) CHARACTER SET `utf8` COLLATE `utf8_bin`; @@ -974,7 +974,7 @@ CREATE TABLE phpbb_users (  	user_regdate int(11) UNSIGNED DEFAULT '0' NOT NULL,  	username varchar(255) DEFAULT '' NOT NULL,  	username_clean varchar(255) DEFAULT '' NOT NULL, -	user_password varchar(40) DEFAULT '' NOT NULL, +	user_password varchar(255) DEFAULT '' NOT NULL,  	user_passchg int(11) UNSIGNED DEFAULT '0' NOT NULL,  	user_pass_convert tinyint(1) UNSIGNED DEFAULT '0' NOT NULL,  	user_email varchar(100) DEFAULT '' NOT NULL, diff --git a/phpBB/install/schemas/oracle_schema.sql b/phpBB/install/schemas/oracle_schema.sql index 5354e5b1bb..ea5cfdf321 100644 --- a/phpBB/install/schemas/oracle_schema.sql +++ b/phpBB/install/schemas/oracle_schema.sql @@ -509,7 +509,7 @@ CREATE TABLE phpbb_forums (  	forum_desc_options number(11) DEFAULT '7' NOT NULL,  	forum_desc_uid varchar2(8) DEFAULT '' ,  	forum_link varchar2(765) DEFAULT '' , -	forum_password varchar2(120) DEFAULT '' , +	forum_password varchar2(765) DEFAULT '' ,  	forum_style number(8) DEFAULT '0' NOT NULL,  	forum_image varchar2(255) DEFAULT '' ,  	forum_rules clob DEFAULT '' , @@ -1217,7 +1217,7 @@ CREATE INDEX phpbb_privmsgs_to_usr_flder_id ON phpbb_privmsgs_to (user_id, folde  CREATE TABLE phpbb_profile_fields (  	field_id number(8) NOT NULL,  	field_name varchar2(765) DEFAULT '' , -	field_type number(4) DEFAULT '0' NOT NULL, +	field_type varchar2(100) DEFAULT '' ,  	field_ident varchar2(20) DEFAULT '' ,  	field_length varchar2(20) DEFAULT '' ,  	field_minlen varchar2(255) DEFAULT '' , @@ -1277,7 +1277,7 @@ CREATE TABLE phpbb_profile_fields_lang (  	field_id number(8) DEFAULT '0' NOT NULL,  	lang_id number(8) DEFAULT '0' NOT NULL,  	option_id number(8) DEFAULT '0' NOT NULL, -	field_type number(4) DEFAULT '0' NOT NULL, +	field_type varchar2(100) DEFAULT '' ,  	lang_value varchar2(765) DEFAULT '' ,  	CONSTRAINT pk_phpbb_profile_fields_lang PRIMARY KEY (field_id, lang_id, option_id)  ) @@ -1798,7 +1798,7 @@ CREATE TABLE phpbb_users (  	user_regdate number(11) DEFAULT '0' NOT NULL,  	username varchar2(255) DEFAULT '' ,  	username_clean varchar2(255) DEFAULT '' , -	user_password varchar2(120) DEFAULT '' , +	user_password varchar2(765) DEFAULT '' ,  	user_passchg number(11) DEFAULT '0' NOT NULL,  	user_pass_convert number(1) DEFAULT '0' NOT NULL,  	user_email varchar2(300) DEFAULT '' , diff --git a/phpBB/install/schemas/postgres_schema.sql b/phpBB/install/schemas/postgres_schema.sql index 7773602c16..f06bcdf2a9 100644 --- a/phpBB/install/schemas/postgres_schema.sql +++ b/phpBB/install/schemas/postgres_schema.sql @@ -384,7 +384,7 @@ CREATE TABLE phpbb_forums (  	forum_desc_options INT4 DEFAULT '7' NOT NULL CHECK (forum_desc_options >= 0),  	forum_desc_uid varchar(8) DEFAULT '' NOT NULL,  	forum_link varchar(255) DEFAULT '' NOT NULL, -	forum_password varchar(40) DEFAULT '' NOT NULL, +	forum_password varchar(255) DEFAULT '' NOT NULL,  	forum_style INT4 DEFAULT '0' NOT NULL CHECK (forum_style >= 0),  	forum_image varchar(255) DEFAULT '' NOT NULL,  	forum_rules varchar(4000) DEFAULT '' NOT NULL, @@ -853,7 +853,7 @@ CREATE SEQUENCE phpbb_profile_fields_seq;  CREATE TABLE phpbb_profile_fields (  	field_id INT4 DEFAULT nextval('phpbb_profile_fields_seq'),  	field_name varchar(255) DEFAULT '' NOT NULL, -	field_type INT2 DEFAULT '0' NOT NULL, +	field_type varchar(100) DEFAULT '' NOT NULL,  	field_ident varchar(20) DEFAULT '' NOT NULL,  	field_length varchar(20) DEFAULT '' NOT NULL,  	field_minlen varchar(255) DEFAULT '' NOT NULL, @@ -893,7 +893,7 @@ CREATE TABLE phpbb_profile_fields_lang (  	field_id INT4 DEFAULT '0' NOT NULL CHECK (field_id >= 0),  	lang_id INT4 DEFAULT '0' NOT NULL CHECK (lang_id >= 0),  	option_id INT4 DEFAULT '0' NOT NULL CHECK (option_id >= 0), -	field_type INT2 DEFAULT '0' NOT NULL, +	field_type varchar(100) DEFAULT '' NOT NULL,  	lang_value varchar(255) DEFAULT '' NOT NULL,  	PRIMARY KEY (field_id, lang_id, option_id)  ); @@ -1246,7 +1246,7 @@ CREATE TABLE phpbb_users (  	user_regdate INT4 DEFAULT '0' NOT NULL CHECK (user_regdate >= 0),  	username varchar_ci DEFAULT '' NOT NULL,  	username_clean varchar_ci DEFAULT '' NOT NULL, -	user_password varchar(40) DEFAULT '' NOT NULL, +	user_password varchar(255) DEFAULT '' NOT NULL,  	user_passchg INT4 DEFAULT '0' NOT NULL CHECK (user_passchg >= 0),  	user_pass_convert INT2 DEFAULT '0' NOT NULL CHECK (user_pass_convert >= 0),  	user_email varchar(100) DEFAULT '' NOT NULL, diff --git a/phpBB/install/schemas/sqlite_schema.sql b/phpBB/install/schemas/sqlite_schema.sql index 079d48bc65..bfb0e211d7 100644 --- a/phpBB/install/schemas/sqlite_schema.sql +++ b/phpBB/install/schemas/sqlite_schema.sql @@ -239,7 +239,7 @@ CREATE TABLE phpbb_forums (  	forum_desc_options INTEGER UNSIGNED NOT NULL DEFAULT '7',  	forum_desc_uid varchar(8) NOT NULL DEFAULT '',  	forum_link varchar(255) NOT NULL DEFAULT '', -	forum_password varchar(40) NOT NULL DEFAULT '', +	forum_password varchar(255) NOT NULL DEFAULT '',  	forum_style INTEGER UNSIGNED NOT NULL DEFAULT '0',  	forum_image varchar(255) NOT NULL DEFAULT '',  	forum_rules text(65535) NOT NULL DEFAULT '', @@ -626,7 +626,7 @@ CREATE INDEX phpbb_privmsgs_to_usr_flder_id ON phpbb_privmsgs_to (user_id, folde  CREATE TABLE phpbb_profile_fields (  	field_id INTEGER PRIMARY KEY NOT NULL ,  	field_name varchar(255) NOT NULL DEFAULT '', -	field_type tinyint(4) NOT NULL DEFAULT '0', +	field_type varchar(100) NOT NULL DEFAULT '',  	field_ident varchar(20) NOT NULL DEFAULT '',  	field_length varchar(20) NOT NULL DEFAULT '',  	field_minlen varchar(255) NOT NULL DEFAULT '', @@ -661,7 +661,7 @@ CREATE TABLE phpbb_profile_fields_lang (  	field_id INTEGER UNSIGNED NOT NULL DEFAULT '0',  	lang_id INTEGER UNSIGNED NOT NULL DEFAULT '0',  	option_id INTEGER UNSIGNED NOT NULL DEFAULT '0', -	field_type tinyint(4) NOT NULL DEFAULT '0', +	field_type varchar(100) NOT NULL DEFAULT '',  	lang_value varchar(255) NOT NULL DEFAULT '',  	PRIMARY KEY (field_id, lang_id, option_id)  ); @@ -945,7 +945,7 @@ CREATE TABLE phpbb_users (  	user_regdate INTEGER UNSIGNED NOT NULL DEFAULT '0',  	username varchar(255) NOT NULL DEFAULT '',  	username_clean varchar(255) NOT NULL DEFAULT '', -	user_password varchar(40) NOT NULL DEFAULT '', +	user_password varchar(255) NOT NULL DEFAULT '',  	user_passchg INTEGER UNSIGNED NOT NULL DEFAULT '0',  	user_pass_convert INTEGER UNSIGNED NOT NULL DEFAULT '0',  	user_email varchar(100) NOT NULL DEFAULT '', diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 851ffe8ec4..abe930c387 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -371,6 +371,7 @@ $lang = array_merge($lang, array(  	'LOGIN_VIEWFORUM'					=> 'The board requires you to be registered and logged in to view this forum.',  	'LOGIN_EXPLAIN_EDIT'				=> 'In order to edit posts in this forum you have to be registered and logged in.',  	'LOGIN_EXPLAIN_VIEWONLINE'			=> 'In order to view the online list you have to be registered and logged in.', +	'LOGIN_REQUIRED'					=> 'You need to login to perform this action.',  	'LOGOUT'							=> 'Logout',  	'LOGOUT_USER'						=> 'Logout [ %s ]',  	'LOG_ME_IN'							=> 'Remember me', @@ -378,6 +379,7 @@ $lang = array_merge($lang, array(  	'MAIN'					=> 'Main',  	'MARK'					=> 'Mark',  	'MARK_ALL'				=> 'Mark all', +	'MARK_ALL_READ'			=> 'Mark all read',  	'MARK_FORUMS_READ'		=> 'Mark forums read',  	'MARK_READ'				=> 'Mark read',  	'MARK_SUBFORUMS_READ'	=> 'Mark subforums read', diff --git a/phpBB/language/en/plupload.php b/phpBB/language/en/plupload.php index 74ae8b7c78..6039de7dbf 100644 --- a/phpBB/language/en/plupload.php +++ b/phpBB/language/en/plupload.php @@ -42,6 +42,7 @@ $lang = array_merge($lang, array(  	'PLUPLOAD_CLOSE'			=> 'Close',  	'PLUPLOAD_DRAG'				=> 'Drag files here.',  	'PLUPLOAD_DUPLICATE_ERROR'	=> 'Duplicate file error.', +	'PLUPLOAD_DRAG_TEXTAREA'	=> 'You may also attach files by dragging and dropping them in the message box.',  	'PLUPLOAD_ERR_INPUT'		=> 'Failed to open input stream.',  	'PLUPLOAD_ERR_MOVE_UPLOADED'	=> 'Failed to move uploaded file.',  	'PLUPLOAD_ERR_OUTPUT'		=> 'Failed to open output stream.', @@ -49,7 +50,6 @@ $lang = array_merge($lang, array(  	'PLUPLOAD_ERR_FILE_COUNT'	=> 'File count error.',  	'PLUPLOAD_ERR_FILE_INVALID_EXT'	=> 'Error: Invalid file extension:',  	'PLUPLOAD_ERR_RUNTIME_MEMORY'	=> 'Runtime ran out of available memory.', -	'PLUPLOAD_ERR_UPLOAD_LIMIT'	=> 'Upload element accepts only %d file(s) at a time. Extra files were stripped.',  	'PLUPLOAD_ERR_UPLOAD_URL'	=> 'Upload URL might be wrong or does not exist.',  	'PLUPLOAD_EXTENSION_ERROR'	=> 'File extension error.',  	'PLUPLOAD_FILE'				=> 'File: %s', diff --git a/phpBB/language/en/search.php b/phpBB/language/en/search.php index 71cbec4b41..f65022fbcb 100644 --- a/phpBB/language/en/search.php +++ b/phpBB/language/en/search.php @@ -60,7 +60,10 @@ $lang = array_merge($lang, array(  	'LOGIN_EXPLAIN_UNREADSEARCH'=> 'The board requires you to be registered and logged in to view your unread posts.',  	'LOGIN_EXPLAIN_NEWPOSTS'	=> 'The board requires you to be registered and logged in to view new posts since your last visit.', -	'MAX_NUM_SEARCH_KEYWORDS_REFINE'	=> 'You specified too many words to search for. Please do not enter more than %1$d words.', +	'MAX_NUM_SEARCH_KEYWORDS_REFINE'	=> array( +		1	=> 'You specified too many words to search for. Please do not enter more than %1$d word.', +		2	=> 'You specified too many words to search for. Please do not enter more than %1$d words.', +	),  	'NO_KEYWORDS'			=> 'You must specify at least one word to search for. Each word must consist of at least %s and must not contain more than %s excluding wildcards.',  	'NO_RECENT_SEARCHES'	=> 'No searches have been carried out recently.', diff --git a/phpBB/memberlist.php b/phpBB/memberlist.php index 14425ca6e4..019b1873a0 100644 --- a/phpBB/memberlist.php +++ b/phpBB/memberlist.php @@ -617,8 +617,7 @@ switch ($mode)  		$profile_fields = array();  		if ($config['load_cpf_viewprofile'])  		{ -			include_once($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); -			$cp = new custom_profile(); +			$cp = $phpbb_container->get('profilefields.manager');  			$profile_fields = $cp->generate_profile_fields_template('grab', $user_id);  			$profile_fields = (isset($profile_fields[$user_id])) ? $cp->generate_profile_fields_template('show', false, $profile_fields[$user_id]) : array();  		} @@ -1555,8 +1554,7 @@ switch ($mode)  			// Load custom profile fields  			if ($config['load_cpf_memberlist'])  			{ -				include_once($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); -				$cp = new custom_profile(); +				$cp = $phpbb_container->get('profilefields.manager');  				// Grab all profile fields from users in id cache for later use - similar to the poster cache  				$profile_fields_cache = $cp->generate_profile_fields_template('grab', $user_list); diff --git a/phpBB/phpbb/auth/provider/apache.php b/phpBB/phpbb/auth/provider/apache.php index 77bc976938..23cdc89829 100644 --- a/phpBB/phpbb/auth/provider/apache.php +++ b/phpBB/phpbb/auth/provider/apache.php @@ -17,19 +17,28 @@ namespace phpbb\auth\provider;  class apache extends \phpbb\auth\provider\base  {  	/** +	* phpBB passwords manager +	* +	* @var \phpbb\passwords\manager +	*/ +	protected $passwords_manager; + +	/**  	 * Apache Authentication Constructor  	 *  	 * @param 	\phpbb\db\driver\driver 	$db  	 * @param 	\phpbb\config\config 		$config +	 * @param	\phpbb\passwords\manager	$passwords_manager  	 * @param 	\phpbb\request\request 		$request  	 * @param 	\phpbb\user 			$user  	 * @param 	string 				$phpbb_root_path  	 * @param 	string 				$php_ext  	 */ -	public function __construct(\phpbb\db\driver\driver $db, \phpbb\config\config $config, \phpbb\request\request $request, \phpbb\user $user, $phpbb_root_path, $php_ext) +	public function __construct(\phpbb\db\driver\driver $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request $request, \phpbb\user $user, $phpbb_root_path, $php_ext)  	{  		$this->db = $db;  		$this->config = $config; +		$this->passwords_manager = $passwords_manager;  		$this->request = $request;  		$this->user = $user;  		$this->phpbb_root_path = $phpbb_root_path; @@ -220,7 +229,7 @@ class apache extends \phpbb\auth\provider\base  		// generate user account data  		return array(  			'username'		=> $username, -			'user_password'	=> phpbb_hash($password), +			'user_password'	=> $this->passwords_manager->hash($password),  			'user_email'	=> '',  			'group_id'		=> (int) $row['group_id'],  			'user_type'		=> USER_NORMAL, diff --git a/phpBB/phpbb/auth/provider/db.php b/phpBB/phpbb/auth/provider/db.php index 6ea04eab36..6bbbc0be16 100644 --- a/phpBB/phpbb/auth/provider/db.php +++ b/phpBB/phpbb/auth/provider/db.php @@ -18,21 +18,29 @@ namespace phpbb\auth\provider;   */  class db extends \phpbb\auth\provider\base  { +	/** +	* phpBB passwords manager +	* +	* @var \phpbb\passwords\manager +	*/ +	protected $passwords_manager;  	/**  	 * Database Authentication Constructor  	 * -	 * @param	\phpbb\db\driver\driver	$db -	 * @param	\phpbb\config\config 	$config -	 * @param	\phpbb\request\request	$request -	 * @param	\phpbb\user		$user -	 * @param	string			$phpbb_root_path -	 * @param	string			$php_ext +	 * @param	\phpbb\db\driver\driver		$db +	 * @param	\phpbb\config\config 		$config +	 * @param	\phpbb\passwords\manager	$passwords_manager +	 * @param	\phpbb\request\request		$request +	 * @param	\phpbb\user			$user +	 * @param	string				$phpbb_root_path +	 * @param	string				$php_ext  	 */ -	public function __construct(\phpbb\db\driver\driver $db, \phpbb\config\config $config, \phpbb\request\request $request, \phpbb\user $user, $phpbb_root_path, $php_ext) +	public function __construct(\phpbb\db\driver\driver $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request $request, \phpbb\user $user, $phpbb_root_path, $php_ext)  	{  		$this->db = $db;  		$this->config = $config; +		$this->passwords_manager = $passwords_manager;  		$this->request = $request;  		$this->user = $user;  		$this->phpbb_root_path = $phpbb_root_path; @@ -191,10 +199,10 @@ class db extends \phpbb\auth\provider\base  				// cp1252 is phpBB2's default encoding, characters outside ASCII range might work when converted into that encoding  				// plain md5 support left in for conversions from other systems. -				if ((strlen($row['user_password']) == 34 && (phpbb_check_hash(md5($password_old_format), $row['user_password']) || phpbb_check_hash(md5(utf8_to_cp1252($password_old_format)), $row['user_password']))) +				if ((strlen($row['user_password']) == 34 && ($this->passwords_manager->check(md5($password_old_format), $row['user_password']) || $this->passwords_manager->check(md5(utf8_to_cp1252($password_old_format)), $row['user_password'])))  					|| (strlen($row['user_password']) == 32  && (md5($password_old_format) == $row['user_password'] || md5(utf8_to_cp1252($password_old_format)) == $row['user_password'])))  				{ -					$hash = phpbb_hash($password_new_format); +					$hash = $this->passwords_manager->hash($password_new_format);  					// Update the password in the users table to the new format and remove user_pass_convert flag  					$sql = 'UPDATE ' . USERS_TABLE . ' @@ -226,12 +234,12 @@ class db extends \phpbb\auth\provider\base  		}  		// Check password ... -		if (!$row['user_pass_convert'] && phpbb_check_hash($password, $row['user_password'])) +		if (!$row['user_pass_convert'] && $this->passwords_manager->check($password, $row['user_password']))  		{  			// Check for old password hash... -			if (strlen($row['user_password']) == 32) +			if ($this->passwords_manager->convert_flag || strlen($row['user_password']) == 32)  			{ -				$hash = phpbb_hash($password); +				$hash = $this->passwords_manager->hash($password);  				// Update the password in the users table to the new format  				$sql = 'UPDATE ' . USERS_TABLE . " diff --git a/phpBB/phpbb/auth/provider/ldap.php b/phpBB/phpbb/auth/provider/ldap.php index 4ce43853bd..e92a227e16 100644 --- a/phpBB/phpbb/auth/provider/ldap.php +++ b/phpBB/phpbb/auth/provider/ldap.php @@ -19,16 +19,25 @@ namespace phpbb\auth\provider;  class ldap extends \phpbb\auth\provider\base  {  	/** +	* phpBB passwords manager +	* +	* @var \phpbb\passwords\manager +	*/ +	protected $passwords_manager; + +	/**  	 * LDAP Authentication Constructor  	 * -	 * @param 	\phpbb\db\driver\driver	$db -	 * @param 	\phpbb\config\config	$config -	 * @param 	\phpbb\user		$user +	 * @param 	\phpbb\db\driver\driver		$db +	 * @param 	\phpbb\config\config		$config +	 * @param	\phpbb\passwords\manager	$passwords_manager +	 * @param 	\phpbb\user			$user  	 */ -	public function __construct(\phpbb\db\driver\driver $db, \phpbb\config\config $config, \phpbb\user $user) +	public function __construct(\phpbb\db\driver\driver $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\user $user)  	{  		$this->db = $db;  		$this->config = $config; +		$this->passwords_manager = $passwords_manager;  		$this->user = $user;  	} @@ -235,7 +244,7 @@ class ldap extends \phpbb\auth\provider\base  					// generate user account data  					$ldap_user_row = array(  						'username'		=> $username, -						'user_password'	=> phpbb_hash($password), +						'user_password'	=> $this->passwords_manager->hash($password),  						'user_email'	=> (!empty($this->config['ldap_email'])) ? utf8_htmlspecialchars($ldap_result[0][htmlspecialchars_decode($this->config['ldap_email'])][0]) : '',  						'group_id'		=> (int) $row['group_id'],  						'user_type'		=> USER_NORMAL, diff --git a/phpBB/phpbb/auth/provider/oauth/oauth.php b/phpBB/phpbb/auth/provider/oauth/oauth.php index 2749661269..0128c89248 100644 --- a/phpBB/phpbb/auth/provider/oauth/oauth.php +++ b/phpBB/phpbb/auth/provider/oauth/oauth.php @@ -34,6 +34,13 @@ class oauth extends \phpbb\auth\provider\base  	protected $config;  	/** +	* phpBB passwords manager +	* +	* @var \phpbb\passwords\manager +	*/ +	protected $passwords_manager; + +	/**  	* phpBB request object  	*  	* @var \phpbb\request\request_interface @@ -101,6 +108,7 @@ class oauth extends \phpbb\auth\provider\base  	*  	* @param	\phpbb\db\driver\driver	$db  	* @param	\phpbb\config\config	$config +	* @param	\phpbb\passwords\manager	$passwords_manager  	* @param	\phpbb\request\request_interface	$request  	* @param	\phpbb\user		$user  	* @param	string			$auth_provider_oauth_token_storage_table @@ -110,10 +118,11 @@ class oauth extends \phpbb\auth\provider\base  	* @param	string			$phpbb_root_path  	* @param	string			$php_ext  	*/ -	public function __construct(\phpbb\db\driver\driver $db, \phpbb\config\config $config, \phpbb\request\request_interface $request, \phpbb\user $user, $auth_provider_oauth_token_storage_table, $auth_provider_oauth_token_account_assoc, \phpbb\di\service_collection $service_providers, $users_table, $phpbb_root_path, $php_ext) +	public function __construct(\phpbb\db\driver\driver $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request_interface $request, \phpbb\user $user, $auth_provider_oauth_token_storage_table, $auth_provider_oauth_token_account_assoc, \phpbb\di\service_collection $service_providers, $users_table, $phpbb_root_path, $php_ext)  	{  		$this->db = $db;  		$this->config = $config; +		$this->passwords_manager = $passwords_manager;  		$this->request = $request;  		$this->user = $user;  		$this->auth_provider_oauth_token_storage_table = $auth_provider_oauth_token_storage_table; @@ -150,7 +159,7 @@ class oauth extends \phpbb\auth\provider\base  		// Temporary workaround for only having one authentication provider available  		if (!$this->request->is_set('oauth_service'))  		{ -			$provider = new \phpbb\auth\provider\db($this->db, $this->config, $this->request, $this->user, $this->phpbb_root_path, $this->php_ext); +			$provider = new \phpbb\auth\provider\db($this->db, $this->config, $this->passwords_manager, $this->request, $this->user, $this->phpbb_root_path, $this->php_ext);  			return $provider->login($username, $password);  		} diff --git a/phpBB/phpbb/console/command/config/command.php b/phpBB/phpbb/console/command/config/command.php new file mode 100644 index 0000000000..b105bc826d --- /dev/null +++ b/phpBB/phpbb/console/command/config/command.php @@ -0,0 +1,22 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ +namespace phpbb\console\command\config; + +abstract class command extends \phpbb\console\command\command +{ +	/** @var \phpbb\config\config */ +	protected $config; + +	function __construct(\phpbb\config\config $config) +	{ +		$this->config = $config; + +		parent::__construct(); +	} +} diff --git a/phpBB/phpbb/console/command/config/delete.php b/phpBB/phpbb/console/command/config/delete.php new file mode 100644 index 0000000000..9a2d00561d --- /dev/null +++ b/phpBB/phpbb/console/command/config/delete.php @@ -0,0 +1,46 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class delete extends command +{ +	protected function configure() +	{ +		$this +			->setName('config:delete') +			->setDescription('Deletes a configuration option') +			->addArgument( +				'key', +				InputArgument::REQUIRED, +				"The configuration option's name" +			) +		; +	} + +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$key = $input->getArgument('key'); + +		if (isset($this->config[$key])) +		{ +			$this->config->delete($key); + +			$output->writeln("<info>Successfully deleted config $key</info>"); +		} +		else +		{ +			$output->writeln("<error>Config $key does not exist</error>"); +		} +	} +} diff --git a/phpBB/phpbb/console/command/config/get.php b/phpBB/phpbb/console/command/config/get.php new file mode 100644 index 0000000000..275c82b53f --- /dev/null +++ b/phpBB/phpbb/console/command/config/get.php @@ -0,0 +1,54 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class get extends command +{ +	protected function configure() +	{ +		$this +			->setName('config:get') +			->setDescription("Gets a configuration option's value") +			->addArgument( +				'key', +				InputArgument::REQUIRED, +				"The configuration option's name" +			) +			->addOption( +				'no-newline', +				null, +				InputOption::VALUE_NONE, +				'Set this option if the value should be printed without a new line at the end.' +			) +		; +	} + +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$key = $input->getArgument('key'); + +		if (isset($this->config[$key]) && $input->getOption('no-newline')) +		{ +			$output->write($this->config[$key]); +		} +		elseif (isset($this->config[$key])) +		{ +			$output->writeln($this->config[$key]); +		} +		else +		{ +			$output->writeln("<error>Could not get config $key</error>"); +		} +	} +} diff --git a/phpBB/phpbb/console/command/config/increment.php b/phpBB/phpbb/console/command/config/increment.php new file mode 100644 index 0000000000..bc6b63c6ff --- /dev/null +++ b/phpBB/phpbb/console/command/config/increment.php @@ -0,0 +1,52 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class increment extends command +{ +	protected function configure() +	{ +		$this +			->setName('config:increment') +			->setDescription("Increments a configuration option's value") +			->addArgument( +				'key', +				InputArgument::REQUIRED, +				"The configuration option's name" +			) +			->addArgument( +				'increment', +				InputArgument::REQUIRED, +				'Amount to increment by' +			) +			->addOption( +				'dynamic', +				'd', +				InputOption::VALUE_NONE, +				'Set this option if the configuration option changes too frequently to be efficiently cached.' +			) +		; +	} + +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$key = $input->getArgument('key'); +		$increment = $input->getArgument('increment'); +		$use_cache = !$input->getOption('dynamic'); + +		$this->config->increment($key, $increment, $use_cache); + +		$output->writeln("<info>Successfully incremented config $key</info>"); +	} +} diff --git a/phpBB/phpbb/console/command/config/set.php b/phpBB/phpbb/console/command/config/set.php new file mode 100644 index 0000000000..9d471a96ad --- /dev/null +++ b/phpBB/phpbb/console/command/config/set.php @@ -0,0 +1,52 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class set extends command +{ +	protected function configure() +	{ +		$this +			->setName('config:set') +			->setDescription("Sets a configuration option's value") +			->addArgument( +				'key', +				InputArgument::REQUIRED, +				"The configuration option's name" +			) +			->addArgument( +				'value', +				InputArgument::REQUIRED, +				'New configuration value, use 0 and 1 to specify boolean values' +			) +			->addOption( +				'dynamic', +				'd', +				InputOption::VALUE_NONE, +				'Set this option if the configuration option changes too frequently to be efficiently cached.' +			) +		; +	} + +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$key = $input->getArgument('key'); +		$value = $input->getArgument('value'); +		$use_cache = !$input->getOption('dynamic'); + +		$this->config->set($key, $value, $use_cache); + +		$output->writeln("<info>Successfully set config $key</info>"); +	} +} diff --git a/phpBB/phpbb/console/command/config/set_atomic.php b/phpBB/phpbb/console/command/config/set_atomic.php new file mode 100644 index 0000000000..03e7a60210 --- /dev/null +++ b/phpBB/phpbb/console/command/config/set_atomic.php @@ -0,0 +1,65 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class set_atomic extends command +{ +	protected function configure() +	{ +		$this +			->setName('config:set-atomic') +			->setDescription("Sets a configuration option's value only if the old matches the current value.") +			->addArgument( +				'key', +				InputArgument::REQUIRED, +				"The configuration option's name" +			) +			->addArgument( +				'old', +				InputArgument::REQUIRED, +				'Current configuration value, use 0 and 1 to specify boolean values' +			) +			->addArgument( +				'new', +				InputArgument::REQUIRED, +				'New configuration value, use 0 and 1 to specify boolean values' +			) +			->addOption( +				'dynamic', +				'd', +				InputOption::VALUE_NONE, +				'Set this option if the configuration option changes too frequently to be efficiently cached.' +			) +		; +	} + +	protected function execute(InputInterface $input, OutputInterface $output) +	{ +		$key = $input->getArgument('key'); +		$old_value = $input->getArgument('old'); +		$new_value = $input->getArgument('new'); +		$use_cache = !$input->getOption('dynamic'); + +		if ($this->config->set_atomic($key, $old_value, $new_value, $use_cache)) +		{ +			$output->writeln("<info>Successfully set config $key</info>"); +			return 0; +		} +		else +		{ +			$output->writeln("<error>Could not set config $key</error>"); +			return 1; +		} +	} +} diff --git a/phpBB/phpbb/db/migration/data/v310/passwords.php b/phpBB/phpbb/db/migration/data/v310/passwords.php new file mode 100644 index 0000000000..3422f75917 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v310/passwords.php @@ -0,0 +1,46 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +namespace phpbb\db\migration\data\v310; + +class passwords extends \phpbb\db\migration\migration +{ +	static public function depends_on() +	{ +		return array('\phpbb\db\migration\data\v30x\release_3_0_11'); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'users'	=> array( +					'user_password'		=> array('VCHAR:255', ''), +				), +				$this->table_prefix . 'forums'	=> array( +					'forum_password'	=> array('VCHAR:255', ''), +				), +			), +		); +	} + +	public function revert_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'users'	=> array( +					'user_password'		=> array('VCHAR:40', ''), +				), +				$this->table_prefix . 'forums'	=> array( +					'forum_password'	=> array('VCHAR:40', ''), +				), +			), +		); +	} +} diff --git a/phpBB/phpbb/db/migration/data/v310/profilefield_types.php b/phpBB/phpbb/db/migration/data/v310/profilefield_types.php new file mode 100644 index 0000000000..2152aaee20 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v310/profilefield_types.php @@ -0,0 +1,106 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_types extends \phpbb\db\migration\migration +{ + +	static public function depends_on() +	{ +		return array( +			'\phpbb\db\migration\data\v310\alpha2', +		); +	} + +	public function update_schema() +	{ +		return array( +			'change_columns'	=> array( +				$this->table_prefix . 'profile_fields'			=> array( +					'field_type'		=> array('VCHAR:100', ''), +				), +				$this->table_prefix . 'profile_fields_lang'		=> array( +					'field_type'		=> array('VCHAR:100', ''), +				), +			), +		); +	} + +	public function update_data() +	{ +		return array( +			array('custom', array(array($this, 'update_profile_fields_type'))), +			array('custom', array(array($this, 'update_profile_fields_lang_type'))), +		); +	} + +	public function update_profile_fields_type() +	{ +		// Update profile field types +		$sql = 'SELECT field_type +			FROM ' . $this->table_prefix . 'profile_fields +			GROUP BY field_type'; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql = 'UPDATE ' . $this->table_prefix . "profile_fields +				SET field_type = '" . $this->db->sql_escape($this->convert_phpbb30_field_type($row['field_type'])) . "' +				WHERE field_type = '" . $this->db->sql_escape($row['field_type']) . "'"; +			$this->sql_query($sql); +		} +		$this->db->sql_freeresult($result); +	} + +	public function update_profile_fields_lang_type() +	{ +		// Update profile field language types +		$sql = 'SELECT field_type +			FROM ' . $this->table_prefix . 'profile_fields_lang +			GROUP BY field_type'; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$sql = 'UPDATE ' . $this->table_prefix . "profile_fields_lang +				SET field_type = '" . $this->db->sql_escape($this->convert_phpbb30_field_type($row['field_type'])) . "' +				WHERE field_type = '" . $this->db->sql_escape($row['field_type']) . "'"; +			$this->sql_query($sql); +		} +		$this->db->sql_freeresult($result); +	} + +	/** +	* Determine the new field type for a given phpBB 3.0 field type +	* +	*	@param	$field_type	string		Field type in 3.0 +	*	@return		string		Field new type which is used since 3.1 +	*/ +	public function convert_phpbb30_field_type($field_type) +	{ +		switch ($field_type) +		{ +			case FIELD_INT: +				return 'profilefields.type.int'; +			case FIELD_STRING: +				return 'profilefields.type.string'; +			case FIELD_TEXT: +				return 'profilefields.type.text'; +			case FIELD_BOOL: +				return 'profilefields.type.bool'; +			case FIELD_DROPDOWN: +				return 'profilefields.type.dropdown'; +			case FIELD_DATE: +				return 'profilefields.type.date'; +			default: +				return $field_type; +		} +	} +} diff --git a/phpBB/phpbb/db/tools.php b/phpBB/phpbb/db/tools.php index 4360c89ac3..65098b643b 100644 --- a/phpBB/phpbb/db/tools.php +++ b/phpBB/phpbb/db/tools.php @@ -1474,52 +1474,7 @@ class tools  		}  		// Get type -		if (strpos($column_data[0], ':') !== false) -		{ -			list($orig_column_type, $column_length) = explode(':', $column_data[0]); -			if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'])) -			{ -				$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length); -			} -			else -			{ -				if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'])) -				{ -					switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0]) -					{ -						case 'div': -							$column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1]; -							$column_length = ceil($column_length); -							$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); -						break; -					} -				} - -				if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'])) -				{ -					switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0]) -					{ -						case 'mult': -							$column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1]; -							if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2]) -							{ -								$column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3]; -							} -							else -							{ -								$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); -							} -						break; -					} -				} -			} -			$orig_column_type .= ':'; -		} -		else -		{ -			$orig_column_type = $column_data[0]; -			$column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]]; -		} +		list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]);  		// Adjust default value if db-dependent specified  		if (is_array($column_data[1])) @@ -1695,6 +1650,65 @@ class tools  	}  	/** +	* Get the column's database type from the type map +	* +	* @param string $column_map_type +	* @return array		column type for this database +	*					and map type without length +	*/ +	function get_column_type($column_map_type) +	{ +		if (strpos($column_map_type, ':') !== false) +		{ +			list($orig_column_type, $column_length) = explode(':', $column_map_type); +			if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'])) +			{ +				$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length); +			} +			else +			{ +				if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'])) +				{ +					switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0]) +					{ +						case 'div': +							$column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1]; +							$column_length = ceil($column_length); +							$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); +						break; +					} +				} + +				if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'])) +				{ +					switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0]) +					{ +						case 'mult': +							$column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1]; +							if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2]) +							{ +								$column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3]; +							} +							else +							{ +								$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); +							} +						break; +					} +				} +			} +			$orig_column_type .= ':'; +		} +		else +		{ +			$orig_column_type = $column_map_type; +			$column_type = $this->dbms_type_map[$this->sql_layer][$column_map_type]; +		} + +		return array($column_type, $orig_column_type); +	} + +	/**  	* Add new column  	*/  	function sql_column_add($table_name, $column_name, $column_data, $inline = false) diff --git a/phpBB/phpbb/log/log.php b/phpBB/phpbb/log/log.php index a6ee06ebf2..62edc6a77f 100644 --- a/phpBB/phpbb/log/log.php +++ b/phpBB/phpbb/log/log.php @@ -424,7 +424,7 @@ class log implements \phpbb\log\log_interface  		if ($count_logs)  		{  			$sql = 'SELECT COUNT(l.log_id) AS total_entries -				FROM ' . LOG_TABLE . ' l, ' . USERS_TABLE . ' u +				FROM ' . $this->log_table . ' l, ' . USERS_TABLE . ' u  				WHERE l.log_type = ' . (int) $log_type . '  					AND l.user_id = u.user_id  					AND l.log_time >= ' . (int) $log_time . " @@ -449,7 +449,7 @@ class log implements \phpbb\log\log_interface  		}  		$sql = 'SELECT l.*, u.username, u.username_clean, u.user_colour -			FROM ' . LOG_TABLE . ' l, ' . USERS_TABLE . ' u +			FROM ' . $this->log_table . ' l, ' . USERS_TABLE . ' u  			WHERE l.log_type = ' . (int) $log_type . '  				AND u.user_id = l.user_id  				' . (($log_time) ? 'AND l.log_time >= ' . (int) $log_time : '') . " diff --git a/phpBB/phpbb/notification/type/approve_post.php b/phpBB/phpbb/notification/type/approve_post.php index 51a9a704b0..e51ff12b3e 100644 --- a/phpBB/phpbb/notification/type/approve_post.php +++ b/phpBB/phpbb/notification/type/approve_post.php @@ -35,6 +35,13 @@ class approve_post extends \phpbb\notification\type\post  	protected $language_key = 'NOTIFICATION_POST_APPROVED';  	/** +	* Inherit notification read status from post. +	* +	* @var bool +	*/ +	protected $inherit_read_status = false; + +	/**  	* Notification option data (for outputting to the user)  	*  	* @var bool|array False if the service should use it's default data diff --git a/phpBB/phpbb/notification/type/approve_topic.php b/phpBB/phpbb/notification/type/approve_topic.php index 6229800c68..11a240e03d 100644 --- a/phpBB/phpbb/notification/type/approve_topic.php +++ b/phpBB/phpbb/notification/type/approve_topic.php @@ -35,6 +35,13 @@ class approve_topic extends \phpbb\notification\type\topic  	protected $language_key = 'NOTIFICATION_TOPIC_APPROVED';  	/** +	* Inherit notification read status from topic. +	* +	* @var bool +	*/ +	protected $inherit_read_status = false; + +	/**  	* Notification option data (for outputting to the user)  	*  	* @var bool|array False if the service should use it's default data diff --git a/phpBB/phpbb/notification/type/base.php b/phpBB/phpbb/notification/type/base.php index 951585853f..10c876b286 100644 --- a/phpBB/phpbb/notification/type/base.php +++ b/phpBB/phpbb/notification/type/base.php @@ -282,15 +282,17 @@ abstract class base implements \phpbb\notification\type\type_interface  	*/  	public function prepare_for_display()  	{ +		$mark_hash = generate_link_hash('mark_notification_read'); +  		if ($this->get_url())  		{ -			$u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id); +			$u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&hash=' . $mark_hash);  		}  		else  		{  			$redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : ''); -			$u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&redirect=' . urlencode($redirect)); +			$u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&hash=' . $mark_hash . '&redirect=' . urlencode($redirect));  		}  		return array( diff --git a/phpBB/phpbb/notification/type/disapprove_post.php b/phpBB/phpbb/notification/type/disapprove_post.php index 411d4195c7..70de2a3e10 100644 --- a/phpBB/phpbb/notification/type/disapprove_post.php +++ b/phpBB/phpbb/notification/type/disapprove_post.php @@ -35,6 +35,13 @@ class disapprove_post extends \phpbb\notification\type\approve_post  	protected $language_key = 'NOTIFICATION_POST_DISAPPROVED';  	/** +	* Inherit notification read status from post. +	* +	* @var bool +	*/ +	protected $inherit_read_status = false; + +	/**  	* Notification option data (for outputting to the user)  	*  	* @var bool|array False if the service should use it's default data diff --git a/phpBB/phpbb/notification/type/disapprove_topic.php b/phpBB/phpbb/notification/type/disapprove_topic.php index 19e9d468ce..d39201d928 100644 --- a/phpBB/phpbb/notification/type/disapprove_topic.php +++ b/phpBB/phpbb/notification/type/disapprove_topic.php @@ -35,6 +35,13 @@ class disapprove_topic extends \phpbb\notification\type\approve_topic  	protected $language_key = 'NOTIFICATION_TOPIC_DISAPPROVED';  	/** +	* Inherit notification read status from topic. +	* +	* @var bool +	*/ +	protected $inherit_read_status = false; + +	/**  	* Notification option data (for outputting to the user)  	*  	* @var bool|array False if the service should use it's default data diff --git a/phpBB/phpbb/notification/type/post.php b/phpBB/phpbb/notification/type/post.php index c2854c17af..bc42c4422b 100644 --- a/phpBB/phpbb/notification/type/post.php +++ b/phpBB/phpbb/notification/type/post.php @@ -35,6 +35,13 @@ class post extends \phpbb\notification\type\base  	protected $language_key = 'NOTIFICATION_POST';  	/** +	* Inherit notification read status from post. +	* +	* @var bool +	*/ +	protected $inherit_read_status = true; + +	/**  	* Notification option data (for outputting to the user)  	*  	* @var bool|array False if the service should use it's default data @@ -315,7 +322,7 @@ class post extends \phpbb\notification\type\base  	*/  	public function pre_create_insert_array($post, $notify_users)  	{ -		if (!sizeof($notify_users)) +		if (!sizeof($notify_users) || !$this->inherit_read_status)  		{  			return array();  		} @@ -360,7 +367,7 @@ class post extends \phpbb\notification\type\base  		// Topics can be "read" before they are public (while awaiting approval).  		// Make sure that if the user has read the topic, it's marked as read in the notification -		if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) +		if ($this->inherit_read_status && isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time)  		{  			$this->notification_read = true;  		} diff --git a/phpBB/phpbb/notification/type/report_post.php b/phpBB/phpbb/notification/type/report_post.php index 89b497efa6..9bf035b91e 100644 --- a/phpBB/phpbb/notification/type/report_post.php +++ b/phpBB/phpbb/notification/type/report_post.php @@ -35,6 +35,13 @@ class report_post extends \phpbb\notification\type\post_in_queue  	protected $language_key = 'NOTIFICATION_REPORT_POST';  	/** +	* Inherit notification read status from post. +	* +	* @var bool +	*/ +	protected $inherit_read_status = false; + +	/**  	* Permission to check for (in find_users_for_notification)  	*  	* @var string Permission name diff --git a/phpBB/phpbb/notification/type/report_post_closed.php b/phpBB/phpbb/notification/type/report_post_closed.php index 5874d48e31..fff45612b3 100644 --- a/phpBB/phpbb/notification/type/report_post_closed.php +++ b/phpBB/phpbb/notification/type/report_post_closed.php @@ -41,6 +41,13 @@ class report_post_closed extends \phpbb\notification\type\post  	*/  	protected $language_key = 'NOTIFICATION_REPORT_CLOSED'; +	/** +	* Inherit notification read status from post. +	* +	* @var bool +	*/ +	protected $inherit_read_status = false; +  	public function is_available()  	{  		return false; diff --git a/phpBB/phpbb/notification/type/topic.php b/phpBB/phpbb/notification/type/topic.php index 6198881d8d..98f086a50b 100644 --- a/phpBB/phpbb/notification/type/topic.php +++ b/phpBB/phpbb/notification/type/topic.php @@ -35,6 +35,13 @@ class topic extends \phpbb\notification\type\base  	protected $language_key = 'NOTIFICATION_TOPIC';  	/** +	* Inherit notification read status from topic. +	* +	* @var bool +	*/ +	protected $inherit_read_status = true; + +	/**  	* Notification option data (for outputting to the user)  	*  	* @var bool|array False if the service should use it's default data @@ -220,7 +227,7 @@ class topic extends \phpbb\notification\type\base  	*/  	public function pre_create_insert_array($post, $notify_users)  	{ -		if (!sizeof($notify_users)) +		if (!sizeof($notify_users) || !$this->inherit_read_status)  		{  			return array();  		} @@ -261,7 +268,7 @@ class topic extends \phpbb\notification\type\base  		// Topics can be "read" before they are public (while awaiting approval).  		// Make sure that if the user has read the topic, it's marked as read in the notification -		if (isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) +		if ($this->inherit_read_status && isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time)  		{  			$this->notification_read = true;  		} diff --git a/phpBB/phpbb/passwords/driver/base.php b/phpBB/phpbb/passwords/driver/base.php new file mode 100644 index 0000000000..8256fd721c --- /dev/null +++ b/phpBB/phpbb/passwords/driver/base.php @@ -0,0 +1,45 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords\driver; + +/** +* @package passwords +*/ +abstract class base implements driver_interface +{ +	/** @var phpbb\config\config */ +	protected $config; + +	/** @var phpbb\passwords\driver\helper */ +	protected $helper; + +	/** @var driver name */ +	protected $name; + +	/** +	* Constructor of passwords driver object +	* +	* @param \phpbb\config\config $config phpBB config +	* @param \phpbb\passwords\driver\helper $helper Password driver helper +	*/ +	public function __construct(\phpbb\config\config $config, helper $helper) +	{ +		$this->config = $config; +		$this->helper = $helper; +	} + +	/** +	* @inheritdoc +	*/ +	public function is_supported() +	{ +		return true; +	} +} diff --git a/phpBB/phpbb/passwords/driver/bcrypt.php b/phpBB/phpbb/passwords/driver/bcrypt.php new file mode 100644 index 0000000000..1d1b1e267d --- /dev/null +++ b/phpBB/phpbb/passwords/driver/bcrypt.php @@ -0,0 +1,104 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords\driver; + +/** +* @package passwords +*/ +class bcrypt extends base +{ +	const PREFIX = '$2a$'; + +	/** +	* @inheritdoc +	*/ +	public function get_prefix() +	{ +		return self::PREFIX; +	} + +	/** +	* @inheritdoc +	*/ +	public function hash($password, $salt = '') +	{ +		// The 2x and 2y prefixes of bcrypt might not be supported +		// Revert to 2a if this is the case +		$prefix = (!$this->is_supported()) ? '$2a$' : $this->get_prefix(); + +		// Do not support 8-bit characters with $2a$ bcrypt +		// Also see http://www.php.net/security/crypt_blowfish.php +		if ($prefix === self::PREFIX) +		{ +			if (ord($password[strlen($password)-1]) & 128) +			{ +				return false; +			} +		} + +		if ($salt == '') +		{ +			$salt = $prefix . '10$' . $this->get_random_salt(); +		} + +		$hash = crypt($password, $salt); +		if (strlen($hash) < 60) +		{ +			return false; +		} +		return $hash; +	} + +	/** +	* @inheritdoc +	*/ +	public function check($password, $hash) +	{ +		$salt = substr($hash, 0, 29); +		if (strlen($salt) != 29) +		{ +			return false; +		} + +		if ($hash == $this->hash($password, $salt)) +		{ +			return true; +		} +		return false; +	} + +	/** +	* Get a random salt value with a length of 22 characters +	* +	* @return string Salt for password hashing +	*/ +	protected function get_random_salt() +	{ +		return $this->helper->hash_encode64($this->helper->get_random_salt(22), 22); +	} + +	/** +	* @inheritdoc +	*/ +	public function get_settings_only($hash, $full = false) +	{ +		if ($full) +		{ +			$pos = stripos($hash, '$', 1) + 1; +			$length = 22 + (strripos($hash, '$') + 1 - $pos); +		} +		else +		{ +			$pos = strripos($hash, '$') + 1; +			$length = 22; +		} +		return substr($hash, $pos, $length); +	} +} diff --git a/phpBB/phpbb/passwords/driver/bcrypt_2y.php b/phpBB/phpbb/passwords/driver/bcrypt_2y.php new file mode 100644 index 0000000000..11c3617e49 --- /dev/null +++ b/phpBB/phpbb/passwords/driver/bcrypt_2y.php @@ -0,0 +1,34 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords\driver; + +/** +* @package passwords +*/ +class bcrypt_2y extends bcrypt +{ +	const PREFIX = '$2y$'; + +	/** +	* @inheritdoc +	*/ +	public function get_prefix() +	{ +		return self::PREFIX; +	} + +	/** +	* @inheritdoc +	*/ +	public function is_supported() +	{ +		return (version_compare(PHP_VERSION, '5.3.7', '<')) ? false : true; +	} +} diff --git a/phpBB/phpbb/passwords/driver/driver_interface.php b/phpBB/phpbb/passwords/driver/driver_interface.php new file mode 100644 index 0000000000..ebaf0626af --- /dev/null +++ b/phpBB/phpbb/passwords/driver/driver_interface.php @@ -0,0 +1,60 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords\driver; + +/** +* @package passwords +*/ +interface driver_interface +{ +	/** +	* Check if hash type is supported +	* +	* @return bool		True if supported, false if not +	*/ +	public function is_supported(); + +	/** +	* Returns the hash prefix +	* +	* @return string	Hash prefix +	*/ +	public function get_prefix(); + +	/** +	* Hash the password +	* +	* @param string $password The password that should be hashed +	* +	* @return bool|string	Password hash or false if something went wrong +	*			during hashing +	*/ +	public function hash($password); + +	/** +	* Check the password against the supplied hash +	* +	* @param string		$password The password to check +	* @param string		$hash The password hash to check against +	* +	* @return bool		True if password is correct, else false +	*/ +	public function check($password, $hash); + +	/** +	* Get only the settings of the specified hash +	* +	* @param string		$hash Password hash +	* @param bool		$full Return full settings or only settings +	*			related to the salt +	* @return string	String containing the hash settings +	*/ +	public function get_settings_only($hash, $full = false); +} diff --git a/phpBB/phpbb/passwords/driver/helper.php b/phpBB/phpbb/passwords/driver/helper.php new file mode 100644 index 0000000000..4b8dc9a123 --- /dev/null +++ b/phpBB/phpbb/passwords/driver/helper.php @@ -0,0 +1,144 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords\driver; + +/** +* @package passwords +*/ +class helper +{ +	/** +	* @var phpbb\config\config +	*/ +	protected $config; + +	/** +	* base64 alphabet +	* @var string +	*/ +	public $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + +	/** +	* Construct a driver helper object +	* +	* @param phpbb\config\config $config phpBB configuration +	*/ +	public function __construct(\phpbb\config\config $config) +	{ +		$this->config = $config; +	} + +	/** +	* Base64 encode hash +	* +	* @param string $input Input string +	* @param int $count Input string length +	* +	* @return string base64 encoded string +	*/ +	public function hash_encode64($input, $count) +	{ +		$output = ''; +		$i = 0; + +		do +		{ +			$value = ord($input[$i++]); +			$output .= $this->itoa64[$value & 0x3f]; + +			if ($i < $count) +			{ +				$value |= ord($input[$i]) << 8; +			} + +			$output .= $this->itoa64[($value >> 6) & 0x3f]; + +			if ($i++ >= $count) +			{ +				break; +			} + +			if ($i < $count) +			{ +				$value |= ord($input[$i]) << 16; +			} + +			$output .= $this->itoa64[($value >> 12) & 0x3f]; + +			if ($i++ >= $count) +			{ +				break; +			} + +			$output .= $this->itoa64[($value >> 18) & 0x3f]; +		} +		while ($i < $count); + +		return $output; +	} + +	/** +	* Return unique id +	* +	* @param string $extra Additional entropy +	* +	* @return string Unique id +	*/ +	public function unique_id($extra = 'c') +	{ +		static $dss_seeded = false; + +		$val = $this->config['rand_seed'] . microtime(); +		$val = md5($val); +		$this->config['rand_seed'] = md5($this->config['rand_seed'] . $val . $extra); + +		if ($dss_seeded !== true && ($this->config['rand_seed_last_update'] < time() - rand(1,10))) +		{ +			$this->config->set('rand_seed_last_update', time(), true); +			$this->config->set('rand_seed', $this->config['rand_seed'], true); +			$dss_seeded = true; +		} + +		return substr($val, 4, 16); +	} + +	/** +	* Get random salt with specified length +	* +	* @param int $length Salt length +	* @param string $rand_seed Seed for random data (optional). For tests. +	* +	* @return string Random salt with specified length +	*/ +	public function get_random_salt($length, $rand_seed = '/dev/urandom') +	{ +		$random = ''; + +		if (($fh = @fopen($rand_seed, 'rb'))) +		{ +			$random = fread($fh, $length); +			fclose($fh); +		} + +		if (strlen($random) < $length) +		{ +			$random = ''; +			$random_state = $this->unique_id(); + +			for ($i = 0; $i < $length; $i += 16) +			{ +				$random_state = md5($this->unique_id() . $random_state); +				$random .= pack('H*', md5($random_state)); +			} +			$random = substr($random, 0, $length); +		} +		return $random; +	} +} diff --git a/phpBB/phpbb/passwords/driver/phpass.php b/phpBB/phpbb/passwords/driver/phpass.php new file mode 100644 index 0000000000..80c4d7a7f0 --- /dev/null +++ b/phpBB/phpbb/passwords/driver/phpass.php @@ -0,0 +1,26 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords\driver; + +/** +* @package passwords +*/ +class phpass extends salted_md5 +{ +	const PREFIX = '$P$'; + +	/** +	* @inheritdoc +	*/ +	public function get_prefix() +	{ +		return self::PREFIX; +	} +} diff --git a/phpBB/phpbb/passwords/driver/salted_md5.php b/phpBB/phpbb/passwords/driver/salted_md5.php new file mode 100644 index 0000000000..5c72726422 --- /dev/null +++ b/phpBB/phpbb/passwords/driver/salted_md5.php @@ -0,0 +1,160 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords\driver; + +/** +* +* @version Version 0.1 / slightly modified for phpBB 3.1.x (using $H$ as hash type identifier) +* +* Portable PHP password hashing framework. +* +* Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in +* the public domain. +* +* There's absolutely no warranty. +* +* The homepage URL for this framework is: +* +*	http://www.openwall.com/phpass/ +* +* Please be sure to update the Version line if you edit this file in any way. +* It is suggested that you leave the main version number intact, but indicate +* your project name (after the slash) and add your own revision information. +* +* Please do not change the "private" password hashing method implemented in +* here, thereby making your hashes incompatible.  However, if you must, please +* change the hash type identifier (the "$P$") to something different. +* +* Obviously, since this code is in the public domain, the above are not +* requirements (there can be none), but merely suggestions. +* +*/ + +/** +* @package passwords +*/ +class salted_md5 extends base +{ +	const PREFIX = '$H$'; + +	/** +	* @inheritdoc +	*/ +	public function get_prefix() +	{ +		return self::PREFIX; +	} + +	/** +	* @inheritdoc +	*/ +	public function hash($password, $setting = '') +	{ +		if ($setting) +		{ +			if (($settings = $this->get_hash_settings($setting)) === false) +			{ +				// Return md5 of password if settings do not +				// comply with our standards. This will only +				// happen if pre-determined settings are +				// directly passed to the driver. The manager +				// will not do this. Same as the old hashing +				// implementatio in phpBB 3.0 +				return md5($password); +			} +		} +		else +		{ +			$settings = $this->get_hash_settings($this->generate_salt()); +		} + +		$hash = md5($settings['salt'] . $password, true); +		do +		{ +			$hash = md5($hash . $password, true); +		} +		while (--$settings['count']); + +		$output = $settings['full']; +		$output .= $this->helper->hash_encode64($hash, 16); + +		return $output; +	} + +	/** +	* @inheritdoc +	*/ +	public function check($password, $hash) +	{ +		if (strlen($hash) !== 34) +		{ +			return md5($password) === $hash; +		} + +		return $hash === $this->hash($password, $hash); +	} + +	/** +	* Generate salt for hashing method +	* +	* @return string Salt for hashing method +	*/ +	protected function generate_salt() +	{ +		$count = 6; + +		$random = $this->helper->get_random_salt($count); + +		$salt = $this->get_prefix(); +		$salt .= $this->helper->itoa64[min($count + 5, 30)]; +		$salt .= $this->helper->hash_encode64($random, $count); + +		return $salt; +	} + +	/** +	* Get hash settings +	* +	* @param string $hash The hash that contains the settings +	* +	* @return bool|array Array containing the count_log2, salt, and full +	*		hash settings string or false if supplied hash is empty +	*		or contains incorrect settings +	*/ +	public function get_hash_settings($hash) +	{ +		if (empty($hash)) +		{ +			return false; +		} + +		$count_log2 = strpos($this->helper->itoa64, $hash[3]); +		$salt = substr($hash, 4, 8); + +		if ($count_log2 < 7 || $count_log2 > 30 || strlen($salt) != 8) +		{ +			return false; +		} + +		return array( +			'count'	=> 1 << $count_log2, +			'salt'	=> $salt, +			'full'	=> substr($hash, 0, 12), +		); +	} + +	/** +	* @inheritdoc +	*/ +	public function get_settings_only($hash, $full = false) +	{ +		return substr($hash, 3, 9); +	} +} diff --git a/phpBB/phpbb/passwords/helper.php b/phpBB/phpbb/passwords/helper.php new file mode 100644 index 0000000000..95bad5805f --- /dev/null +++ b/phpBB/phpbb/passwords/helper.php @@ -0,0 +1,103 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords; + +/** +* @package passwords +*/ +class helper +{ +	/** +	* Get hash settings from combined hash +	* +	* @param string $hash Password hash of combined hash +	* +	* @return array An array containing the hash settings for the hash +	*		types in successive order as described by the combined +	*		password hash or an empty array if hash does not +	*		properly fit the combined hash format +	*/ +	public function get_combined_hash_settings($hash) +	{ +		$output = array(); + +		preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $hash, $match); +		$hash_settings = substr($hash, strpos($hash, $match[1]) + strlen($match[1]) + 1); +		$matches = explode('\\', $match[1]); +		foreach ($matches as $cur_type) +		{ +			$dollar_position = strpos($hash_settings, '$'); +			$output[] = substr($hash_settings, 0, ($dollar_position != false) ? $dollar_position : strlen($hash_settings)); +			$hash_settings = substr($hash_settings, $dollar_position + 1); +		} + +		return $output; +	} + +	/** +	* Combine hash prefixes, settings, and actual hash +	* +	* @param array $data Array containing the keys 'prefix' and 'settings'. +	*			It will hold the prefixes and settings +	* @param string $type Data type of the supplied value +	* @param string $value Value that should be put into the data array +	* +	* @return string|null Return complete combined hash if type is neither +	*			'prefix' nor 'settings', nothing if it is +	*/ +	public function combine_hash_output(&$data, $type, $value) +	{ +		if ($type == 'prefix') +		{ +			$data[$type] .= ($data[$type] !== '$') ? '\\' : ''; +			$data[$type] .= str_replace('$', '', $value); +		} +		elseif ($type == 'settings') +		{ +			$data[$type] .= ($data[$type] !== '$') ? '$' : ''; +			$data[$type] .= $value; +		} +		else +		{ +			// Return full hash +			return $data['prefix'] . $data['settings'] . '$' . $value; +		} +	} + +	/** +	* Rebuild hash for hashing functions +	* +	* @param string $prefix Hash prefix +	* @param string $settings Hash settings +	* +	* @return string Rebuilt hash for hashing functions +	*/ +	public function rebuild_hash($prefix, $settings) +	{ +		$rebuilt_hash = $prefix; +		if (strpos($settings, '\\') !== false) +		{ +			$settings = str_replace('\\', '$', $settings); +		} +		$rebuilt_hash .= $settings; +		return $rebuilt_hash; +	} + +	/** +	* Obtain only the actual hash after the prefixes +	* +	* @param string		$hash The full password hash +	* @return string	Actual hash (incl. settings) +	*/ +	public function obtain_hash_only($hash) +	{ +		return substr($hash, strripos($hash, '$') + 1); +	} +} diff --git a/phpBB/phpbb/passwords/manager.php b/phpBB/phpbb/passwords/manager.php new file mode 100644 index 0000000000..0ac6b05ec4 --- /dev/null +++ b/phpBB/phpbb/passwords/manager.php @@ -0,0 +1,341 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\passwords; + +/** +* @package passwords +*/ +class manager +{ +	/** +	* Default hashing method +	*/ +	protected $type = false; + +	/** +	* Hashing algorithm type map +	* Will be used to map hash prefix to type +	*/ +	protected $type_map = false; + +	/** +	* Service collection of hashing algorithms +	* Needs to be public for passwords helper +	*/ +	public $algorithms = false; + +	/** +	* Password convert flag. Signals that password should be converted +	*/ +	public $convert_flag = false; + +	/** +	* Passwords helper +	* @var phpbb\passwords\helper +	*/ +	protected $helper; + +	/** +	* phpBB configuration +	* @var phpbb\config\config +	*/ +	protected $config; + +	/** +	* Construct a passwords object +	* +	* @param phpbb\config\config $config phpBB configuration +	* @param array $hashing_algorithms Hashing driver +	*			service collection +	* @param phpbb\passwords\helper $helper Passwords helper object +	* @param string $defaults List of default driver types +	*/ +	public function __construct(\phpbb\config\config $config, $hashing_algorithms, helper $helper, $defaults) +	{ +		$this->config = $config; +		$this->helper = $helper; + +		$this->fill_type_map($hashing_algorithms); +		$this->register_default_type($defaults); +	} + +	/** +	* Register default type +	* Will register the first supported type from the list of default types +	* +	* @param array $defaults List of default types in order from first to +	*			use to last to use +	*/ +	protected function register_default_type($defaults) +	{ +		foreach ($defaults as $type) +		{ +			if ($this->algorithms[$type]->is_supported()) +			{ +				$this->type = $this->algorithms[$type]->get_prefix(); +				break; +			} +		} +	} + +	/** +	* Fill algorithm type map +	* +	* @param phpbb\di\service_collection $hashing_algorithms +	*/ +	protected function fill_type_map($hashing_algorithms) +	{ +		foreach ($hashing_algorithms as $algorithm) +		{ +			if (!isset($this->type_map[$algorithm->get_prefix()])) +			{ +				$this->type_map[$algorithm->get_prefix()] = $algorithm; +			} +		} +		$this->algorithms = $hashing_algorithms; +	} + +	/** +	* Get the algorithm specified by a specific prefix +	* +	* @param string $prefix Password hash prefix +	* +	* @return object|bool The hash type object or false if prefix is not +	*			supported +	*/ +	protected function get_algorithm($prefix) +	{ +		if (isset($this->type_map[$prefix])) +		{ +			return $this->type_map[$prefix]; +		} +		else +		{ +			return false; +		} +	} + +	/** +	* Detect the hash type of the supplied hash +	* +	* @param string $hash Password hash that should be checked +	* +	* @return object|bool The hash type object or false if the specified +	*			type is not supported +	*/ +	public function detect_algorithm($hash) +	{ +		/* +		* preg_match() will also show hashing algos like $2a\H$, which +		* is a combination of bcrypt and phpass. Legacy algorithms +		* like md5 will not be matched by this and need to be treated +		* differently. +		*/ +		if (!preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $hash, $match)) +		{ +			return $this->get_algorithm('$H$'); +		} + +		// Be on the lookout for multiple hashing algorithms +		// 2 is correct: H\2a > 2, H\P > 2 +		if (strlen($match[1]) > 2) +		{ +			$hash_types = explode('\\', $match[1]); +			$return_ary = array(); +			foreach ($hash_types as $type) +			{ +				// we do not support the same hashing +				// algorithm more than once +				if (isset($return_ary[$type])) +				{ +					return false; +				} + +				$return_ary[$type] = $this->get_algorithm('$' . $type . '$'); + +				if (empty($return_ary[$type])) +				{ +					return false; +				} +			} +			return $return_ary; +		} + +		// get_algorithm() will automatically return false if prefix +		// is not supported +		return $this->get_algorithm($match[0]); +	} + +	/** +	* Hash supplied password +	* +	* @param string $password Password that should be hashed +	* @param string $type Hash type. Will default to standard hash type if +	*			none is supplied +	* @return string|bool Password hash of supplied password or false if +	*			if something went wrong during hashing +	*/ +	public function hash($password, $type = '') +	{ +		if (strlen($password) > 4096) +		{ +			// If the password is too huge, we will simply reject it +			// and not let the server try to hash it. +			return false; +		} + +		// Try to retrieve algorithm by service name if type doesn't +		// start with dollar sign +		if (!is_array($type) && strpos($type, '$') !== 0 && isset($this->algorithms[$type])) +		{ +			$type = $this->algorithms[$type]->get_prefix(); +		} + +		$type = ($type === '') ? $this->type : $type; + +		if (is_array($type)) +		{ +			return $this->combined_hash_password($password, $type); +		} + +		if (isset($this->type_map[$type])) +		{ +			$hashing_algorithm = $this->type_map[$type]; +		} +		else +		{ +			return false; +		} + +		return $hashing_algorithm->hash($password); +	} + +	/** +	* Check supplied password against hash and set convert_flag if password +	* needs to be converted to different format (preferrably newer one) +	* +	* @param string $password Password that should be checked +	* @param string $hash Stored hash +	* @return string|bool True if password is correct, false if not +	*/ +	public function check($password, $hash) +	{ +		if (strlen($password) > 4096) +		{ +			// If the password is too huge, we will simply reject it +			// and not let the server try to hash it. +			return false; +		} + +		// First find out what kind of hash we're dealing with +		$stored_hash_type = $this->detect_algorithm($hash); +		if ($stored_hash_type == false) +		{ +			return false; +		} + +		// Multiple hash passes needed +		if (is_array($stored_hash_type)) +		{ +			$correct = $this->check_combined_hash($password, $stored_hash_type, $hash); +			$this->convert_flag = ($correct === true) ? true : false; +			return $correct; +		} + +		if ($stored_hash_type->get_prefix() !== $this->type) +		{ +			$this->convert_flag = true; +		} +		else +		{ +			$this->convert_flag = false; +		} + +		return $stored_hash_type->check($password, $hash); +	} + +	/** +	* Create combined hash from already hashed password +	* +	* @param string $password_hash Complete current password hash +	* @param string $type Type of the hashing algorithm the password hash +	*		should be combined with +	* @return string|bool Combined password hash if combined hashing was +	*		successful, else false +	*/ +	public function combined_hash_password($password_hash, $type) +	{ +		$data = array( +			'prefix' => '$', +			'settings' => '$', +		); +		$hash_settings = $this->helper->get_combined_hash_settings($password_hash); +		$hash = $hash_settings[0]; + +		// Put settings of current hash into data array +		$stored_hash_type = $this->detect_algorithm($password_hash); +		$this->helper->combine_hash_output($data, 'prefix', $stored_hash_type->get_prefix()); +		$this->helper->combine_hash_output($data, 'settings', $stored_hash_type->get_settings_only($password_hash)); + +		// Hash current hash with the defined types +		foreach ($type as $cur_type) +		{ +			if (isset($this->algorithms[$cur_type])) +			{ +				$new_hash_type = $this->algorithms[$cur_type]; +			} +			else +			{ +				$new_hash_type = $this->get_algorithm($cur_type); +			} + +			if (!$new_hash_type) +			{ +				return false; +			} + +			$new_hash = $new_hash_type->hash(str_replace($stored_hash_type->get_settings_only($password_hash), '', $hash)); +			$this->helper->combine_hash_output($data, 'prefix', $new_hash_type->get_prefix()); +			$this->helper->combine_hash_output($data, 'settings', substr(str_replace('$', '\\', $new_hash_type->get_settings_only($new_hash, true)), 0)); +			$hash = str_replace($new_hash_type->get_settings_only($new_hash), '', $this->helper->obtain_hash_only($new_hash)); +		} +		return $this->helper->combine_hash_output($data, 'hash', $hash); +	} + +	/** +	* Check combined password hash against the supplied password +	* +	* @param string $password Password entered by user +	* @param array $stored_hash_type An array containing the hash types +	*				as described by stored password hash +	* @param string $hash Stored password hash +	* +	* @return bool True if password is correct, false if not +	*/ +	public function check_combined_hash($password, $stored_hash_type, $hash) +	{ +		$i = 0; +		$data = array( +			'prefix' => '$', +			'settings' => '$', +		); +		$hash_settings = $this->helper->get_combined_hash_settings($hash); +		foreach ($stored_hash_type as $key => $hash_type) +		{ +			$rebuilt_hash = $this->helper->rebuild_hash($hash_type->get_prefix(), $hash_settings[$i]); +			$this->helper->combine_hash_output($data, 'prefix', $key); +			$this->helper->combine_hash_output($data, 'settings', $hash_settings[$i]); +			$cur_hash = $hash_type->hash($password, $rebuilt_hash); +			$password = str_replace($rebuilt_hash, '', $cur_hash); +			$i++; +		} +		return ($hash === $this->helper->combine_hash_output($data, 'hash', $password)); +	} +} diff --git a/phpBB/phpbb/path_helper.php b/phpBB/phpbb/path_helper.php index 8cd8808261..a8e12c4063 100644 --- a/phpBB/phpbb/path_helper.php +++ b/phpBB/phpbb/path_helper.php @@ -102,6 +102,27 @@ class path_helper  	}  	/** +	* Strips away the web root path and prepends the normal root path +	* +	* This replaces get_web_root_path() . some_url with +	*	$phpbb_root_path . some_url +	* +	* @param string $path The path to be updated +	* @return string +	*/ +	public function remove_web_root_path($path) +	{ +		if (strpos($path, $this->get_web_root_path()) === 0) +		{ +			$path = substr($path, strlen($this->get_web_root_path())); + +			return $this->phpbb_root_path . $path; +		} + +		return $path; +	} + +	/**  	* Get a relative root path from the current URL  	*  	* @return string @@ -162,4 +183,27 @@ class path_helper  		*/  		return $this->web_root_path = $this->phpbb_root_path . str_repeat('../', $corrections - 1);  	} + +	/** +	* Eliminates useless . and .. components from specified URL +	* +	* @param string $url URL to clean +	* +	* @return string Cleaned URL +	*/ +	public function clean_url($url) +	{ +		$delimiter_position = strpos($url, '://'); +		// URL should contain :// but it shouldn't start with it. +		// Do not clean URLs that do not fit these constraints. +		if (empty($delimiter_position)) +		{ +			return $url; +		} +		$scheme = substr($url, 0, $delimiter_position) . '://'; +		// Add length of URL delimiter to position +		$path = substr($url, $delimiter_position + 3); + +		return $scheme . $this->filesystem->clean_path($path); +	}  } diff --git a/phpBB/phpbb/permissions.php b/phpBB/phpbb/permissions.php index 8319e6d123..a3fddb0b9e 100644 --- a/phpBB/phpbb/permissions.php +++ b/phpBB/phpbb/permissions.php @@ -251,6 +251,7 @@ class permissions  		'f_reply'		=> array('lang' => 'ACL_F_REPLY', 'cat' => 'post'),  		'f_edit'		=> array('lang' => 'ACL_F_EDIT', 'cat' => 'post'),  		'f_delete'		=> array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), +		'f_softdelete'	=> array('lang' => 'ACL_F_SOFTDELETE', 'cat' => 'post'),  		'f_ignoreflood' => array('lang' => 'ACL_F_IGNOREFLOOD', 'cat' => 'post'),  		'f_postcount'	=> array('lang' => 'ACL_F_POSTCOUNT', 'cat' => 'post'),  		'f_noapprove'	=> array('lang' => 'ACL_F_NOAPPROVE', 'cat' => 'post'), diff --git a/phpBB/phpbb/profilefields/lang_helper.php b/phpBB/phpbb/profilefields/lang_helper.php new file mode 100644 index 0000000000..7bae1bdc18 --- /dev/null +++ b/phpBB/phpbb/profilefields/lang_helper.php @@ -0,0 +1,130 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields; + +/** +* Custom Profile Fields +* @package phpBB3 +*/ +class lang_helper +{ +	/** +	* Array with the language option, grouped by field and language +	* @var array +	*/ +	protected $options_lang = array(); + +	/** +	* Database object +	* @var \phpbb\db\driver\driver +	*/ +	protected $db; + +	/** +	* Table where the language strings are stored +	* @var string +	*/ +	protected $language_table; + +	/** +	* Construct +	* +	* @param	\phpbb\db\driver\driver	$db		Database object +	* @param	string		$language_table		Table where the language strings are stored +	*/ +	public function __construct($db, $language_table) +	{ +		$this->db = $db; +		$this->language_table = $language_table; +	} + +	/** +	* Get language entries for options and store them here for later use +	*/ +	public function get_option_lang($field_id, $lang_id, $field_type, $preview_options) +	{ +		if ($preview_options !== false) +		{ +			$lang_options = (!is_array($preview_options)) ? explode("\n", $preview_options) : $preview_options; + +			foreach ($lang_options as $num => $var) +			{ +				if (!isset($this->options_lang[$field_id])) +				{ +					$this->options_lang[$field_id] = array(); +				} +				if (!isset($this->options_lang[$field_id][$lang_id])) +				{ +					$this->options_lang[$field_id][$lang_id] = array(); +				} +				$this->options_lang[$field_id][$lang_id][($num + 1)] = $var; +			} +		} +		else +		{ +			$sql = 'SELECT option_id, lang_value +				FROM ' . $this->language_table . ' +					WHERE field_id = ' . (int) $field_id . ' +					AND lang_id = ' . (int) $lang_id . " +					AND field_type = '" . $this->db->sql_escape($field_type) . "' +				ORDER BY option_id"; +			$result = $this->db->sql_query($sql); + +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$this->options_lang[$field_id][$lang_id][($row['option_id'] + 1)] = $row['lang_value']; +			} +			$this->db->sql_freeresult($result); +		} +	} + +	/** +	* Are language options set for this field? +	* +	* @param	int		$field_id		Database ID of the field +	* @param	int		$lang_id		ID of the language +	* @param	int		$field_value	Selected value of the field +	* @return boolean +	*/ +	public function is_set($field_id, $lang_id = null, $field_value = null) +	{ +		$is_set = isset($this->options_lang[$field_id]); + +		if ($is_set && (!is_null($lang_id) || !is_null($field_value))) +		{ +			$is_set = isset($this->options_lang[$field_id][$lang_id]); +		} + +		if ($is_set && !is_null($field_value)) +		{ +			$is_set = isset($this->options_lang[$field_id][$lang_id][$field_value]); +		} + +		return $is_set; +	} + +	/** +	* Get the selected language string +	* +	* @param	int		$field_id		Database ID of the field +	* @param	int		$lang_id		ID of the language +	* @param	int		$field_value	Selected value of the field +	* @return string +	*/ +	public function get($field_id, $lang_id, $field_value = null) +	{ +		if (is_null($field_value)) +		{ +			return $this->options_lang[$field_id][$lang_id]; +		} + +		return $this->options_lang[$field_id][$lang_id][$field_value]; +	} +} diff --git a/phpBB/phpbb/profilefields/manager.php b/phpBB/phpbb/profilefields/manager.php new file mode 100644 index 0000000000..7564c920c9 --- /dev/null +++ b/phpBB/phpbb/profilefields/manager.php @@ -0,0 +1,375 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields; + +/** +* Custom Profile Fields +* @package phpBB3 +*/ +class manager +{ +	/** +	* Auth object +	* @var \phpbb\auth\auth +	*/ +	protected $auth; + +	/** +	* Database object +	* @var \phpbb\db\driver\driver +	*/ +	protected $db; + +	/** +	* Request object +	* @var \phpbb\request\request +	*/ +	protected $request; + +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* Service Collection object +	* @var \phpbb\di\service_collection +	*/ +	protected $type_collection; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	protected $fields_table; + +	protected $fields_language_table; + +	protected $fields_data_table; + +	protected $profile_cache = array(); + +	/** +	* Construct +	* +	* @param	\phpbb\auth\auth			$auth		Auth object +	* @param	\phpbb\db\driver\driver		$db			Database object +	* @param	\phpbb\request\request		$request	Request object +	* @param	\phpbb\template\template	$template	Template object +	* @param	\phpbb\di\service_collection $type_collection +	* @param	\phpbb\user					$user		User object +	* @param	string				$fields_table +	* @param	string				$fields_language_table +	* @param	string				$fields_data_table +	*/ +	public function __construct(\phpbb\auth\auth $auth, \phpbb\db\driver\driver $db, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\di\service_collection $type_collection, \phpbb\user $user, $fields_table, $fields_language_table, $fields_data_table) +	{ +		$this->auth = $auth; +		$this->db = $db; +		$this->request = $request; +		$this->template = $template; +		$this->type_collection = $type_collection; +		$this->user = $user; + +		$this->fields_table = $fields_table; +		$this->fields_language_table = $fields_language_table; +		$this->fields_data_table = $fields_data_table; +	} + +	/** +	* Assign editable fields to template, mode can be profile (for profile change) or register (for registration) +	* Called by ucp_profile and ucp_register +	*/ +	public function generate_profile_fields($mode, $lang_id) +	{ +		$sql_where = ''; +		switch ($mode) +		{ +			case 'register': +				// If the field is required we show it on the registration page +				$sql_where .= ' AND f.field_show_on_reg = 1'; +			break; + +			case 'profile': +				// Show hidden fields to moderators/admins +				if (!$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_')) +				{ +					$sql_where .= ' AND f.field_show_profile = 1'; +				} +			break; + +			default: +				trigger_error('Wrong profile mode specified', E_USER_ERROR); +			break; +		} + +		$sql = 'SELECT l.*, f.* +			FROM ' . $this->fields_language_table . ' l, ' . $this->fields_table . " f +			WHERE f.field_active = 1 +				$sql_where +				AND l.lang_id = " . (int) $lang_id . ' +				AND l.field_id = f.field_id +			ORDER BY f.field_order'; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			// Return templated field +			$profile_field = $this->type_collection[$row['field_type']]; +			$tpl_snippet = $profile_field->process_field_row('change', $row); + +			$this->template->assign_block_vars('profile_fields', array( +				'LANG_NAME'		=> $this->user->lang($row['lang_name']), +				'LANG_EXPLAIN'	=> $this->user->lang($row['lang_explain']), +				'FIELD'			=> $tpl_snippet, +				'FIELD_ID'		=> $profile_field->get_field_ident($row), +				'S_REQUIRED'	=> ($row['field_required']) ? true : false, +			)); +		} +		$this->db->sql_freeresult($result); +	} + +	/** +	* Build profile cache, used for display +	*/ +	protected function build_cache() +	{ +		$this->profile_cache = array(); + +		// Display hidden/no_view fields for admin/moderator +		$sql = 'SELECT l.*, f.* +			FROM ' . $this->fields_language_table . ' l, ' . $this->fields_table . ' f +			WHERE l.lang_id = ' . $this->user->get_iso_lang_id() . ' +				AND f.field_active = 1 ' . +				((!$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_')) ? '	AND f.field_hide = 0 ' : '') . ' +				AND f.field_no_view = 0 +				AND l.field_id = f.field_id +			ORDER BY f.field_order'; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$this->profile_cache[$row['field_ident']] = $row; +		} +		$this->db->sql_freeresult($result); +	} + +	/** +	* Submit profile field for validation +	*/ +	public function submit_cp_field($mode, $lang_id, &$cp_data, &$cp_error) +	{ +		$sql_where = ''; +		switch ($mode) +		{ +			case 'register': +				// If the field is required we show it on the registration page +				$sql_where .= ' AND f.field_show_on_reg = 1'; +			break; + +			case 'profile': +				// Show hidden fields to moderators/admins +				if (!$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_')) +				{ +					$sql_where .= ' AND f.field_show_profile = 1'; +				} +			break; + +			default: +				trigger_error('Wrong profile mode specified', E_USER_ERROR); +			break; +		} + +		$sql = 'SELECT l.*, f.* +			FROM ' . $this->fields_language_table . ' l, ' . $this->fields_table . ' f +			WHERE l.lang_id = ' . (int) $lang_id . " +				AND f.field_active = 1 +				$sql_where +				AND l.field_id = f.field_id +			ORDER BY f.field_order"; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$profile_field = $this->type_collection[$row['field_type']]; +			$cp_data['pf_' . $row['field_ident']] = $profile_field->get_profile_field($row); +			$check_value = $cp_data['pf_' . $row['field_ident']]; + +			if (($cp_result = $profile_field->validate_profile_field($check_value, $row)) !== false) +			{ +				// If the result is not false, it's an error message +				$cp_error[] = $cp_result; +			} +		} +		$this->db->sql_freeresult($result); +	} + +	/** +	* Update profile field data directly +	*/ +	public function update_profile_field_data($user_id, $cp_data) +	{ +		if (!sizeof($cp_data)) +		{ +			return; +		} + +		$sql = 'UPDATE ' . $this->fields_data_table . ' +			SET ' . $this->db->sql_build_array('UPDATE', $cp_data) . ' +			WHERE user_id = ' . (int) $user_id; +		$this->db->sql_query($sql); + +		if (!$this->db->sql_affectedrows()) +		{ +			$cp_data['user_id'] = (int) $user_id; + +			$this->db->sql_return_on_error(true); + +			$sql = 'INSERT INTO ' . $this->fields_data_table . ' ' . $this->db->sql_build_array('INSERT', $cp_data); +			$this->db->sql_query($sql); + +			$this->db->sql_return_on_error(false); +		} +	} + +	/** +	* Assign fields to template, used for viewprofile, viewtopic and memberlist (if load setting is enabled) +	* This is directly connected to the user -> mode == grab is to grab the user specific fields, mode == show is for assigning the row to the template +	*/ +	public function generate_profile_fields_template($mode, $user_id = 0, $profile_row = false) +	{ +		if ($mode == 'grab') +		{ +			if (!is_array($user_id)) +			{ +				$user_id = array($user_id); +			} + +			if (!sizeof($this->profile_cache)) +			{ +				$this->build_cache(); +			} + +			if (!sizeof($user_id)) +			{ +				return array(); +			} + +			$sql = 'SELECT * +				FROM ' . $this->fields_data_table . ' +				WHERE ' . $this->db->sql_in_set('user_id', array_map('intval', $user_id)); +			$result = $this->db->sql_query($sql); + +			$field_data = array(); +			while ($row = $this->db->sql_fetchrow($result)) +			{ +				$field_data[$row['user_id']] = $row; +			} +			$this->db->sql_freeresult($result); + +			$user_fields = array(); + +			$user_ids = $user_id; + +			// Go through the fields in correct order +			foreach (array_keys($this->profile_cache) as $used_ident) +			{ +				foreach ($field_data as $user_id => $row) +				{ +					$user_fields[$user_id][$used_ident]['value'] = $row['pf_' . $used_ident]; +					$user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident]; +				} + +				foreach ($user_ids as $user_id) +				{ +					if (!isset($user_fields[$user_id][$used_ident]) && $this->profile_cache[$used_ident]['field_show_novalue']) +					{ +						$user_fields[$user_id][$used_ident]['value'] = ''; +						$user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident]; +					} +				} +			} + +			return $user_fields; +		} +		else if ($mode == 'show') +		{ +			// $profile_row == $user_fields[$row['user_id']]; +			$tpl_fields = array(); +			$tpl_fields['row'] = $tpl_fields['blockrow'] = array(); + +			foreach ($profile_row as $ident => $ident_ary) +			{ +				$profile_field = $this->type_collection[$ident_ary['data']['field_type']]; +				$value = $profile_field->get_profile_value($ident_ary['value'], $ident_ary['data']); + +				if ($value === NULL) +				{ +					continue; +				} + +				$tpl_fields['row'] += array( +					'PROFILE_' . strtoupper($ident) . '_VALUE'	=> $value, +					'PROFILE_' . strtoupper($ident) . '_TYPE'	=> $ident_ary['data']['field_type'], +					'PROFILE_' . strtoupper($ident) . '_NAME'	=> $this->user->lang($ident_ary['data']['lang_name']), +					'PROFILE_' . strtoupper($ident) . '_EXPLAIN'=> $this->user->lang($ident_ary['data']['lang_explain']), + +					'S_PROFILE_' . strtoupper($ident)			=> true, +				); + +				$tpl_fields['blockrow'][] = array( +					'PROFILE_FIELD_VALUE'	=> $value, +					'PROFILE_FIELD_TYPE'	=> $ident_ary['data']['field_type'], +					'PROFILE_FIELD_NAME'	=> $this->user->lang($ident_ary['data']['lang_name']), +					'PROFILE_FIELD_EXPLAIN'	=> $this->user->lang($ident_ary['data']['lang_explain']), + +					'S_PROFILE_' . strtoupper($ident)		=> true, +				); +			} + +			return $tpl_fields; +		} +		else +		{ +			trigger_error('Wrong mode for custom profile', E_USER_ERROR); +		} +	} + +	/** +	* Build Array for user insertion into custom profile fields table +	*/ +	public function build_insert_sql_array($cp_data) +	{ +		$sql_not_in = array(); +		foreach ($cp_data as $key => $null) +		{ +			$sql_not_in[] = (strncmp($key, 'pf_', 3) === 0) ? substr($key, 3) : $key; +		} + +		$sql = 'SELECT f.field_type, f.field_ident, f.field_default_value, l.lang_default_value +			FROM ' . $this->fields_language_table . ' l, ' . $this->fields_table . ' f +			WHERE l.lang_id = ' . $this->user->get_iso_lang_id() . ' +				' . ((sizeof($sql_not_in)) ? ' AND ' . $this->db->sql_in_set('f.field_ident', $sql_not_in, true) : '') . ' +				AND l.field_id = f.field_id'; +		$result = $this->db->sql_query($sql); + +		while ($row = $this->db->sql_fetchrow($result)) +		{ +			$profile_field = $this->type_collection[$row['field_type']]; +			$cp_data['pf_' . $row['field_ident']] = $profile_field->get_default_field_value($row); +		} +		$this->db->sql_freeresult($result); + +		return $cp_data; +	} +} diff --git a/phpBB/phpbb/profilefields/type/type_base.php b/phpBB/phpbb/profilefields/type/type_base.php new file mode 100644 index 0000000000..95e9b8768b --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_base.php @@ -0,0 +1,175 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +abstract class type_base implements type_interface +{ +	/** +	* Request object +	* @var \phpbb\request\request +	*/ +	protected $request; + +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Construct +	* +	* @param	\phpbb\request\request		$request	Request object +	* @param	\phpbb\template\template	$template	Template object +	* @param	\phpbb\user					$user		User object +	* @param	string		$language_table		Table where the language strings are stored +	*/ +	public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->request = $request; +		$this->template = $template; +		$this->user = $user; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_name() +	{ +		return $this->user->lang('FIELD_' . strtoupper($this->get_name_short())); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_service_name() +	{ +		return 'profilefields.type.' . $this->get_name_short(); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_template_filename() +	{ +		return 'profilefields/' . $this->get_name_short() . '.html'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_field_ident($field_data) +	{ +		return 'pf_' . $field_data['field_ident']; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_language_options_input($field_data) +	{ +		$field_data['l_lang_name']			= $this->request->variable('l_lang_name', array(0 => ''), true); +		$field_data['l_lang_explain']			= $this->request->variable('l_lang_explain', array(0 => ''), true); +		$field_data['l_lang_default_value']	= $this->request->variable('l_lang_default_value', array(0 => ''), true); +		$field_data['l_lang_options']			= $this->request->variable('l_lang_options', array(0 => ''), true); + +		return $field_data; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function prepare_options_form(&$exclude_options, &$visibility_options) +	{ +		return $this->request->variable('lang_options', '', true); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_options_on_submit($error, $field_data) +	{ +		return $error; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_excluded_options($key, $action, $current_value, &$field_data, $step) +	{ +		if ($step == 3 && ($field_data[$key] || $action != 'edit') && $key == 'l_lang_options' && is_array($field_data[$key])) +		{ +			foreach ($field_data[$key] as $lang_id => $options) +			{ +				$field_data[$key][$lang_id] = explode("\n", $options); +			} + +			return $current_value; +		} + +		return $current_value; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function prepare_hidden_fields($step, $key, $action, &$field_data) +	{ +		if (!$this->request->is_set($key)) +		{ +			// Do not set this variable, we will use the default value +			return null; +		} +		else if ($key == 'field_ident' && isset($field_data[$key])) +		{ +			return $field_data[$key]; +		} +		else +		{ +			return $this->request->variable($key, '', true); +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function display_options(&$template_vars, &$field_data) +	{ +		return; +	} + +	/** +	* Return templated value/field. Possible values for $mode are: +	* change == user is able to set/enter profile values; preview == just show the value +	*/ +	public function process_field_row($mode, $profile_row) +	{ +		$preview_options = ($mode == 'preview') ? $profile_row['lang_options'] : false; + +		// set template filename +		$this->template->set_filenames(array( +			'cp_body'		=> $this->get_template_filename(), +		)); + +		// empty previously filled blockvars +		$this->template->destroy_block_vars($this->get_name_short()); + +		// Assign template variables +		$this->generate_field($profile_row, $preview_options); + +		return $this->template->assign_display('cp_body'); +	} +} diff --git a/phpBB/phpbb/profilefields/type/type_bool.php b/phpBB/phpbb/profilefields/type/type_bool.php new file mode 100644 index 0000000000..47a5ed4992 --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_bool.php @@ -0,0 +1,387 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +class type_bool extends type_base +{ +	/** +	* Profile fields language helper +	* @var \phpbb\profilefields\lang_helper +	*/ +	protected $lang_helper; + +	/** +	* Request object +	* @var \phpbb\request\request +	*/ +	protected $request; + +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Construct +	* +	* @param	\phpbb\profilefields\lang_helper		$lang_helper	Profile fields language helper +	* @param	\phpbb\request\request		$request	Request object +	* @param	\phpbb\template\template	$template	Template object +	* @param	\phpbb\user					$user		User object +	* @param	string		$language_table		Table where the language strings are stored +	*/ +	public function __construct(\phpbb\profilefields\lang_helper $lang_helper, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->lang_helper = $lang_helper; +		$this->request = $request; +		$this->template = $template; +		$this->user = $user; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_name_short() +	{ +		return 'bool'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_options($default_lang_id, $field_data) +	{ +		$profile_row = array( +			'var_name'				=> 'field_default_value', +			'field_id'				=> 1, +			'lang_name'				=> $field_data['lang_name'], +			'lang_explain'			=> $field_data['lang_explain'], +			'lang_id'				=> $default_lang_id, +			'field_default_value'	=> $field_data['field_default_value'], +			'field_ident'			=> 'field_default_value', +			'field_type'			=> $this->get_service_name(), +			'field_length'			=> $field_data['field_length'], +			'lang_options'			=> $field_data['lang_options'], +		); + +		$options = array( +			0 => array('TITLE' => $this->user->lang['FIELD_TYPE'], 'EXPLAIN' => $this->user->lang['BOOL_TYPE_EXPLAIN'], 'FIELD' => '<label><input type="radio" class="radio" name="field_length" value="1"' . (($field_data['field_length'] == 1) ? ' checked="checked"' : '') . ' onchange="document.getElementById(\'add_profile_field\').submit();" /> ' . $this->user->lang['RADIO_BUTTONS'] . '</label><label><input type="radio" class="radio" name="field_length" value="2"' . (($field_data['field_length'] == 2) ? ' checked="checked"' : '') . ' onchange="document.getElementById(\'add_profile_field\').submit();" /> ' . $this->user->lang['CHECKBOX'] . '</label>'), +			1 => array('TITLE' => $this->user->lang['DEFAULT_VALUE'], 'FIELD' => $this->process_field_row('preview', $profile_row)), +		); + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_option_values() +	{ +		return array( +			'field_length'		=> 1, +			'field_minlen'		=> 0, +			'field_maxlen'		=> 0, +			'field_validation'	=> '', +			'field_novalue'		=> 0, +			'field_default_value'	=> 0, +		); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_field_value($field_data) +	{ +		return $field_data['field_default_value']; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_field($profile_row) +	{ +		$var_name = 'pf_' . $profile_row['field_ident']; + +		// Checkbox +		if ($profile_row['field_length'] == 2) +		{ +			return ($this->request->is_set($var_name)) ? 1 : 0; +		} +		else +		{ +			return $this->request->variable($var_name, (int) $profile_row['field_default_value']); +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_profile_field(&$field_value, $field_data) +	{ +		$field_value = (bool) $field_value; + +		if (!$field_value && $field_data['field_required']) +		{ +			return $this->user->lang('FIELD_REQUIRED', $field_data['lang_name']); +		} + +		return false; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_value($field_value, $field_data) +	{ +		$field_id = $field_data['field_id']; +		$lang_id = $field_data['lang_id']; + +		if (!$this->lang_helper->is_set($field_id, $lang_id)) +		{ +			$this->lang_helper->get_option_lang($field_id, $lang_id, FIELD_BOOL, false); +		} + +		if (!$field_value && $field_data['field_show_novalue']) +		{ +			$field_value = $field_data['field_default_value']; +		} + +		if ($field_data['field_length'] == 1) +		{ +			return ($this->lang_helper->is_set($field_id, $lang_id, (int) $field_value)) ? $this->lang_helper->get($field_id, $lang_id, (int) $field_value) : null; +		} +		else if (!$field_value) +		{ +			return null; +		} +		else +		{ +			return $this->lang_helper->is_set($field_id, $lang_id, $field_value + 1); +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function generate_field($profile_row, $preview_options = false) +	{ +		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; +		$field_ident = $profile_row['field_ident']; +		$default_value = $profile_row['field_default_value']; + +		// checkbox - set the value to "true" if it has been set to 1 +		if ($profile_row['field_length'] == 2) +		{ +			$value = ($this->request->is_set($field_ident) && $this->request->variable($field_ident, $default_value) == 1) ? true : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); +		} +		else +		{ +			$value = ($this->request->is_set($field_ident)) ? $this->request->variable($field_ident, $default_value) : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); +		} + +		$profile_row['field_value'] = (int) $value; +		$this->template->assign_block_vars('bool', array_change_key_case($profile_row, CASE_UPPER)); + +		if ($profile_row['field_length'] == 1) +		{ +			if (!$this->lang_helper->is_set($profile_row['field_id'], $profile_row['lang_id'], 1)) +			{ +				$this->lang_helper->get_option_lang($profile_row['field_id'], $profile_row['lang_id'], $this->get_service_name(), $preview_options); +			} + +			$options = $this->lang_helper->get($profile_row['field_id'], $profile_row['lang_id']); +			foreach ($options as $option_id => $option_value) +			{ +				$this->template->assign_block_vars('bool.options', array( +					'OPTION_ID'	=> $option_id, +					'CHECKED'	=> ($value == $option_id) ? ' checked="checked"' : '', +					'VALUE'		=> $option_value, +				)); +			} +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_field_ident($field_data) +	{ +		return ($field_data['field_length'] == '1') ? '' : 'pf_' . $field_data['field_ident']; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_database_column_type() +	{ +		return 'TINT:2'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_language_options($field_data) +	{ +		$options = array( +			'lang_name'		=> 'string', +			'lang_options'	=> 'two_options', +		); + +		if ($field_data['lang_explain']) +		{ +			$options['lang_explain'] = 'text'; +		} + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_language_options_input($field_data) +	{ +		$field_data['l_lang_name']			= $this->request->variable('l_lang_name', array(0 => ''), true); +		$field_data['l_lang_explain']		= $this->request->variable('l_lang_explain', array(0 => ''), true); +		$field_data['l_lang_default_value']	= $this->request->variable('l_lang_default_value', array(0 => ''), true); + +		/** +		* @todo check if this line is correct... +		$field_data['l_lang_default_value']	= $this->request->variable('l_lang_default_value', array(0 => array('')), true); +		*/ +		$field_data['l_lang_options']	= $this->request->variable('l_lang_options', array(0 => array('')), true); + +		return $field_data; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function prepare_options_form(&$exclude_options, &$visibility_options) +	{ +		$exclude_options[1][] = 'lang_options'; + +		return $this->request->variable('lang_options', array(''), true); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_options_on_submit($error, $field_data) +	{ +		if (empty($field_data['lang_options'][0]) || empty($field_data['lang_options'][1])) +		{ +			$error[] = $this->user->lang['NO_FIELD_ENTRIES']; +		} + +		return $error; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_excluded_options($key, $action, $current_value, &$field_data, $step) +	{ +		if ($step == 2 && $key == 'field_default_value') +		{ +			// 'field_length' == 1 defines radio buttons. Possible values are 1 or 2 only. +			// 'field_length' == 2 defines checkbox. Possible values are 0 or 1 only. +			// If we switch the type on step 2, we have to adjust field value. +			// 1 is a common value for the checkbox and radio buttons. + +			// Adjust unchecked checkbox value. +			// If we return or save settings from 2nd/3rd page +			// and the checkbox is unchecked, set the value to 0. +			if ($this->request->is_set('step') && !$this->request->is_set($key)) +			{ +				return 0; +			} + +			// If we switch to the checkbox type but former radio buttons value was 2, +			// which is not the case for the checkbox, set it to 0 (unchecked). +			if ($field_data['field_length'] == 2 && $current_value == 2) +			{ +				return 0; +			} +			// If we switch to the radio buttons but the former checkbox value was 0, +			// which is not the case for the radio buttons, set it to 0. +			else if ($field_data['field_length'] == 1 && $current_value == 0) +			{ +				return 2; +			} +		} + +		if ($step == 3 && ($field_data[$key] || $action != 'edit') && $key == 'l_lang_options') +		{ +			$field_data[$key] = $this->request->variable($key, array(0 => array('')), true); + +			return $current_value; +		} + +		return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function prepare_hidden_fields($step, $key, $action, &$field_data) +	{ +		if ($key == 'l_lang_options' && $this->request->is_set('l_lang_options')) +		{ +			return $this->request->variable($key, array(array('')), true); +		} +		else if ($key == 'field_default_value') +		{ +			return $this->request->variable($key, $field_data[$key]); +		} +		else +		{ +			if (!$this->request->is_set($key)) +			{ +				return false; +			} +			else if ($key == 'field_ident' && isset($field_data[$key])) +			{ +				return $field_data[$key]; +			} +			else +			{ +				return ($key == 'lang_options') ? $this->request->variable($key, array(''), true) : $this->request->variable($key, '', true); +			} +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function display_options(&$template_vars, &$field_data) +	{ +		// Initialize these array elements if we are creating a new field +		if (!sizeof($field_data['lang_options'])) +		{ +			// No options have been defined for a boolean field. +			$field_data['lang_options'][0] = ''; +			$field_data['lang_options'][1] = ''; +		} + +		$template_vars = array_merge($template_vars, array( +			'S_BOOL'					=> true, +			'L_LANG_OPTIONS_EXPLAIN'	=> $this->user->lang['BOOL_ENTRIES_EXPLAIN'], +			'FIRST_LANG_OPTION'			=> $field_data['lang_options'][0], +			'SECOND_LANG_OPTION'		=> $field_data['lang_options'][1], +		)); +	} +} diff --git a/phpBB/phpbb/profilefields/type/type_date.php b/phpBB/phpbb/profilefields/type/type_date.php new file mode 100644 index 0000000000..409c2e7be0 --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_date.php @@ -0,0 +1,358 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +class type_date extends type_base +{ +	/** +	* Request object +	* @var \phpbb\request\request +	*/ +	protected $request; + +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Construct +	* +	* @param	\phpbb\request\request		$request	Request object +	* @param	\phpbb\template\template	$template	Template object +	* @param	\phpbb\user					$user		User object +	* @param	string		$language_table		Table where the language strings are stored +	*/ +	public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->request = $request; +		$this->template = $template; +		$this->user = $user; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_name_short() +	{ +		return 'date'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_options($default_lang_id, $field_data) +	{ +		$profile_row = array( +			'var_name'				=> 'field_default_value', +			'lang_name'				=> $field_data['lang_name'], +			'lang_explain'			=> $field_data['lang_explain'], +			'lang_id'				=> $default_lang_id, +			'field_default_value'	=> $field_data['field_default_value'], +			'field_ident'			=> 'field_default_value', +			'field_type'			=> $this->get_service_name(), +			'field_length'			=> $field_data['field_length'], +		); + +		$always_now = request_var('always_now', -1); +		if ($always_now == -1) +		{ +			$s_checked = ($field_data['field_default_value'] == 'now') ? true : false; +		} +		else +		{ +			$s_checked = ($always_now) ? true : false; +		} + +		$options = array( +			0 => array('TITLE' => $this->user->lang['DEFAULT_VALUE'],	'FIELD' => $this->process_field_row('preview', $profile_row)), +			1 => array('TITLE' => $this->user->lang['ALWAYS_TODAY'],	'FIELD' => '<label><input type="radio" class="radio" name="always_now" value="1"' . (($s_checked) ? ' checked="checked"' : '') . ' onchange="document.getElementById(\'add_profile_field\').submit();" /> ' . $this->user->lang['YES'] . '</label><label><input type="radio" class="radio" name="always_now" value="0"' . ((!$s_checked) ? ' checked="checked"' : '') . ' onchange="document.getElementById(\'add_profile_field\').submit();" /> ' . $this->user->lang['NO'] . '</label>'), +		); + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_option_values() +	{ +		return array( +			'field_length'		=> 10, +			'field_minlen'		=> 10, +			'field_maxlen'		=> 10, +			'field_validation'	=> '', +			'field_novalue'		=> ' 0- 0-   0', +			'field_default_value'	=> ' 0- 0-   0', +		); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_field_value($field_data) +	{ +		if ($field_data['field_default_value'] == 'now') +		{ +			$now = getdate(); +			$field_data['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); +		} + +		return $field_data['field_default_value']; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_field($profile_row) +	{ +		$var_name = 'pf_' . $profile_row['field_ident']; + +		if (!$this->request->is_set($var_name . '_day')) +		{ +			if ($profile_row['field_default_value'] == 'now') +			{ +				$now = getdate(); +				$profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); +			} +			list($day, $month, $year) = explode('-', $profile_row['field_default_value']); +		} +		else +		{ +			$day = $this->request->variable($var_name . '_day', 0); +			$month = $this->request->variable($var_name . '_month', 0); +			$year = $this->request->variable($var_name . '_year', 0); +		} + +		return sprintf('%2d-%2d-%4d', $day, $month, $year); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_profile_field(&$field_value, $field_data) +	{ +		$field_validate = explode('-', $field_value); + +		$day = (isset($field_validate[0])) ? (int) $field_validate[0] : 0; +		$month = (isset($field_validate[1])) ? (int) $field_validate[1] : 0; +		$year = (isset($field_validate[2])) ? (int) $field_validate[2] : 0; + +		if ((!$day || !$month || !$year) && !$field_data['field_required']) +		{ +			return false; +		} + +		if ((!$day || !$month || !$year) && $field_data['field_required']) +		{ +			return $this->user->lang('FIELD_REQUIRED', $field_data['lang_name']); +		} + +		if ($day < 0 || $day > 31 || $month < 0 || $month > 12 || ($year < 1901 && $year > 0) || $year > gmdate('Y', time()) + 50) +		{ +			return $this->user->lang('FIELD_INVALID_DATE', $field_data['lang_name']); +		} + +		if (checkdate($month, $day, $year) === false) +		{ +			return $this->user->lang('FIELD_INVALID_DATE', $field_data['lang_name']); +		} + +		return false; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_value($field_value, $field_data) +	{ +		$date = explode('-', $field_value); +		$day = (isset($date[0])) ? (int) $date[0] : 0; +		$month = (isset($date[1])) ? (int) $date[1] : 0; +		$year = (isset($date[2])) ? (int) $date[2] : 0; + +		if (!$day && !$month && !$year && !$field_data['field_show_novalue']) +		{ +			return null; +		} +		else if ($day && $month && $year) +		{ +			// Date should display as the same date for every user regardless of timezone +			return $this->user->create_datetime() +				->setDate($year, $month, $day) +				->setTime(0, 0, 0) +				->format($this->user->lang['DATE_FORMAT'], true); +		} + +		return $field_value; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function generate_field($profile_row, $preview_options = false) +	{ +		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; +		$field_ident = $profile_row['field_ident']; + +		$now = getdate(); + +		if (!$this->request->is_set($profile_row['field_ident'] . '_day')) +		{ +			if ($profile_row['field_default_value'] == 'now') +			{ +				$profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); +			} +			list($day, $month, $year) = explode('-', ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $profile_row['field_default_value'] : $this->user->profile_fields[$field_ident])); +		} +		else +		{ +			if ($preview_options !== false && $profile_row['field_default_value'] == 'now') +			{ +				$profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); +				list($day, $month, $year) = explode('-', ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $profile_row['field_default_value'] : $this->user->profile_fields[$field_ident])); +			} +			else +			{ +				$day = $this->request->variable($profile_row['field_ident'] . '_day', 0); +				$month = $this->request->variable($profile_row['field_ident'] . '_month', 0); +				$year = $this->request->variable($profile_row['field_ident'] . '_year', 0); +			} +		} + +		$profile_row['s_day_options'] = '<option value="0"' . ((!$day) ? ' selected="selected"' : '') . '>--</option>'; +		for ($i = 1; $i < 32; $i++) +		{ +			$profile_row['s_day_options'] .= '<option value="' . $i . '"' . (($i == $day) ? ' selected="selected"' : '') . ">$i</option>"; +		} + +		$profile_row['s_month_options'] = '<option value="0"' . ((!$month) ? ' selected="selected"' : '') . '>--</option>'; +		for ($i = 1; $i < 13; $i++) +		{ +			$profile_row['s_month_options'] .= '<option value="' . $i . '"' . (($i == $month) ? ' selected="selected"' : '') . ">$i</option>"; +		} + +		$profile_row['s_year_options'] = '<option value="0"' . ((!$year) ? ' selected="selected"' : '') . '>--</option>'; +		for ($i = $now['year'] - 100; $i <= $now['year'] + 100; $i++) +		{ +			$profile_row['s_year_options'] .= '<option value="' . $i . '"' . (($i == $year) ? ' selected="selected"' : '') . ">$i</option>"; +		} + +		$profile_row['field_value'] = 0; +		$this->template->assign_block_vars('date', array_change_key_case($profile_row, CASE_UPPER)); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_field_ident($field_data) +	{ +		return ''; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_database_column_type() +	{ +		return 'VCHAR:10'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_language_options($field_data) +	{ +		$options = array( +			'lang_name' => 'string', +		); + +		if ($field_data['lang_explain']) +		{ +			$options['lang_explain'] = 'text'; +		} + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_excluded_options($key, $action, $current_value, &$field_data, $step) +	{ +		if ($step == 2 && $key == 'field_default_value') +		{ +			$always_now = $this->request->variable('always_now', -1); + +			if ($always_now == 1 || ($always_now === -1 && $current_value == 'now')) +			{ +				$now = getdate(); + +				$field_data['field_default_value_day'] = $now['mday']; +				$field_data['field_default_value_month'] = $now['mon']; +				$field_data['field_default_value_year'] = $now['year']; +				$current_value = 'now'; +				$this->request->overwrite('field_default_value', $current_value, \phpbb\request\request_interface::POST); +			} +			else +			{ +				if ($this->request->is_set('field_default_value_day')) +				{ +					$field_data['field_default_value_day'] = $this->request->variable('field_default_value_day', 0); +					$field_data['field_default_value_month'] = $this->request->variable('field_default_value_month', 0); +					$field_data['field_default_value_year'] = $this->request->variable('field_default_value_year', 0); +					$current_value = sprintf('%2d-%2d-%4d', $field_data['field_default_value_day'], $field_data['field_default_value_month'], $field_data['field_default_value_year']); +					$this->request->overwrite('field_default_value', $current_value, \phpbb\request\request_interface::POST); +				} +				else +				{ +					list($field_data['field_default_value_day'], $field_data['field_default_value_month'], $field_data['field_default_value_year']) = explode('-', $current_value); +				} +			} + +			return $current_value; +		} + + +		return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function prepare_hidden_fields($step, $key, $action, &$field_data) +	{ +		if ($key == 'field_default_value') +		{ +			$always_now = $this->request->variable('always_now', 0); + +			if ($always_now) +			{ +				return 'now'; +			} +			else if ($this->request->is_set('field_default_value_day')) +			{ +				$field_data['field_default_value_day'] = $this->request->variable('field_default_value_day', 0); +				$field_data['field_default_value_month'] = $this->request->variable('field_default_value_month', 0); +				$field_data['field_default_value_year'] = $this->request->variable('field_default_value_year', 0); +				return sprintf('%2d-%2d-%4d', $field_data['field_default_value_day'], $field_data['field_default_value_month'], $field_data['field_default_value_year']); +			} +		} + +		return parent::prepare_hidden_fields($step, $key, $action, $field_data); +	} +} diff --git a/phpBB/phpbb/profilefields/type/type_dropdown.php b/phpBB/phpbb/profilefields/type/type_dropdown.php new file mode 100644 index 0000000000..9a6545249d --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_dropdown.php @@ -0,0 +1,298 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +class type_dropdown extends type_base +{ +	/** +	* Profile fields language helper +	* @var \phpbb\profilefields\lang_helper +	*/ +	protected $lang_helper; + +	/** +	* Request object +	* @var \phpbb\request\request +	*/ +	protected $request; + +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Construct +	* +	* @param	\phpbb\profilefields\lang_helper		$lang_helper	Profile fields language helper +	* @param	\phpbb\request\request		$request	Request object +	* @param	\phpbb\template\template	$template	Template object +	* @param	\phpbb\user					$user		User object +	* @param	string		$language_table		Table where the language strings are stored +	*/ +	public function __construct(\phpbb\profilefields\lang_helper $lang_helper, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->lang_helper = $lang_helper; +		$this->request = $request; +		$this->template = $template; +		$this->user = $user; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_name_short() +	{ +		return 'dropdown'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_options($default_lang_id, $field_data) +	{ +		$profile_row[0] = array( +			'var_name'				=> 'field_default_value', +			'field_id'				=> 1, +			'lang_name'				=> $field_data['lang_name'], +			'lang_explain'			=> $field_data['lang_explain'], +			'lang_id'				=> $default_lang_id, +			'field_default_value'	=> $field_data['field_default_value'], +			'field_ident'			=> 'field_default_value', +			'field_type'			=> $this->get_service_name(), +			'lang_options'			=> $field_data['lang_options'], +		); + +		$profile_row[1] = $profile_row[0]; +		$profile_row[1]['var_name'] = 'field_novalue'; +		$profile_row[1]['field_ident'] = 'field_novalue'; +		$profile_row[1]['field_default_value']	= $field_data['field_novalue']; + +		$options = array( +			0 => array('TITLE' => $this->user->lang['DEFAULT_VALUE'], 'FIELD' => $this->process_field_row('preview', $profile_row[0])), +			1 => array('TITLE' => $this->user->lang['NO_VALUE_OPTION'], 'EXPLAIN' => $this->user->lang['NO_VALUE_OPTION_EXPLAIN'], 'FIELD' => $this->process_field_row('preview', $profile_row[1])), +		); + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_option_values() +	{ +		return array( +			'field_length'		=> 0, +			'field_minlen'		=> 0, +			'field_maxlen'		=> 5, +			'field_validation'	=> '', +			'field_novalue'		=> 0, +			'field_default_value'	=> 0, +		); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_field_value($field_data) +	{ +		return $field_data['field_default_value']; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_field($profile_row) +	{ +		$var_name = 'pf_' . $profile_row['field_ident']; +		return $this->request->variable($var_name, (int) $profile_row['field_default_value']); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_profile_field(&$field_value, $field_data) +	{ +		$field_value = (int) $field_value; + +		// retrieve option lang data if necessary +		if (!$this->lang_helper->is_set($field_data['field_id'], $field_data['lang_id'], 1)) +		{ +			$this->lang_helper->get_option_lang($field_data['field_id'], $field_data['lang_id'], $this->get_service_name(), false); +		} + +		if (!$this->lang_helper->is_set($field_data['field_id'], $field_data['lang_id'], $field_value)) +		{ +			return $this->user->lang('FIELD_INVALID_VALUE', $field_data['lang_name']); +		} + +		if ($field_value == $field_data['field_novalue'] && $field_data['field_required']) +		{ +			return $this->user->lang('FIELD_REQUIRED', $field_data['lang_name']); +		} + +		return false; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_value($field_value, $field_data) +	{ +		$field_id = $field_data['field_id']; +		$lang_id = $field_data['lang_id']; +		if (!$this->lang_helper->is_set($field_id, $lang_id)) +		{ +			$this->lang_helper->get_option_lang($field_id, $lang_id, $this->get_service_name(), false); +		} + +		if ($field_value == $field_data['field_novalue'] && !$field_data['field_show_novalue']) +		{ +			return null; +		} + +		$field_value = (int) $field_value; + +		// User not having a value assigned +		if (!$this->lang_helper->is_set($field_id, $lang_id, $field_value)) +		{ +			if ($field_data['field_show_novalue']) +			{ +				$field_value = $field_data['field_novalue']; +			} +			else +			{ +				return null; +			} +		} + +		return $this->lang_helper->get($field_id, $lang_id, $field_value); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function generate_field($profile_row, $preview_options = false) +	{ +		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; +		$field_ident = $profile_row['field_ident']; +		$default_value = $profile_row['field_default_value']; + +		$value = ($this->request->is_set($field_ident)) ? $this->request->variable($field_ident, $default_value) : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); + +		if (!$this->lang_helper->is_set($profile_row['field_id'], $profile_row['lang_id'], 1)) +		{ +			$this->lang_helper->get_option_lang($profile_row['field_id'], $profile_row['lang_id'], $this->get_service_name(), $preview_options); +		} + +		$profile_row['field_value'] = (int) $value; +		$this->template->assign_block_vars('dropdown', array_change_key_case($profile_row, CASE_UPPER)); + +		$options = $this->lang_helper->get($profile_row['field_id'], $profile_row['lang_id']); +		foreach ($options as $option_id => $option_value) +		{ +			$this->template->assign_block_vars('dropdown.options', array( +				'OPTION_ID'	=> $option_id, +				'SELECTED'	=> ($value == $option_id) ? ' selected="selected"' : '', +				'VALUE'		=> $option_value, +			)); +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_database_column_type() +	{ +		return 'UINT'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_language_options($field_data) +	{ +		$options = array( +			'lang_name'		=> 'string', +			'lang_options'	=> 'optionfield', +		); + +		if ($field_data['lang_explain']) +		{ +			$options['lang_explain'] = 'text'; +		} + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function prepare_options_form(&$exclude_options, &$visibility_options) +	{ +		$exclude_options[1][] = 'lang_options'; + +		return $this->request->variable('lang_options', '', true); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_options_on_submit($error, $field_data) +	{ +		if (!sizeof($field_data['lang_options'])) +		{ +			$error[] = $this->user->lang['NO_FIELD_ENTRIES']; +		} + +		return $error; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_excluded_options($key, $action, $current_value, &$field_data, $step) +	{ +		if ($step == 2 && $key == 'field_maxlen') +		{ +			// Get the number of options if this key is 'field_maxlen' +			return sizeof(explode("\n", $this->request->variable('lang_options', '', true))); +		} + + +		return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function display_options(&$template_vars, &$field_data) +	{ +		// Initialize these array elements if we are creating a new field +		if (!sizeof($field_data['lang_options'])) +		{ +			// No options have been defined for the dropdown menu +			$field_data['lang_options'] = array(); +		} + +		$template_vars = array_merge($template_vars, array( +			'S_DROPDOWN'				=> true, +			'L_LANG_OPTIONS_EXPLAIN'	=> $this->user->lang['DROPDOWN_ENTRIES_EXPLAIN'], +			'LANG_OPTIONS'				=> implode("\n", $field_data['lang_options']), +		)); +	} +} diff --git a/phpBB/phpbb/profilefields/type/type_int.php b/phpBB/phpbb/profilefields/type/type_int.php new file mode 100644 index 0000000000..6ffc8fbea0 --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_int.php @@ -0,0 +1,234 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +class type_int extends type_base +{ +	/** +	* Request object +	* @var \phpbb\request\request +	*/ +	protected $request; + +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Construct +	* +	* @param	\phpbb\request\request		$request	Request object +	* @param	\phpbb\template\template	$template	Template object +	* @param	\phpbb\user					$user		User object +	* @param	string		$language_table		Table where the language strings are stored +	*/ +	public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->request = $request; +		$this->template = $template; +		$this->user = $user; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_name_short() +	{ +		return 'int'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_options($default_lang_id, $field_data) +	{ +		$options = array( +			0 => array('TITLE' => $this->user->lang['FIELD_LENGTH'],		'FIELD' => '<input type="number" min="0" max="99999" name="field_length" size="5" value="' . $field_data['field_length'] . '" />'), +			1 => array('TITLE' => $this->user->lang['MIN_FIELD_NUMBER'],	'FIELD' => '<input type="number" min="0" max="99999" name="field_minlen" size="5" value="' . $field_data['field_minlen'] . '" />'), +			2 => array('TITLE' => $this->user->lang['MAX_FIELD_NUMBER'],	'FIELD' => '<input type="number" min="0" max="99999" name="field_maxlen" size="5" value="' . $field_data['field_maxlen'] . '" />'), +			3 => array('TITLE' => $this->user->lang['DEFAULT_VALUE'],		'FIELD' => '<input type="post" name="field_default_value" value="' . $field_data['field_default_value'] . '" />'), +		); + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_option_values() +	{ +		return array( +			'field_length'		=> 5, +			'field_minlen'		=> 0, +			'field_maxlen'		=> 100, +			'field_validation'	=> '', +			'field_novalue'		=> 0, +			'field_default_value'	=> 0, +		); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_field_value($field_data) +	{ +		 if ($field_data['field_default_value'] === '') +		{ +			// We cannot insert an empty string into an integer column. +			return null; +		} + +		return $field_data['field_default_value']; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_field($profile_row) +	{ +		$var_name = 'pf_' . $profile_row['field_ident']; +		if ($this->request->is_set($var_name) && $this->request->variable($var_name, '') === '') +		{ +			return null; +		} +		else +		{ +			return $this->request->variable($var_name, (int) $profile_row['field_default_value']); +		} +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_profile_field(&$field_value, $field_data) +	{ +		if (trim($field_value) === '' && !$field_data['field_required']) +		{ +			return false; +		} + +		$field_value = (int) $field_value; + +		if ($field_value < $field_data['field_minlen']) +		{ +			return $this->user->lang('FIELD_TOO_SMALL', (int) $row['field_minlen'], $row['lang_name']); +		} +		else if ($field_value > $field_data['field_maxlen']) +		{ +			return $this->user->lang('FIELD_TOO_LARGE', (int) $row['field_maxlen'], $row['lang_name']); +		} + +		return false; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_value($field_value, $field_data) +	{ +		if ($field_value === '' && !$field_data['field_show_novalue']) +		{ +			return null; +		} +		return (int) $field_value; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function generate_field($profile_row, $preview_options = false) +	{ +		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; +		$field_ident = $profile_row['field_ident']; +		$default_value = $profile_row['field_default_value']; + +		if ($this->request->is_set($field_ident)) +		{ +			$value = ($this->request->variable($field_ident, '') === '') ? null : $this->request->variable($field_ident, $default_value); +		} +		else +		{ +			if ($preview_options === false && array_key_exists($field_ident, $this->user->profile_fields) && is_null($this->user->profile_fields[$field_ident])) +			{ +				$value = null; +			} +			else if (!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) +			{ +				$value = $default_value; +			} +			else +			{ +				$value = $this->user->profile_fields[$field_ident]; +			} +		} + +		$profile_row['field_value'] = (is_null($value) || $value === '') ? '' : (int) $value; + +		$this->template->assign_block_vars('int', array_change_key_case($profile_row, CASE_UPPER)); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_field_ident($field_data) +	{ +		return 'pf_' . $field_data['field_ident']; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_database_column_type() +	{ +		return 'BINT'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_language_options($field_data) +	{ +		$options = array( +			'lang_name' => 'string', +		); + +		if ($field_data['lang_explain']) +		{ +			$options['lang_explain'] = 'text'; +		} + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_excluded_options($key, $action, $current_value, &$field_data, $step) +	{ +		if ($step == 2 && $key == 'field_default_value') +		{ +			// Permit an empty string +			if ($action == 'create' && $this->request->variable('field_default_value', '') === '') +			{ +				return ''; +			} +		} + +		return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); +	} +} diff --git a/phpBB/phpbb/profilefields/type/type_interface.php b/phpBB/phpbb/profilefields/type/type_interface.php new file mode 100644 index 0000000000..7d0cf7662f --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_interface.php @@ -0,0 +1,177 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +interface type_interface +{ +	/** +	* Get the translated name of the type +	* +	* @return string Translated name of the field type +	*/ +	public function get_name(); + +	/** +	* Get the short name of the type, used for error messages and template loops +	* +	* @return string lowercase version of the fields type +	*/ +	public function get_name_short(); + +	/** +	* Get the name of service representing the type +	* +	* @return string lowercase version of the fields type +	*/ +	public function get_service_name(); + +	/** +	* Get dropdown options for second step in ACP +	* +	* @param string	$default_lang_id	ID of the default language +	* @param array	$field_data			Array with data for this field +	* @return array	with the acp options +	*/ +	public function get_options($default_lang_id, $field_data); + +	/** +	* Get default values for the options of this type +	* +	* @return array with values like default field size and more +	*/ +	public function get_default_option_values(); + +	/** +	* Get default value for this type +	* +	* @param array	$field_data			Array with data for this field +	* @return mixed default value for new users when no value is given +	*/ +	public function get_default_field_value($field_data); + +	/** +	* Get profile field value on submit +	* +	* @param array	$profile_row			Array with data for this field +	* @return mixed		Submitted value of the profile field +	*/ +	public function get_profile_field($profile_row); + +	/** +	* Validate entered profile field data +	* +	* @param mixed	$field_value		Field value to validate +	* @param array	$field_data			Array with requirements of the field +	* @return mixed		String with the error message +	*/ +	public function validate_profile_field(&$field_value, $field_data); + +	/** +	* Get Profile Value for display +	* +	* @param mixed	$field_value		Field value as stored in the database +	* @param array	$field_data			Array with requirements of the field +	* @return mixed		Field value to display +	*/ +	public function get_profile_value($field_value, $field_data); + +	/** +	* Generate the input field for display +	* +	* @param array	$profile_row		Array with data for this field +	* @param mixed	$preview_options	When previewing we use different data +	* @return null +	*/ +	public function generate_field($profile_row, $preview_options = false); + +	/** +	* Get the ident of the field +	* +	* Some types are multivalue, we can't give them a field_id +	* as we would not know which to pick. +	* +	* @param array	$field_data		Array with data for this field +	* @return string ident of the field +	*/ +	public function get_field_ident($field_data); + +	/** +	* Get the column type for the database +	* +	* @return string	Returns the database column type +	*/ +	public function get_database_column_type(); + +	/** +	* Get the options we need to display for the language input fields in the ACP +	* +	* @param array	$field_data		Array with data for this field +	* @return array		Returns the language options we need to generate +	*/ +	public function get_language_options($field_data); + +	/** +	* Get the input for the supplied language options +	* +	* @param array	$field_data		Array with data for this field +	* @return array		Returns the language options we need to generate +	*/ +	public function get_language_options_input($field_data); + +	/** +	* Allows exclusion of options in single steps of the creation process +	* +	* @param array	$exclude_options		Array with options that should be excluded in the steps +	* @param array	$visibility_options		Array with options responsible for the fields visibility +	* @return mixed		Returns the provided language options +	*/ +	public function prepare_options_form(&$exclude_options, &$visibility_options); + +	/** +	* Allows exclusion of options in single steps of the creation process +	* +	* @param array	$error					Array with error messages +	* @param array	$field_data		Array with data for this field +	* @return array		Array with error messages +	*/ +	public function validate_options_on_submit($error, $field_data); + +	/** +	* Allows manipulating the intended variables if needed +	* +	* @param string	$key			Name of the option +	* @param string	$action			Currently performed action (create|edit) +	* @param mixed	$current_value	Currently value of the option +	* @param array	$field_data		Array with data for this field +	* @param int	$step			Step on which the option is excluded +	* @return mixed		Final value of the option +	*/ +	public function get_excluded_options($key, $action, $current_value, &$field_data, $step); + +	/** +	* Allows manipulating the intended variables if needed +	* +	* @param string	$key			Name of the option +	* @param int	$step			Step on which the option is hidden +	* @param string	$action			Currently performed action (create|edit) +	* @param array	$field_data		Array with data for this field +	* @return mixed		Final value of the option +	*/ +	public function prepare_hidden_fields($step, $key, $action, &$field_data); + +	/** +	* Allows assigning of additional template variables +	* +	* @param array	$template_vars	Template variables we are going to assign +	* @param array	$field_data		Array with data for this field +	* @return null +	*/ +	public function display_options(&$template_vars, &$field_data); +} diff --git a/phpBB/phpbb/profilefields/type/type_string.php b/phpBB/phpbb/profilefields/type/type_string.php new file mode 100644 index 0000000000..12a14d9b83 --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_string.php @@ -0,0 +1,156 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +class type_string extends type_string_common +{ +	/** +	* Request object +	* @var \phpbb\request\request +	*/ +	protected $request; + +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Construct +	* +	* @param	\phpbb\request\request		$request	Request object +	* @param	\phpbb\template\template	$template	Template object +	* @param	\phpbb\user					$user		User object +	* @param	string		$language_table		Table where the language strings are stored +	*/ +	public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->request = $request; +		$this->template = $template; +		$this->user = $user; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_name_short() +	{ +		return 'string'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_options($default_lang_id, $field_data) +	{ +		$options = array( +			0 => array('TITLE' => $this->user->lang['FIELD_LENGTH'],		'FIELD' => '<input type="number" min="0" name="field_length" size="5" value="' . $field_data['field_length'] . '" />'), +			1 => array('TITLE' => $this->user->lang['MIN_FIELD_CHARS'],	'FIELD' => '<input type="number" min="0" name="field_minlen" size="5" value="' . $field_data['field_minlen'] . '" />'), +			2 => array('TITLE' => $this->user->lang['MAX_FIELD_CHARS'],	'FIELD' => '<input type="number" min="0" size="5" value="' . $field_data['field_maxlen'] . '" />'), +			3 => array('TITLE' => $this->user->lang['FIELD_VALIDATION'],	'FIELD' => '<select name="field_validation">' . $this->validate_options($field_data) . '</select>'), +		); + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_option_values() +	{ +		return array( +			'field_length'		=> 10, +			'field_minlen'		=> 0, +			'field_maxlen'		=> 20, +			'field_validation'	=> '.*', +			'field_novalue'		=> '', +			'field_default_value'	=> '', +		); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_field($profile_row) +	{ +		$var_name = 'pf_' . $profile_row['field_ident']; +		return $this->request->variable($var_name, (string) $profile_row['field_default_value'], true); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_profile_field(&$field_value, $field_data) +	{ +		return $this->validate_string_profile_field('string', $field_value, $field_data); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function generate_field($profile_row, $preview_options = false) +	{ +		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; +		$field_ident = $profile_row['field_ident']; +		$default_value = $profile_row['lang_default_value']; +		$profile_row['field_value'] = ($this->request->is_set($field_ident)) ? $this->request->variable($field_ident, $default_value, true) : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); + +		$this->template->assign_block_vars('string', array_change_key_case($profile_row, CASE_UPPER)); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_database_column_type() +	{ +		return 'VCHAR'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_language_options($field_data) +	{ +		$options = array( +			'lang_name' => 'string', +		); + +		if ($field_data['lang_explain']) +		{ +			$options['lang_explain'] = 'text'; +		} + +		if (strlen($field_data['lang_default_value'])) +		{ +			$options['lang_default_value'] = 'string'; +		} + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function display_options(&$template_vars, &$field_data) +	{ +		$template_vars = array_merge($template_vars, array( +			'S_STRING'					=> true, +			'L_DEFAULT_VALUE_EXPLAIN'	=> $this->user->lang['STRING_DEFAULT_VALUE_EXPLAIN'], +			'LANG_DEFAULT_VALUE'		=> $field_data['lang_default_value'], +		)); +	} +} diff --git a/phpBB/phpbb/profilefields/type/type_string_common.php b/phpBB/phpbb/profilefields/type/type_string_common.php new file mode 100644 index 0000000000..d322099c34 --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_string_common.php @@ -0,0 +1,114 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +abstract class type_string_common extends type_base +{ +	/** +	* Return possible validation options +	*/ +	function validate_options($field_data) +	{ +		$validate_ary = array('CHARS_ANY' => '.*', 'NUMBERS_ONLY' => '[0-9]+', 'ALPHA_ONLY' => '[\w]+', 'ALPHA_SPACERS' => '[\w_\+\. \-\[\]]+'); + +		$validate_options = ''; +		foreach ($validate_ary as $lang => $value) +		{ +			$selected = ($field_data['field_validation'] == $value) ? ' selected="selected"' : ''; +			$validate_options .= '<option value="' . $value . '"' . $selected . '>' . $this->user->lang[$lang] . '</option>'; +		} + +		return $validate_options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_field_value($field_data) +	{ +		return $field_data['lang_default_value']; +	} + +	/** +	* Validate entered profile field data +	* +	* @param string	$field_type			Field type (string or text) +	* @param mixed	$field_value		Field value to validate +	* @param array	$field_data			Array with requirements of the field +	* @return mixed		String with key of the error language string, false otherwise +	*/ +	public function validate_string_profile_field($field_type, &$field_value, $field_data) +	{ +		if (trim($field_value) === '' && !$field_data['field_required']) +		{ +			return false; +		} +		else if (trim($field_value) === '' && $field_data['field_required']) +		{ +			return $this->user->lang('FIELD_REQUIRED', $field_data['lang_name']); +		} + +		if ($field_data['field_minlen'] && utf8_strlen($field_value) < $field_data['field_minlen']) +		{ +			return $this->user->lang('FIELD_TOO_SHORT', (int) $row['field_minlen'], $row['lang_name']); +		} +		else if ($field_data['field_maxlen'] && utf8_strlen($field_value) > $field_data['field_maxlen']) +		{ +			return $this->user->lang('FIELD_TOO_LONG', (int) $row['field_maxlen'], $row['lang_name']); +		} + +		if (!empty($field_data['field_validation']) && $field_data['field_validation'] != '.*') +		{ +			$field_validate = ($field_type != 'text') ? $field_value : bbcode_nl2br($field_value); +			if (!preg_match('#^' . str_replace('\\\\', '\\', $field_data['field_validation']) . '$#i', $field_validate)) +			{ +				switch ($row['field_validation']) +				{ +					case '[0-9]+': +						return $this->user->lang('FIELD_INVALID_CHARS_NUMBERS_ONLY', $row['lang_name']); + +					case '[\w]+': +						return $this->user->lang('FIELD_INVALID_CHARS_ALPHA_ONLY', $row['lang_name']); + +					case '[\w_\+\. \-\[\]]+': +						return $this->user->lang('FIELD_INVALID_CHARS_SPACERS_ONLY', $row['lang_name']); +				} +			} +		} + +		return false; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_value($field_value, $field_data) +	{ +		if (!$field_value && !$field_data['field_show_novalue']) +		{ +			return null; +		} + +		$field_value = make_clickable($field_value); +		$field_value = censor_text($field_value); +		$field_value = bbcode_nl2br($field_value); +		return $field_value; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function prepare_options_form(&$exclude_options, &$visibility_options) +	{ +		$exclude_options[1][] = 'lang_default_value'; + +		return $this->request->variable('lang_options', '', true); +	} +} diff --git a/phpBB/phpbb/profilefields/type/type_text.php b/phpBB/phpbb/profilefields/type/type_text.php new file mode 100644 index 0000000000..660bb20ef8 --- /dev/null +++ b/phpBB/phpbb/profilefields/type/type_text.php @@ -0,0 +1,201 @@ +<?php +/** +* +* @package phpBB +* @copyright (c) 2014 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +namespace phpbb\profilefields\type; + +class type_text extends type_string_common +{ +	/** +	* Request object +	* @var \phpbb\request\request +	*/ +	protected $request; + +	/** +	* Template object +	* @var \phpbb\template\template +	*/ +	protected $template; + +	/** +	* User object +	* @var \phpbb\user +	*/ +	protected $user; + +	/** +	* Construct +	* +	* @param	\phpbb\request\request		$request	Request object +	* @param	\phpbb\template\template	$template	Template object +	* @param	\phpbb\user					$user		User object +	* @param	string		$language_table		Table where the language strings are stored +	*/ +	public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) +	{ +		$this->request = $request; +		$this->template = $template; +		$this->user = $user; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_name_short() +	{ +		return 'text'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_options($default_lang_id, $field_data) +	{ +		$options = array( +			0 => array('TITLE' => $this->user->lang['FIELD_LENGTH'],		'FIELD' => '<input type="number" min="0" max="99999" name="rows" size="5" value="' . $field_data['rows'] . '" /> ' . $this->user->lang['ROWS'] . '</dd><dd><input type="number" min="0" max="99999" name="columns" size="5" value="' . $field_data['columns'] . '" /> ' . $this->user->lang['COLUMNS'] . ' <input type="hidden" name="field_length" value="' . $field_data['field_length'] . '" />'), +			1 => array('TITLE' => $this->user->lang['MIN_FIELD_CHARS'],	'FIELD' => '<input type="number" min="0" max="9999999999" name="field_minlen" size="10" value="' . $field_data['field_minlen'] . '" />'), +			2 => array('TITLE' => $this->user->lang['MAX_FIELD_CHARS'],	'FIELD' => '<input type="number" min="0" max="9999999999" name="field_maxlen" size="10" value="' . $field_data['field_maxlen'] . '" />'), +			3 => array('TITLE' => $this->user->lang['FIELD_VALIDATION'],	'FIELD' => '<select name="field_validation">' . $this->validate_options($field_data) . '</select>'), +		); + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_default_option_values() +	{ +		return array( +			'field_length'		=> '5|80', +			'field_minlen'		=> 0, +			'field_maxlen'		=> 1000, +			'field_validation'	=> '.*', +			'field_novalue'		=> '', +			'field_default_value'	=> '', +		); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_profile_field($profile_row) +	{ +		$var_name = 'pf_' . $profile_row['field_ident']; +		return $this->request->variable($var_name, (string) $profile_row['field_default_value'], true); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function validate_profile_field(&$field_value, $field_data) +	{ +		return $this->validate_string_profile_field('text', $field_value, $field_data); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function generate_field($profile_row, $preview_options = false) +	{ +		$field_length = explode('|', $profile_row['field_length']); +		$profile_row['field_rows'] = $field_length[0]; +		$profile_row['field_cols'] = $field_length[1]; +		$profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; +		$field_ident = $profile_row['field_ident']; +		$default_value = $profile_row['lang_default_value']; + +		$profile_row['field_value'] = ($this->request->is_set($field_ident)) ? $this->request->variable($field_ident, $default_value, true) : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); + +		$this->template->assign_block_vars('text', array_change_key_case($profile_row, CASE_UPPER)); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_database_column_type() +	{ +		return 'MTEXT'; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_language_options($field_data) +	{ +		$options = array( +			'lang_name' => 'string', +		); + +		if ($field_data['lang_explain']) +		{ +			$options['lang_explain'] = 'text'; +		} + +		if (strlen($field_data['lang_default_value'])) +		{ +			$options['lang_default_value'] = 'text'; +		} + +		return $options; +	} + +	/** +	* {@inheritDoc} +	*/ +	public function get_excluded_options($key, $action, $current_value, &$field_data, $step) +	{ +		if ($step == 2 && $key == 'field_length') +		{ +			if ($this->request->is_set('rows')) +			{ +				$field_data['rows'] = $this->request->variable('rows', 0); +				$field_data['columns'] = $this->request->variable('columns', 0); +				$current_value = $field_data['rows'] . '|' . $field_data['columns']; +			} +			else +			{ +				$row_col = explode('|', $current_value); +				$field_data['rows'] = $row_col[0]; +				$field_data['columns'] = $row_col[1]; +			} + +			return $current_value; +		} + +		return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function prepare_hidden_fields($step, $key, $action, &$field_data) +	{ +		if ($key == 'field_length' &&  $this->request->is_set('rows')) +		{ +			$field_data['rows'] = $this->request->variable('rows', 0); +			$field_data['columns'] = $this->request->variable('columns', 0); +			return $field_data['rows'] . '|' . $field_data['columns']; +		} + +		return parent::prepare_hidden_fields($step, $key, $action, $field_data); +	} + +	/** +	* {@inheritDoc} +	*/ +	public function display_options(&$template_vars, &$field_data) +	{ +		$template_vars = array_merge($template_vars, array( +			'S_TEXT'					=> true, +			'L_DEFAULT_VALUE_EXPLAIN'	=> $this->user->lang['TEXT_DEFAULT_VALUE_EXPLAIN'], +			'LANG_DEFAULT_VALUE'		=> $field_data['lang_default_value'], +		)); +	} +} diff --git a/phpBB/phpbb/search/fulltext_mysql.php b/phpBB/phpbb/search/fulltext_mysql.php index cdd2da222f..509b73e26e 100644 --- a/phpBB/phpbb/search/fulltext_mysql.php +++ b/phpBB/phpbb/search/fulltext_mysql.php @@ -216,7 +216,7 @@ class fulltext_mysql extends \phpbb\search\base  		// We limit the number of allowed keywords to minimize load on the database  		if ($this->config['max_num_search_keywords'] && sizeof($this->split_words) > $this->config['max_num_search_keywords'])  		{ -			trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $this->config['max_num_search_keywords'], sizeof($this->split_words))); +			trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', (int) $this->config['max_num_search_keywords'], sizeof($this->split_words)));  		}  		// to allow phrase search, we need to concatenate quoted words diff --git a/phpBB/phpbb/search/fulltext_native.php b/phpBB/phpbb/search/fulltext_native.php index 1b314a24d3..1a89182978 100644 --- a/phpBB/phpbb/search/fulltext_native.php +++ b/phpBB/phpbb/search/fulltext_native.php @@ -277,7 +277,7 @@ class fulltext_native extends \phpbb\search\base  		// We limit the number of allowed keywords to minimize load on the database  		if ($this->config['max_num_search_keywords'] && $num_keywords > $this->config['max_num_search_keywords'])  		{ -			trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', $this->config['max_num_search_keywords'], $num_keywords)); +			trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', (int) $this->config['max_num_search_keywords'], $num_keywords));  		}  		// $keywords input format: each word separated by a space, words in a bracket are not separated diff --git a/phpBB/phpbb/template/twig/environment.php b/phpBB/phpbb/template/twig/environment.php index 24bd55b3c5..aa55f1e011 100644 --- a/phpBB/phpbb/template/twig/environment.php +++ b/phpBB/phpbb/template/twig/environment.php @@ -11,15 +11,15 @@ namespace phpbb\template\twig;  class environment extends \Twig_Environment  { -	/** @var array */ -	protected $phpbb_extensions; -  	/** @var \phpbb\config\config */  	protected $phpbb_config;  	/** @var \phpbb\path_helper */  	protected $phpbb_path_helper; +	/** @var \phpbb\extension\manager */ +	protected $extension_manager; +  	/** @var string */  	protected $phpbb_root_path; @@ -33,18 +33,19 @@ class environment extends \Twig_Environment  	* Constructor  	*  	* @param \phpbb\config\config $phpbb_config -	* @param array $phpbb_extensions Array of enabled extensions (name => path)  	* @param \phpbb\path_helper +	* @param \phpbb\extension\manager  	* @param string $phpbb_root_path  	* @param Twig_LoaderInterface $loader  	* @param array $options Array of options to pass to Twig  	*/ -	public function __construct($phpbb_config, $phpbb_extensions, \phpbb\path_helper $path_helper, \Twig_LoaderInterface $loader = null, $options = array()) +	public function __construct($phpbb_config, \phpbb\path_helper $path_helper, \phpbb\extension\manager $extension_manager = null, \Twig_LoaderInterface $loader = null, $options = array())  	{  		$this->phpbb_config = $phpbb_config; -		$this->phpbb_extensions = $phpbb_extensions;  		$this->phpbb_path_helper = $path_helper; +		$this->extension_manager = $extension_manager; +  		$this->phpbb_root_path = $this->phpbb_path_helper->get_phpbb_root_path();  		$this->web_root_path = $this->phpbb_path_helper->get_web_root_path(); @@ -60,7 +61,7 @@ class environment extends \Twig_Environment  	*/  	public function get_phpbb_extensions()  	{ -		return $this->phpbb_extensions; +		return ($this->extension_manager) ? $this->extension_manager->all_enabled() : array();  	}  	/** diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index ddadcfd89a..83630f5992 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -94,8 +94,8 @@ class twig extends \phpbb\template\base  		$this->twig = new \phpbb\template\twig\environment(  			$this->config, -			($this->extension_manager) ? $this->extension_manager->all_enabled() : array(),  			$this->path_helper, +			$this->extension_manager,  			$loader,  			array(  				'cache'			=> (defined('IN_INSTALL')) ? false : $this->cachepath, diff --git a/phpBB/posting.php b/phpBB/posting.php index 0d2cff40bc..902f1b2175 100644 --- a/phpBB/posting.php +++ b/phpBB/posting.php @@ -255,8 +255,18 @@ if (!$auth->acl_get('f_read', $forum_id))  	{  		trigger_error('USER_CANNOT_READ');  	} +	$message = $user->lang['LOGIN_EXPLAIN_POST']; -	login_box('', $user->lang['LOGIN_EXPLAIN_POST']); +	if ($request->is_ajax()) +	{ +		$json = new phpbb\json_response(); +		$json->send(array( +			'title'		=> $user->lang['INFORMATION'], +			'message'	=> $message, +		)); +	} + +	login_box('', $message);  }  // Permission to do the action asked? @@ -326,8 +336,18 @@ if (!$is_authed)  	{  		trigger_error('USER_CANNOT_' . strtoupper($check_auth));  	} +	$message = $user->lang['LOGIN_EXPLAIN_' . strtoupper($mode)]; -	login_box('', $user->lang['LOGIN_EXPLAIN_' . strtoupper($mode)]); +	if ($request->is_ajax()) +	{ +		$json = new phpbb\json_response(); +		$json->send(array( +			'title'		=> $user->lang['INFORMATION'], +			'message'	=> $message, +		)); +	} + +	login_box('', $message);  }  // Is the user able to post within this forum? @@ -484,7 +504,7 @@ $message_parser->get_submitted_attachment_data($post_data['poster_id']);  if ($post_data['post_attachment'] && !$submit && !$refresh && !$preview && $mode == 'edit')  {  	// Do not change to SELECT * -	$sql = 'SELECT attach_id, is_orphan, attach_comment, real_filename +	$sql = 'SELECT attach_id, is_orphan, attach_comment, real_filename, filesize  		FROM ' . ATTACHMENTS_TABLE . "  		WHERE post_msg_id = $post_id  			AND in_message = 0 @@ -1464,6 +1484,7 @@ $template->assign_vars(array(  	'L_POST_A'					=> $page_title,  	'L_ICON'					=> ($mode == 'reply' || $mode == 'quote' || ($mode == 'edit' && $post_id != $post_data['topic_first_post_id'])) ? $user->lang['POST_ICON'] : $user->lang['TOPIC_ICON'],  	'L_MESSAGE_BODY_EXPLAIN'	=> $user->lang('MESSAGE_BODY_EXPLAIN', (int) $config['max_post_chars']), +	'L_TOO_MANY_ATTACHMENTS'	=> $user->lang('TOO_MANY_ATTACHMENTS', (int) $config['max_attachments']),  	'FORUM_NAME'			=> $post_data['forum_name'],  	'FORUM_DESC'			=> ($post_data['forum_desc']) ? generate_text_for_display($post_data['forum_desc'], $post_data['forum_desc_uid'], $post_data['forum_desc_bitfield'], $post_data['forum_desc_options']) : '', @@ -1487,6 +1508,8 @@ $template->assign_vars(array(  	'U_VIEW_TOPIC'			=> ($mode != 'post') ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id") : '',  	'U_PROGRESS_BAR'		=> append_sid("{$phpbb_root_path}posting.$phpEx", "f=$forum_id&mode=popup"),  	'UA_PROGRESS_BAR'		=> addslashes(append_sid("{$phpbb_root_path}posting.$phpEx", "f=$forum_id&mode=popup")), +	'ATTACH_ORDER'			=> ($config['display_order']) ? 'asc' : 'desc', +	'MAX_ATTACHMENTS'		=> ($auth->acl_get('a_') || $auth->acl_get('m_', $forum_id)) ? 0 : (int) $config['max_attachments'],  	'S_PRIVMSGS'				=> false,  	'S_CLOSE_PROGRESS_WINDOW'	=> (isset($_POST['add_file'])) ? true : false, @@ -1525,8 +1548,9 @@ $template->assign_vars(array(  	'S_BBCODE_QUOTE'		=> $quote_status,  	'S_POST_ACTION'			=> $s_action, -	'S_HIDDEN_FIELDS'		=> $s_hidden_fields) -); +	'S_HIDDEN_FIELDS'		=> $s_hidden_fields, +	'S_ATTACH_DATA'			=> json_encode($message_parser->attachment_data), +));  /**  * This event allows you to modify template variables for the posting screen diff --git a/phpBB/styles/prosilver/template/ajax.js b/phpBB/styles/prosilver/template/ajax.js index 28656d47d3..1d70adc48d 100644 --- a/phpBB/styles/prosilver/template/ajax.js +++ b/phpBB/styles/prosilver/template/ajax.js @@ -106,6 +106,47 @@ phpbb.addAjaxCallback('mark_topics_read', function(res, update_topic_links) {  	phpbb.closeDarkenWrapper(3000);  }); +// This callback will mark all notifications read +phpbb.addAjaxCallback('notification.mark_all_read', function(res) { +	if (typeof res.success !== 'undefined') { +		phpbb.markNotifications($('#notification_list li.bg2'), 0); +		phpbb.closeDarkenWrapper(3000); +	} +}); + +// This callback will mark a notification read +phpbb.addAjaxCallback('notification.mark_read', function(res) { +	if (typeof res.success !== 'undefined') { +		var unreadCount = Number($('#notification_list_button strong').html()) - 1; +		phpbb.markNotifications($(this).parent('li.bg2'), unreadCount); +	} +}); + +/** + * Mark notification popup rows as read. + * + * @param {jQuery} el jQuery object(s) to mark read. + * @param {int} unreadCount The new unread notifications count. + */ +phpbb.markNotifications = function(el, unreadCount) { +	// Remove the unread status. +	el.removeClass('bg2'); +	el.find('a.mark_read').remove(); + +	// Update the notification link to the real URL. +	el.each(function() { +		var link = $(this).find('a'); +		link.attr('href', link.attr('data-real-url')); +	}); + +	// Update the unread count. +	$('#notification_list_button strong').html(unreadCount); +	// Remove the Mark all read link if there are no unread notifications. +	if (!unreadCount) { +		$('#mark_all_notifications').remove(); +	} +}; +  // This callback finds the post from the delete link, and removes it.  phpbb.addAjaxCallback('post_delete', function() {  	var el = $(this), diff --git a/phpBB/styles/prosilver/template/custom_profile_fields.html b/phpBB/styles/prosilver/template/custom_profile_fields.html deleted file mode 100644 index 7de97f64cb..0000000000 --- a/phpBB/styles/prosilver/template/custom_profile_fields.html +++ /dev/null @@ -1,31 +0,0 @@ -<!-- BEGIN dropdown --> -<select name="{dropdown.FIELD_IDENT}" id="{dropdown.FIELD_IDENT}"> -	<!-- BEGIN options --><option value="{dropdown.options.OPTION_ID}"{dropdown.options.SELECTED}>{dropdown.options.VALUE}</option><!-- END options --> -</select> -<!-- END dropdown --> - -<!-- BEGIN text --> -<textarea name="{text.FIELD_IDENT}" id="{text.FIELD_IDENT}" rows="{text.FIELD_ROWS}" cols="{text.FIELD_COLS}" class="inputbox autowidth">{text.FIELD_VALUE}</textarea> -<!-- END text --> - -<!-- BEGIN string --> -<input type="text" class="inputbox autowidth" name="{string.FIELD_IDENT}" id="{string.FIELD_IDENT}" size="{string.FIELD_LENGTH}" maxlength="{string.FIELD_MAXLEN}" value="{string.FIELD_VALUE}" /> -<!-- END string --> - -<!-- BEGIN bool --> -<!-- IF bool.FIELD_LENGTH eq 1 --> -	<!-- BEGIN options --><label for="{bool.FIELD_IDENT}_{bool.options.OPTION_ID}"><input type="radio" class="radio" name="{bool.FIELD_IDENT}" id="{bool.FIELD_IDENT}_{bool.options.OPTION_ID}" value="{bool.options.OPTION_ID}"{bool.options.CHECKED} /> {bool.options.VALUE}</label> <!-- END options --> -<!-- ELSE --> -	<input type="checkbox" class="radio" name="{bool.FIELD_IDENT}" id="{bool.FIELD_IDENT}"<!-- IF bool.FIELD_VALUE --> checked="checked"<!-- ENDIF --> /> -<!-- ENDIF --> -<!-- END bool --> - -<!-- BEGIN int --> -<input type="number" min="{int.FIELD_MINLEN}" max="{int.FIELD_MAXLEN}" class="inputbox autowidth" name="{int.FIELD_IDENT}" id="{int.FIELD_IDENT}" size="{int.FIELD_LENGTH}" value="{int.FIELD_VALUE}" /> -<!-- END int --> - -<!-- BEGIN date --> -<label for="{date.FIELD_IDENT}_day">{L_DAY}{L_COLON} <select name="{date.FIELD_IDENT}_day" id="{date.FIELD_IDENT}_day">{date.S_DAY_OPTIONS}</select></label> -<label for="{date.FIELD_IDENT}_month">{L_MONTH}{L_COLON} <select name="{date.FIELD_IDENT}_month" id="{date.FIELD_IDENT}_month">{date.S_MONTH_OPTIONS}</select></label> -<label for="{date.FIELD_IDENT}_year">{L_YEAR}{L_COLON} <select name="{date.FIELD_IDENT}_year" id="{date.FIELD_IDENT}_year">{date.S_YEAR_OPTIONS}</select></label> -<!-- END date --> diff --git a/phpBB/styles/prosilver/template/forum_fn.js b/phpBB/styles/prosilver/template/forum_fn.js index a45f750a63..495df2871d 100644 --- a/phpBB/styles/prosilver/template/forum_fn.js +++ b/phpBB/styles/prosilver/template/forum_fn.js @@ -747,7 +747,7 @@ function parse_document(container)  	/**  	* Hide empty responsive tables  	*/ -	container.find('table.responsive > tbody').each(function() { +	container.find('table.responsive > tbody').not('.responsive-skip-empty').each(function() {  		var items = $(this).children('tr');  		if (items.length == 0)  		{ diff --git a/phpBB/styles/prosilver/template/index_body.html b/phpBB/styles/prosilver/template/index_body.html index 58a5420db3..50c7640e62 100644 --- a/phpBB/styles/prosilver/template/index_body.html +++ b/phpBB/styles/prosilver/template/index_body.html @@ -31,6 +31,9 @@  		<fieldset class="quick-login">  			<label for="username"><span>{L_USERNAME}{L_COLON}</span> <input type="text" name="username" id="username" size="10" class="inputbox" title="{L_USERNAME}" /></label>  			<label for="password"><span>{L_PASSWORD}{L_COLON}</span> <input type="password" name="password" id="password" size="10" class="inputbox" title="{L_PASSWORD}" /></label> +			<!-- IF U_SEND_PASSWORD --> +				<a href="{U_SEND_PASSWORD}">{L_FORGOT_PASS}</a> +			<!-- ENDIF -->  			<!-- IF S_AUTOLOGIN_ENABLED -->  				<span class="responsive-hide">|</span> <label for="autologin">{L_LOG_ME_IN} <input type="checkbox" name="autologin" id="autologin" /></label>  			<!-- ENDIF --> diff --git a/phpBB/styles/prosilver/template/mcp_header.html b/phpBB/styles/prosilver/template/mcp_header.html index e3efe49943..51f496605e 100644 --- a/phpBB/styles/prosilver/template/mcp_header.html +++ b/phpBB/styles/prosilver/template/mcp_header.html @@ -39,7 +39,7 @@  		</div>  	</div> -	<div id="cp-main" class="mcp-main"> +	<div id="cp-main" class="mcp-main panel-container">  		<!-- IF MESSAGE -->  		<div class="content">  			<h2>{L_MESSAGE}</h2> diff --git a/phpBB/styles/prosilver/template/overall_footer.html b/phpBB/styles/prosilver/template/overall_footer.html index f7d0269edb..662008b7fa 100644 --- a/phpBB/styles/prosilver/template/overall_footer.html +++ b/phpBB/styles/prosilver/template/overall_footer.html @@ -60,8 +60,8 @@  <!-- EVENT overall_footer_after --> -{$SCRIPTS}  <!-- IF S_PLUPLOAD --><!-- INCLUDE plupload.html --><!-- ENDIF --> +{$SCRIPTS}  </body>  </html> diff --git a/phpBB/styles/prosilver/template/overall_header.html b/phpBB/styles/prosilver/template/overall_header.html index e4e1b3443b..a89585d899 100644 --- a/phpBB/styles/prosilver/template/overall_header.html +++ b/phpBB/styles/prosilver/template/overall_header.html @@ -36,7 +36,6 @@  <!-- ENDIF -->  <!-- IF S_PLUPLOAD --> -	<link href="{T_ASSETS_PATH}/plupload/jquery.plupload.queue/css/jquery.plupload.queue.css?assets_version={T_ASSETS_VERSION}" rel="stylesheet" type="text/css" media="screen, projection" />  	<link href="{T_THEME_PATH}/plupload.css?assets_version={T_ASSETS_VERSION}" rel="stylesheet" type="text/css" media="screen, projection" />  <!-- ENDIF --> @@ -107,7 +106,12 @@  						<div class="dropdown-contents">  							<div class="header">  								{L_NOTIFICATIONS} -								<span class="header_settings"><a href="{U_NOTIFICATION_SETTINGS}">{L_SETTINGS}</a></span> +								<span class="header_settings"> +									<a href="{U_NOTIFICATION_SETTINGS}">{L_SETTINGS}</a> +									<!-- IF NOTIFICATIONS_COUNT --> +										<span id="mark_all_notifications"> • <a href="{U_MARK_ALL_NOTIFICATIONS}" data-ajax="notification.mark_all_read">{L_MARK_ALL_READ}</a></span> +									<!-- ENDIF --> +								</span>  							</div>  							<ul> @@ -118,17 +122,18 @@  								<!-- ENDIF -->  								<!-- BEGIN notifications -->  									<li class="<!-- IF notifications.UNREAD --> bg2<!-- ENDIF -->"> -										<!-- IF notifications.URL --><a href="<!-- IF notifications.UNREAD -->{notifications.U_MARK_READ}<!-- ELSE -->{notifications.URL}<!-- ENDIF -->"><!-- ENDIF --> +										<!-- IF notifications.URL --> +											<a href="<!-- IF notifications.UNREAD -->{notifications.U_MARK_READ}" data-real-url="{notifications.URL}<!-- ELSE -->{notifications.URL}<!-- ENDIF -->"> +										<!-- ENDIF -->  											<!-- IF notifications.AVATAR -->{notifications.AVATAR}<!-- ELSE --><img src="{T_THEME_PATH}/images/no_avatar.gif" alt="" /><!-- ENDIF -->  											<div class="notification_text">  												<p>{notifications.FORMATTED_TITLE}</p>  												<p>» {notifications.TIME}</p> - -												<!-- IF not notifications.URL and notifications.U_MARK_READ --> -													<p><a href="{notifications.U_MARK_READ}">{L_MARK_READ}</a></p> -												<!-- ENDIF -->  											</div>  										<!-- IF notifications.URL --></a><!-- ENDIF --> +										<!-- IF notifications.UNREAD --> +											<a href="{notifications.U_MARK_READ}" class="mark_read icon-mark" data-ajax="notification.mark_read" title="{L_MARK_READ}"></a> +										<!-- ENDIF -->  									</li>  					   			<!-- END notifications -->  							</ul> diff --git a/phpBB/styles/prosilver/template/plupload.html b/phpBB/styles/prosilver/template/plupload.html index 290992ddd6..21acae1c44 100644 --- a/phpBB/styles/prosilver/template/plupload.html +++ b/phpBB/styles/prosilver/template/plupload.html @@ -1,5 +1,3 @@ -<script type="text/javascript" src="{T_ASSETS_PATH}/plupload/plupload.full.min.js?assets_version={T_ASSETS_VERSION}"></script> -<script type="text/javascript" src="{T_ASSETS_PATH}/plupload/jquery.plupload.queue/jquery.plupload.queue.min.js?assets_version={T_ASSETS_VERSION}"></script>  <script type="text/javascript">  //<![CDATA[  phpbb.plupload = { @@ -37,7 +35,6 @@ phpbb.plupload = {  		'Status': '{LA_PLUPLOAD_STATUS}',  		'Stop Upload': '{LA_PLUPLOAD_STOP_UPLOAD}',  		'Stop current upload': '{LA_PLUPLOAD_STOP_CURRENT_UPLOAD}', -		'Upload element accepts only %d file(s) at a time. Extra files were stripped.': '{LA_PLUPLOAD_ERR_UPLOAD_LIMIT}',  		"Upload URL might be wrong or doesn't exist.": '{LA_PLUPLOAD_ERR_UPLOAD_URL}',  		'Uploaded %d/%d files': '{LA_PLUPLOAD_UPLOADED}',  		'%d files queued': '{LA_PLUPLOAD_FILES_QUEUED}', @@ -51,14 +48,22 @@ phpbb.plupload = {  		unique_names: true,  		filters: [{FILTERS}],  		{S_RESIZE} -		headers: {'X-PHPBB-USING-PLUPLOAD': '1'}, +		headers: {'X-PHPBB-USING-PLUPLOAD': '1', 'X-Requested-With': 'XMLHttpRequest'},  		file_data_name: 'fileupload',  		multipart_params: {'add_file': '{LA_ADD_FILE}'}, -		img_path: '{T_ASSETS_PATH}/plupload/jquery.plupload.queue/img', -		element_hook: '#attach-panel .inner', -		form_hook: '#postform' -	} -}; +		form_hook: '#postform', +		browse_button: 'add_files', +		drop_element : 'message', +	}, +	lang: { +		ERROR: '{LA_ERROR}', +		TOO_MANY_ATTACHMENTS: '{LA_TOO_MANY_ATTACHMENTS}', +	}, +	order: '{ATTACH_ORDER}', +	maxFiles: {MAX_ATTACHMENTS}, +	data: {S_ATTACH_DATA}, +}  //]]>  </script> -<script type="text/javascript" src="{T_ASSETS_PATH}/javascript/plupload.js"></script> +<!-- INCLUDEJS {T_ASSETS_PATH}/plupload/plupload.full.min.js --> +<!-- INCLUDEJS {T_ASSETS_PATH}/javascript/plupload.js --> diff --git a/phpBB/styles/prosilver/template/posting_attach_body.html b/phpBB/styles/prosilver/template/posting_attach_body.html index fa9dd19c09..8a2004c29b 100644 --- a/phpBB/styles/prosilver/template/posting_attach_body.html +++ b/phpBB/styles/prosilver/template/posting_attach_body.html @@ -1,9 +1,9 @@ -<div class="panel bg3" id="attach-panel"> +<div class="panel bg3 panel-container" id="attach-panel">  	<div class="inner"> -	<p>{L_ADD_ATTACHMENT_EXPLAIN}</p> +	<p>{L_ADD_ATTACHMENT_EXPLAIN} <span class="hidden" id="drag-n-drop-message">{L_PLUPLOAD_DRAG_TEXTAREA}</span></p> -	<fieldset class="fields2"> +	<fieldset class="fields2" id="attach-panel-basic">  	<dl>  		<dt><label for="fileupload">{L_FILENAME}{L_COLON}</label></dt>  		<dd> @@ -17,5 +17,73 @@  	</dl>  	</fieldset> +	<div id="attach-panel-multi"> +		<input type="button" class="button2" value="{L_PLUPLOAD_ADD_FILES}" id="add_files" /> +	</div> + +	<div class="panel<!-- IF not .attach_row --> hidden<!-- ENDIF -->" id="file-list-container"> +		<div class="inner"> +			<table class="table1 zebra-list"> +				<thead> +					<tr> +						<th class="attach-name">{L_PLUPLOAD_FILENAME}</th> +						<th class="attach-comment">{L_FILE_COMMENT}</th> +						<th class="attach-filesize">{L_PLUPLOAD_SIZE}</th> +						<th class="attach-status">{L_PLUPLOAD_STATUS}</th> +					</tr> +				</thead> +				<tbody class="responsive-skip-empty" id="file-list"> +					<tr class="attach-row" id="attach-row-tpl"> +							<td class="attach-name"> +								<dfn style="display: none;">{L_PLUPLOAD_FILENAME}</dfn> +								<span class="file-name"></span> +								<span class="attach-controls"> +									<input type="button" value="{L_PLACE_INLINE}" class="button2 hidden file-inline-bbcode" />  +									<input type="button" value="{L_DELETE_FILE}" class="button2 file-delete" /> +								</span> +								<span class="clear"></span> +							</td> +							<td class="attach-comment"> +								<dfn style="display: none;">{L_FILE_COMMENT}</dfn> +								<textarea rows="1" cols="30" class="inputbox"></textarea> +							</td> +							<td class="attach-filesize"> +								<dfn style="display: none;">{L_PLUPLOAD_SIZE}</dfn> +								<span class="file-size"></span> +							</td> +							<td class="attach-status"> +								<dfn style="display: none;">{L_PLUPLOAD_STATUS}</dfn> +								<span class="file-progress"> +									<span class="file-progress-bar"></span> +								</span> +								<span class="file-status"></span> +							</td> +					</tr> +					<!-- BEGIN attach_row --> +						<tr class="attach-row" data-attach-id="{attach_row.ATTACH_ID}"> +							<td class="attach-name"> +								<span class="file-name"><a href="{attach_row.U_VIEW_ATTACHMENT}">{attach_row.FILENAME}</a></span> +								<span class="attach-controls"> +									<!-- IF S_INLINE_ATTACHMENT_OPTIONS --><input type="button" value="{L_PLACE_INLINE}" class="button2 file-inline-bbcode" />  <!-- ENDIF --> +									<input type="submit" name="delete_file[{attach_row.ASSOC_INDEX}]" value="{L_DELETE_FILE}" class="button2 file-delete" /> +								</span> +								<span class="clear"></span> +							</td> +							<td class="attach-comment"> +								<textarea name="comment_list[{attach_row.ASSOC_INDEX}]" rows="1" cols="30" class="inputbox">{attach_row.FILE_COMMENT}</textarea> +								{attach_row.S_HIDDEN} +							</td> +							<td class="attach-filesize"> +								<span class="file-size">{attach_row.FILESIZE}</span> +							</td> +							<td class="attach-status"> +								<span class="file-status file-uploaded"></span> +							</td> +						</tr> +					<!-- END attach_row --> +				</tbody> +			</table> +		</div> +	</div>  	</div>  </div> diff --git a/phpBB/styles/prosilver/template/posting_editor.html b/phpBB/styles/prosilver/template/posting_editor.html index 5677b92fc3..2ae224bc3a 100644 --- a/phpBB/styles/prosilver/template/posting_editor.html +++ b/phpBB/styles/prosilver/template/posting_editor.html @@ -149,34 +149,6 @@  	</div>  	<!-- ENDIF --> -	<!-- IF S_HAS_ATTACHMENTS --> -		<div class="panel bg2"> -			<div class="inner"> -			<h3>{L_POSTED_ATTACHMENTS}</h3> - -			<fieldset class="fields2"> - -			<!-- BEGIN attach_row --> -			<dl> - -				<dt><label for="comment_list_{attach_row.ASSOC_INDEX}">{L_FILE_COMMENT}{L_COLON}</label></dt> -				<dd><textarea name="comment_list[{attach_row.ASSOC_INDEX}]" id="comment_list_{attach_row.ASSOC_INDEX}" rows="1" cols="35" class="inputbox">{attach_row.FILE_COMMENT}</textarea></dd> -				<dd><a href="{attach_row.U_VIEW_ATTACHMENT}">{attach_row.FILENAME}</a></dd> -				<dd style="margin-top: 5px;"> -					<!-- IF S_INLINE_ATTACHMENT_OPTIONS --><input type="button" value="{L_PLACE_INLINE}" onclick="attach_inline({attach_row.ASSOC_INDEX}, '{attach_row.A_FILENAME}');" class="button2" />  <!-- ENDIF --> -					<input type="submit" name="delete_file[{attach_row.ASSOC_INDEX}]" value="{L_DELETE_FILE}" class="button2" /> -				</dd> -			</dl> -			{attach_row.S_HIDDEN} -				<!-- IF not attach_row.S_LAST_ROW --><hr class="dashed" /><!-- ENDIF --> -			<!-- END attach_row --> - -			</fieldset> - -			</div> -		</div> -	<!-- ENDIF --> -  	<!-- IF not S_SHOW_DRAFTS and not $SIG_EDIT eq 1 -->  	<div class="panel bg2">  		<div class="inner"> @@ -198,7 +170,15 @@  		<div id="tabs" class="sub-panels" data-show-panel="options-panel">  			<ul>  				<li id="options-panel-tab" class="activetab"><a href="#tabs" data-subpanel="options-panel"><span>{L_OPTIONS}</span></a></li> -				<!-- IF S_SHOW_ATTACH_BOX --><li id="attach-panel-tab"><a href="#tabs" data-subpanel="attach-panel"><span>{L_ADD_ATTACHMENT}</span></a></li><!-- ENDIF --> +				<!-- IF S_SHOW_ATTACH_BOX --> +					<li id="attach-panel-tab"> +						<a href="#tabs" data-subpanel="attach-panel"> +							<span> +								{L_ATTACHMENTS} <strong id="file-total-progress"><strong id="file-total-progress-bar"></strong></strong> +							</span> +						</a> +					</li>				 +				<!-- ENDIF -->  				<!-- IF S_SHOW_POLL_BOX || S_POLL_DELETE --><li id="poll-panel-tab"><a href="#tabs" data-subpanel="poll-panel"><span>{L_ADD_POLL}</span></a></li><!-- ENDIF -->  			</ul>  		</div> diff --git a/phpBB/styles/prosilver/template/posting_smilies.html b/phpBB/styles/prosilver/template/posting_smilies.html index cb542c1a8c..c5371b9b6a 100644 --- a/phpBB/styles/prosilver/template/posting_smilies.html +++ b/phpBB/styles/prosilver/template/posting_smilies.html @@ -13,7 +13,7 @@  	<div class="inner">  		<!-- BEGIN smiley -->   			<a href="#" onclick="initInsertions(); insert_text('{smiley.A_SMILEY_CODE}', true, true); return false;"><img src="{smiley.SMILEY_IMG}" width="{smiley.SMILEY_WIDTH}" height="{smiley.SMILEY_HEIGHT}" alt="{smiley.SMILEY_CODE}" title="{smiley.SMILEY_DESC}" /></a>  -		<!-- END smiley	--> +		<!-- END smiley -->  	</div>  </div> diff --git a/phpBB/styles/prosilver/template/profilefields/bool.html b/phpBB/styles/prosilver/template/profilefields/bool.html new file mode 100644 index 0000000000..f1d7ba75f4 --- /dev/null +++ b/phpBB/styles/prosilver/template/profilefields/bool.html @@ -0,0 +1,7 @@ +<!-- BEGIN bool --> +<!-- IF bool.FIELD_LENGTH eq 1 --> +	<!-- BEGIN options --><label for="{bool.FIELD_IDENT}_{bool.options.OPTION_ID}"><input type="radio" class="radio" name="{bool.FIELD_IDENT}" id="{bool.FIELD_IDENT}_{bool.options.OPTION_ID}" value="{bool.options.OPTION_ID}"{bool.options.CHECKED} /> {bool.options.VALUE}</label> <!-- END options --> +<!-- ELSE --> +	<input type="checkbox" class="radio" name="{bool.FIELD_IDENT}" id="{bool.FIELD_IDENT}"<!-- IF bool.FIELD_VALUE --> checked="checked"<!-- ENDIF --> /> +<!-- ENDIF --> +<!-- END bool --> diff --git a/phpBB/styles/prosilver/template/profilefields/date.html b/phpBB/styles/prosilver/template/profilefields/date.html new file mode 100644 index 0000000000..5d5bc04ed6 --- /dev/null +++ b/phpBB/styles/prosilver/template/profilefields/date.html @@ -0,0 +1,5 @@ +<!-- BEGIN date --> +<label for="{date.FIELD_IDENT}_day">{L_DAY}{L_COLON} <select name="{date.FIELD_IDENT}_day" id="{date.FIELD_IDENT}_day">{date.S_DAY_OPTIONS}</select></label> +<label for="{date.FIELD_IDENT}_month">{L_MONTH}{L_COLON} <select name="{date.FIELD_IDENT}_month" id="{date.FIELD_IDENT}_month">{date.S_MONTH_OPTIONS}</select></label> +<label for="{date.FIELD_IDENT}_year">{L_YEAR}{L_COLON} <select name="{date.FIELD_IDENT}_year" id="{date.FIELD_IDENT}_year">{date.S_YEAR_OPTIONS}</select></label> +<!-- END date --> diff --git a/phpBB/styles/prosilver/template/profilefields/dropdown.html b/phpBB/styles/prosilver/template/profilefields/dropdown.html new file mode 100644 index 0000000000..243b7039da --- /dev/null +++ b/phpBB/styles/prosilver/template/profilefields/dropdown.html @@ -0,0 +1,5 @@ +<!-- BEGIN dropdown --> +<select name="{dropdown.FIELD_IDENT}" id="{dropdown.FIELD_IDENT}"> +	<!-- BEGIN options --><option value="{dropdown.options.OPTION_ID}"{dropdown.options.SELECTED}>{dropdown.options.VALUE}</option><!-- END options --> +</select> +<!-- END dropdown --> diff --git a/phpBB/styles/prosilver/template/profilefields/int.html b/phpBB/styles/prosilver/template/profilefields/int.html new file mode 100644 index 0000000000..a6f9a0a49e --- /dev/null +++ b/phpBB/styles/prosilver/template/profilefields/int.html @@ -0,0 +1,3 @@ +<!-- BEGIN int --> +<input type="number" min="{int.FIELD_MINLEN}" max="{int.FIELD_MAXLEN}" class="inputbox autowidth" name="{int.FIELD_IDENT}" id="{int.FIELD_IDENT}" size="{int.FIELD_LENGTH}" value="{int.FIELD_VALUE}" /> +<!-- END int --> diff --git a/phpBB/styles/prosilver/template/profilefields/string.html b/phpBB/styles/prosilver/template/profilefields/string.html new file mode 100644 index 0000000000..cf457d39ad --- /dev/null +++ b/phpBB/styles/prosilver/template/profilefields/string.html @@ -0,0 +1,3 @@ +<!-- BEGIN string --> +<input type="text" class="inputbox autowidth" name="{string.FIELD_IDENT}" id="{string.FIELD_IDENT}" size="{string.FIELD_LENGTH}" maxlength="{string.FIELD_MAXLEN}" value="{string.FIELD_VALUE}" /> +<!-- END string --> diff --git a/phpBB/styles/prosilver/template/profilefields/text.html b/phpBB/styles/prosilver/template/profilefields/text.html new file mode 100644 index 0000000000..f54c63925c --- /dev/null +++ b/phpBB/styles/prosilver/template/profilefields/text.html @@ -0,0 +1,3 @@ +<!-- BEGIN text --> +<textarea name="{text.FIELD_IDENT}" id="{text.FIELD_IDENT}" rows="{text.FIELD_ROWS}" cols="{text.FIELD_COLS}" class="inputbox">{text.FIELD_VALUE}</textarea> +<!-- END text --> diff --git a/phpBB/styles/prosilver/template/ucp_groups_manage.html b/phpBB/styles/prosilver/template/ucp_groups_manage.html index eb431e9f3f..5772a735d3 100644 --- a/phpBB/styles/prosilver/template/ucp_groups_manage.html +++ b/phpBB/styles/prosilver/template/ucp_groups_manage.html @@ -56,7 +56,7 @@  		<dt><label for="group_colour">{L_GROUP_COLOR}{L_COLON}</label><br /><span>{L_GROUP_COLOR_EXPLAIN}</span></dt>  		<dd>  			<input name="group_colour" type="text" id="group_colour" value="{GROUP_COLOUR}" size="6" maxlength="6" class="inputbox narrow" /> -			<span style="background-color: {GROUP_COLOUR};">   </span> +			<span style="background-color: #{GROUP_COLOUR};">   </span>  			[ <a href="#" id="color_palette_toggle">{L_COLOUR_SWATCH}</a> ]  			<div id="color_palette_placeholder" class="hidden" data-orientation="h" data-height="12" data-width="15" data-target="#group_colour"></div>  		</dd> diff --git a/phpBB/styles/prosilver/template/ucp_header.html b/phpBB/styles/prosilver/template/ucp_header.html index c5d58b8eaa..8ce2c54294 100644 --- a/phpBB/styles/prosilver/template/ucp_header.html +++ b/phpBB/styles/prosilver/template/ucp_header.html @@ -98,4 +98,4 @@  	</div> -	<div id="cp-main" class="ucp-main"> +	<div id="cp-main" class="ucp-main panel-container"> diff --git a/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html b/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html index 4a8d3da08a..4d7346fda8 100644 --- a/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html +++ b/phpBB/styles/prosilver/template/ucp_pm_viewmessage_print.html @@ -2,8 +2,6 @@  <html dir="{S_CONTENT_DIRECTION}" lang="{S_USER_LANG}">  <head>  <meta charset="utf-8"> -<meta name="keywords" content="" /> -<meta name="description" content="" />  <meta name="robots" content="noindex" />  {META}  <title>{SITENAME} • {PAGE_TITLE}</title> diff --git a/phpBB/styles/prosilver/template/viewtopic_print.html b/phpBB/styles/prosilver/template/viewtopic_print.html index fd0b2dc3e6..6c4dcfadf1 100644 --- a/phpBB/styles/prosilver/template/viewtopic_print.html +++ b/phpBB/styles/prosilver/template/viewtopic_print.html @@ -2,8 +2,6 @@  <html dir="{S_CONTENT_DIRECTION}" lang="{S_USER_LANG}">  <head>  <meta charset="utf-8"> -<meta name="keywords" content="" /> -<meta name="description" content="" />  <meta name="robots" content="noindex" />  {META}  <title>{SITENAME} • {PAGE_TITLE}</title> @@ -28,7 +26,7 @@  		<!-- BEGIN postrow -->  			<div class="post">  				<h3>{postrow.POST_SUBJECT}</h3> -				<div class="date">{postrow.MINI_POST_IMG}{L_POSTED}{L_COLON} <strong>{postrow.POST_DATE}</strong></div> +				<div class="date">{L_POSTED}{L_COLON} <strong>{postrow.POST_DATE}</strong></div>  				<div class="author">{L_POST_BY_AUTHOR} <strong>{postrow.POST_AUTHOR}</strong></div>  				<div class="content">{postrow.MESSAGE}</div>  			</div> diff --git a/phpBB/styles/prosilver/theme/bidi.css b/phpBB/styles/prosilver/theme/bidi.css index d5e98d6197..57d9a33c12 100644 --- a/phpBB/styles/prosilver/theme/bidi.css +++ b/phpBB/styles/prosilver/theme/bidi.css @@ -90,6 +90,13 @@  	text-align: left;  } +/* Bulletin icons for list items +----------------------------------------*/ +.rtl ul.linklist.bulletin li:before { +	padding-left: 4px; +	padding-right: 0; +} +  /* Dropdown menu  ---------------------------------------- */  .rtl .dropdown-container.topic-tools { @@ -198,8 +205,9 @@  /* Pagination in viewforum for multipage topics */  .rtl .row .pagination { +	background-position: 100% 50%;  	float: left; -	padding: 1px 0 1px 15px; +	padding: 1px 15px 1px 0;  }  .rtl .pagination span { @@ -278,16 +286,6 @@ ul.linklist li.small-icon > a, ul.linklist li.breadcrumbs span:first-child > a {  /**  * content.css  */ -.rtl ul.topiclist dfn { -	/* Labels for post/view counts */ -	position: relative; -	width: 1px; -	height: 1px; -	overflow: hidden; -	display: block; -	left: 0; -} -  .rtl ul.topiclist dt, .rtl li.header dt {  	float: right;  	margin-right: 0; @@ -357,8 +355,8 @@ ul.linklist li.small-icon > a, ul.linklist li.breadcrumbs span:first-child > a {  }  .rtl li.header dd { -	margin-left: 0; -	margin-right: 1px; +	padding-left: 0; +	padding-right: 1px;  }  .rtl dl.icon { @@ -387,15 +385,18 @@ ul.linklist li.small-icon > a, ul.linklist li.breadcrumbs span:first-child > a {  /* Post body styles  ----------------------------------------*/ -.rtl .postbody { +.rtl .postbody, .rtl .postbody h3 {  	float: right;  } +.rtl p.post-notice { +	padding-left: 5px; +	padding-right: 26px; +} +  .rtl p.post-notice:before {  	left: auto;  	right: 0; -	padding-left: 5px; -	padding-right: 26px;  }  /* Topic review panel @@ -600,6 +601,10 @@ ul.linklist li.small-icon > a, ul.linklist li.breadcrumbs span:first-child > a {  	margin-left: 0;  } +.tabs-container h2 { +	float: right; +} +  /* CP tabbed menu  ----------------------------------------*/  .rtl #tabs { @@ -617,7 +622,12 @@ ul.linklist li.small-icon > a, ul.linklist li.breadcrumbs span:first-child > a {  /* Mini tabbed menu used in MCP  ----------------------------------------*/  .rtl #minitabs { -	margin: -20px 0 0 7px; +	margin-right: 0; +	margin-left: 7px; +} + +.rtl .tabs-container #minitabs { +	float: left;  }  .rtl #minitabs li { @@ -712,7 +722,7 @@ ul.linklist li.small-icon > a, ul.linklist li.breadcrumbs span:first-child > a {  }  .rtl fieldset.fields1 dd { -	margin-right: 10em; +	margin-right: 15em;  	margin-left: 0;  	border-right-width: 0;  	border-left-width: 1px; diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css index 68fbcde4f9..1d4c657a14 100644 --- a/phpBB/styles/prosilver/theme/colours.css +++ b/phpBB/styles/prosilver/theme/colours.css @@ -76,8 +76,22 @@ hr {  	color: #000000;  } -.bg1	{ background-color: #ECF3F7; } -.bg2	{ background-color: #e1ebf2; } +.bg1 { +	background-color: #ECF3F7; +} + +table.zebra-list tr:nth-child(odd) td, ul.zebra-list li:nth-child(odd) { +	background-color: #ECF3F7; +} + +.bg2 { +	background-color: #e1ebf2; +} + +table.zebra-list tr:nth-child(even) td, ul.zebra-list li:nth-child(even) { +	background-color: #e1ebf2; +} +  .bg3	{ background-color: #cadceb; }  .ucprowbg { @@ -258,6 +272,11 @@ a:active	{ color: #368AD2; }  	color: #C8E6FF;  } +/* Notification mark read link */ +#notification_list a.mark_read { +	background-color: #FFFFFF; +} +  /* Links for forum/topic lists */  a.forumtitle {  	color: #105289; @@ -716,6 +735,7 @@ a.sendemail {  .icon-notification				{ background-image: url("./images/icon_notification.gif"); }  .icon-pm						{ background-image: url("./images/icon_pm.gif"); }  .icon-download					{ background-image: url("./images/icon_download.gif"); } +.icon-mark						{ background-image: url("./images/icon_mark.gif"); }  /* Profile & navigation icons */  .email-icon, .email-icon a		{ background-image: url("./images/icon_contact_email.gif"); } @@ -797,11 +817,11 @@ Colours and backgrounds for cp.css  /* Main CP box  ----------------------------------------*/ -#cp-main h3, #cp-main hr, #cp-menu hr { +.panel-container h3, .panel-container hr, #cp-menu hr {  	border-color: #A4B3BF;  } -#cp-main .panel li.row { +.panel-container .panel li.row {  	border-bottom-color: #B5C1CB;  	border-top-color: #F9F9F9;  } @@ -810,11 +830,11 @@ ul.cplist {  	border-top-color: #B5C1CB;  } -#cp-main .panel li.header dd, #cp-main .panel li.header dt { +.panel-container .panel li.header dd, .panel-container .panel li.header dt {  	color: #000000;  } -#cp-main table.table1 thead th { +.panel-container table.table1 thead th {  	color: #333333;  	border-bottom-color: #333333;  } @@ -915,11 +935,11 @@ ul.cplist {  /* Preferences pane layout  ----------------------------------------*/ -#cp-main h2 { +.panel-container h2 {  	color: #333333;  } -#cp-main .panel { +.panel-container .panel {  	background-color: #F9F9F9;  } diff --git a/phpBB/styles/prosilver/theme/common.css b/phpBB/styles/prosilver/theme/common.css index 66ee77f60b..288477f52a 100644 --- a/phpBB/styles/prosilver/theme/common.css +++ b/phpBB/styles/prosilver/theme/common.css @@ -1015,6 +1015,7 @@ form > p.post-notice strong {  	list-style-type: none;  	font-size: 0.95em;  	clear: both; +	position: relative;  }  #notification_list ul li:before, #notification_list ul li:after { diff --git a/phpBB/styles/prosilver/theme/cp.css b/phpBB/styles/prosilver/theme/cp.css index 2003fef954..bf235b3fb5 100644 --- a/phpBB/styles/prosilver/theme/cp.css +++ b/phpBB/styles/prosilver/theme/cp.css @@ -20,16 +20,16 @@  	padding: 0;  } -#cp-main .panel p { +.panel-container .panel p {  	font-size: 1.1em;  } -#cp-main .panel ol { +.panel-container .panel ol {  	margin-left: 2em;  	font-size: 1.1em;  } -#cp-main .panel li.row { +.panel-container .panel li.row {  	border-bottom: 1px solid transparent;  	border-top: 1px solid transparent;  } @@ -39,21 +39,21 @@ ul.cplist {  	border-top: 1px solid transparent;  } -#cp-main .panel li.header dd, #cp-main .panel li.header dt { +.panel-container .panel li.header dd, .panel-container .panel li.header dt {  	margin-bottom: 2px;  } -#cp-main table.table1 { +.panel-container table.table1 {  	margin-bottom: 1em;  } -#cp-main table.table1 thead th { +.panel-container table.table1 thead th {  	font-weight: bold;  	border-bottom: 1px solid transparent;  	padding: 5px;  } -#cp-main table.table1 tbody th { +.panel-container table.table1 tbody th {  	font-style: italic;  	background-color: transparent !important;  	border-bottom: none; @@ -102,12 +102,6 @@ ul.cplist {  	margin-bottom: 0px;  } -.tabs-container #minitabs { -	float: right; -	margin-top: 19px; -	max-width: 50%; -} -  .tabs-container:after {  	display: block;  	clear: both; @@ -193,6 +187,12 @@ ul.cplist {  	margin: -20px 7px 0 0;  } +.tabs-container #minitabs { +	float: right; +	margin-top: 19px; +	max-width: 50%; +} +  #minitabs ul {  	margin:0;  	padding: 0; diff --git a/phpBB/styles/prosilver/theme/images/icon_mark.gif b/phpBB/styles/prosilver/theme/images/icon_mark.gif Binary files differnew file mode 100644 index 0000000000..1a33fc3264 --- /dev/null +++ b/phpBB/styles/prosilver/theme/images/icon_mark.gif diff --git a/phpBB/styles/prosilver/theme/links.css b/phpBB/styles/prosilver/theme/links.css index 7e3dc094f0..d43886256d 100644 --- a/phpBB/styles/prosilver/theme/links.css +++ b/phpBB/styles/prosilver/theme/links.css @@ -49,6 +49,29 @@ ul.linklist li.small-icon > a, ul.linklist li.breadcrumbs span:first-child > a {  	padding-left: 17px;  } +/* Notification mark read link */ +#notification_list a.mark_read { +	background-position: center center; +	background-repeat: no-repeat; +	border-radius: 3px 0 0 3px; +	display: none; +	margin-top: -20px; +	position: absolute; +	z-index: 2; +	right: 0; +	top: 50%; +	width: 30px; +	height: 40px; +} + +#notification_list li:hover a.mark_read { +	display: block; +} + +#notification_list a.mark_read:hover { +	width: 40px; +} +  /* Links for forum/topic lists */  a.forumtitle {  	font-family: "Trebuchet MS", Helvetica, Arial, Sans-serif; diff --git a/phpBB/styles/prosilver/theme/plupload.css b/phpBB/styles/prosilver/theme/plupload.css index 16c26822b5..8569eca662 100644 --- a/phpBB/styles/prosilver/theme/plupload.css +++ b/phpBB/styles/prosilver/theme/plupload.css @@ -1,11 +1,76 @@ -.plupload_filelist li.can_delete:hover { -	cursor: pointer; +#attach-panel-multi { +	display: none; +	margin-bottom: 1em;  } -.plupload_filelist li.can_delete:hover a { -	background: url('../../../assets/plupload/jquery.plupload.queue/img/delete.gif'); +#file-list td { +	vertical-align: middle;  } -.plupload_filelist li a.working { -	background: url('../../../assets/plupload/jquery.plupload.queue/img/throbber.gif'); +.attach-name { +	width: 50%; +} + +.attach-comment { +	width: 30%; +} + +.attach-filesize { +	width: 15%; +} + +.attach-status { +	width: 5%; +} + +.attach-filesize, .attach-status { +	text-align: center; +} + +.attach-controls { +	display: inline-block; +	float: right; +} + +#attach-row-tpl, .nojs .file-inline-bbcode { +	display: none; +} + +#file-total-progress { +	height: 2px; +	display: block; +	position: relative; +	margin: 4px -10px -6px -10px; +} + +.file-progress { +	background-color: #CCCCCC; +	display:inline-block; +	height: 8px; +	width: 50px; +} + +.file-progress-bar, #file-total-progress-bar { +	background-color: green; +	display: block; +	height: 100%; +	width: 0; +} + +.file-status.file-working { +	background: url('../../../assets/plupload/img/throbber.gif'); +} + +.file-status.file-uploaded { +	background: url('../../../assets/plupload/img/done.gif'); +} + +.file-status.file-error { +	background: url('../../../assets/plupload/img/error.gif'); +} + +.file-status { +	display: inline-block; +	height: 16px; +	width: 16px;  } diff --git a/phpBB/styles/prosilver/theme/responsive.css b/phpBB/styles/prosilver/theme/responsive.css index 397ff12942..bfa2448296 100644 --- a/phpBB/styles/prosilver/theme/responsive.css +++ b/phpBB/styles/prosilver/theme/responsive.css @@ -499,6 +499,10 @@ fieldset.display-actions {  	margin: 0 25px;  } +.attach-comment dfn { +	width: 100%; +} +  @media only screen and (max-width: 500px), only screen and (max-device-width: 500px)  {  	p.responsive-center { @@ -526,4 +530,9 @@ fieldset.display-actions {  		display: block;  		margin-bottom: 5px;  	} + +	.attach-controls { +		margin-top: 5px; +		width: 100%; +	}  } diff --git a/phpBB/styles/subsilver2/template/index_body.html b/phpBB/styles/subsilver2/template/index_body.html index 763028966f..55a62fcf18 100644 --- a/phpBB/styles/subsilver2/template/index_body.html +++ b/phpBB/styles/subsilver2/template/index_body.html @@ -83,7 +83,7 @@  		<td class="cat"><h4><a href="{U_LOGIN_LOGOUT}">{L_LOGIN_LOGOUT}</a></h4></td>  	</tr>  	<tr> -		<td class="row1" align="center"><span class="genmed">{L_USERNAME}{L_COLON}</span> <input class="post" type="text" name="username" size="10" />  <span class="genmed">{L_PASSWORD}{L_COLON}</span> <input class="post" type="password" name="password" size="10" />  <!-- IF S_AUTOLOGIN_ENABLED --> <span class="gensmall">{L_LOG_ME_IN}</span> <input type="checkbox" class="radio" name="autologin" /><!-- ENDIF -->  <input type="submit" class="btnmain" name="login" value="{L_LOGIN}" /></td> +		<td class="row1" align="center"><span class="genmed">{L_USERNAME}{L_COLON}</span> <input class="post" type="text" name="username" size="10" />  <span class="genmed">{L_PASSWORD}{L_COLON}</span> <input class="post" type="password" name="password" size="10" />  <!-- IF U_SEND_PASSWORD --><a href="{U_SEND_PASSWORD}">{L_FORGOT_PASS}</a>  <!-- ENDIF --> <!-- IF S_AUTOLOGIN_ENABLED --> <span class="gensmall">{L_LOG_ME_IN}</span> <input type="checkbox" class="radio" name="autologin" /><!-- ENDIF -->  <input type="submit" class="btnmain" name="login" value="{L_LOGIN}" /></td>  	</tr>  	</table>  	{S_LOGIN_REDIRECT} diff --git a/phpBB/styles/subsilver2/template/overall_header.html b/phpBB/styles/subsilver2/template/overall_header.html index 16e0f6abaf..9ca989ea8b 100644 --- a/phpBB/styles/subsilver2/template/overall_header.html +++ b/phpBB/styles/subsilver2/template/overall_header.html @@ -198,12 +198,14 @@ function marklist(id, name, state)  				<!-- IF S_BOARD_DISABLED and S_USER_LOGGED_IN -->  <span class="error">{L_BOARD_DISABLED}</span><!-- ENDIF -->  			</td>  			<td class="genmed" align="{S_CONTENT_FLOW_END}"> +				<!-- EVENT overall_header_navigation_prepend -->  				<a href="{U_FAQ}"><img src="{T_THEME_PATH}/images/icon_mini_faq.gif" width="12" height="13" alt="*" /> {L_FAQ}</a>  				<!-- IF S_DISPLAY_SEARCH -->   <a href="{U_SEARCH}"><img src="{T_THEME_PATH}/images/icon_mini_search.gif" width="12" height="13" alt="*" /> {L_SEARCH}</a><!-- ENDIF -->  				<!-- IF not S_IS_BOT -->  					<!-- IF S_DISPLAY_MEMBERLIST -->   <a href="{U_MEMBERLIST}"><img src="{T_THEME_PATH}/images/icon_mini_members.gif" width="12" height="13" alt="*" /> {L_MEMBERLIST}</a><!-- ENDIF -->  					<!-- IF S_USER_LOGGED_IN -->   <a href="{U_PROFILE}"><img src="{T_THEME_PATH}/images/icon_mini_profile.gif" width="12" height="13" alt="*" /> {L_PROFILE}</a><!-- ENDIF -->  				<!-- ENDIF --> +				<!-- EVENT overall_header_navigation_append -->  			</td>  		</tr>  		</table> diff --git a/phpBB/styles/subsilver2/template/ucp_pm_viewmessage_print.html b/phpBB/styles/subsilver2/template/ucp_pm_viewmessage_print.html index fd0244a79d..9b1eb3b230 100644 --- a/phpBB/styles/subsilver2/template/ucp_pm_viewmessage_print.html +++ b/phpBB/styles/subsilver2/template/ucp_pm_viewmessage_print.html @@ -2,6 +2,7 @@  <html dir="{S_CONTENT_DIRECTION}" lang="{S_USER_LANG}">  <head>  <meta charset="utf-8"> +<meta name="robots" content="noindex" />  <title>{SITENAME} :: {PAGE_TITLE}</title>  <style type="text/css"> diff --git a/phpBB/styles/subsilver2/template/viewtopic_print.html b/phpBB/styles/subsilver2/template/viewtopic_print.html index 78fab17d02..b788d699cb 100644 --- a/phpBB/styles/subsilver2/template/viewtopic_print.html +++ b/phpBB/styles/subsilver2/template/viewtopic_print.html @@ -2,6 +2,7 @@  <html dir="{S_CONTENT_DIRECTION}" lang="{S_USER_LANG}">  <head>  <meta charset="utf-8"> +<meta name="robots" content="noindex" />  <title>{SITENAME} :: {PAGE_TITLE}</title>  <style type="text/css"> diff --git a/phpBB/viewforum.php b/phpBB/viewforum.php index 2c7a0a4829..9a229a0770 100644 --- a/phpBB/viewforum.php +++ b/phpBB/viewforum.php @@ -556,20 +556,26 @@ if (sizeof($topic_list))  // If we have some shadow topics, update the rowset to reflect their topic information  if (sizeof($shadow_topic_list))  { -	$sql = 'SELECT * -		FROM ' . TOPICS_TABLE . ' -		WHERE ' . $db->sql_in_set('topic_id', array_keys($shadow_topic_list)); +	// SQL array for obtaining shadow topics +	$sql_array = array( +		'SELECT'	=> 't.*', +		'FROM'		=> array( +			TOPICS_TABLE		=> 't' +		), +		'WHERE'		=> $db->sql_in_set('t.topic_id', array_keys($shadow_topic_list)), +	);  	/**  	* Event to modify the SQL query before the shadowtopic data is retrieved  	*  	* @event core.viewforum_get_shadowtopic_data -	* @var	string	sql		The SQL string to get the data of any shadowtopics +	* @var	array	sql_array		SQL array to get the data of any shadowtopics  	* @since 3.1-A1  	*/ -	$vars = array('sql'); +	$vars = array('sql_array');  	extract($phpbb_dispatcher->trigger_event('core.viewforum_get_shadowtopic_data', compact($vars))); +	$sql = $db->sql_build_query('SELECT', $sql_array);  	$result = $db->sql_query($sql);  	while ($row = $db->sql_fetchrow($result)) diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index 14040176cb..227cfdd029 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1265,11 +1265,7 @@ $db->sql_freeresult($result);  // Load custom profile fields  if ($config['load_cpf_viewtopic'])  { -	if (!class_exists('custom_profile')) -	{ -		include($phpbb_root_path . 'includes/functions_profile_fields.' . $phpEx); -	} -	$cp = new custom_profile(); +	$cp = $phpbb_container->get('profilefields.manager');  	// Grab all profile fields from users in id cache for later use - similar to the poster cache  	$profile_fields_tmp = $cp->generate_profile_fields_template('grab', $id_cache); diff --git a/tests/auth/fixtures/user.xml b/tests/auth/fixtures/user.xml index 34584babbf..6d475930a4 100644 --- a/tests/auth/fixtures/user.xml +++ b/tests/auth/fixtures/user.xml @@ -18,6 +18,21 @@  			<value>1</value>  			<value>foobar</value>  			<value>foobar</value> +			<value>$2y$10$4RmpyVu2y8Yf/lP3.yQBquKvE54TCUuEDEBJYY6FDDFN3LcbCGz9i</value> +			<value>0</value> +			<value>0</value> +			<value>example@example.com</value> +			<value>0</value> +			<value>0</value> +			<value></value> +			<value></value> +			<value></value> +			<value></value> +		</row> +		<row> +			<value>2</value> +			<value>foobar2</value> +			<value>foobar2</value>  			<value>$H$9E45lK6J8nLTSm9oJE5aNCSTFK9wqa/</value>  			<value>0</value>  			<value>0</value> diff --git a/tests/auth/fixtures/user_533.xml b/tests/auth/fixtures/user_533.xml new file mode 100644 index 0000000000..c2a86ea0f9 --- /dev/null +++ b/tests/auth/fixtures/user_533.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<dataset> +	<table name="phpbb_users"> +		<column>user_id</column> +		<column>username</column> +		<column>username_clean</column> +		<column>user_password</column> +		<column>user_passchg</column> +		<column>user_pass_convert</column> +		<column>user_email</column> +		<column>user_type</column> +		<column>user_login_attempts</column> +		<column>user_permissions</column> +		<column>user_sig</column> +		<column>user_occ</column> +		<column>user_interests</column> +		<row> +			<value>1</value> +			<value>foobar</value> +			<value>foobar</value> +			<value>$2a$10$e01Syh9PbJjUkio66eFuUu4FhCE2nRgG7QPc1JACalsPXcIuG2bbi</value> +			<value>0</value> +			<value>0</value> +			<value>example@example.com</value> +			<value>0</value> +			<value>0</value> +			<value></value> +			<value></value> +			<value></value> +			<value></value> +		</row> +		<row> +			<value>2</value> +			<value>foobar2</value> +			<value>foobar2</value> +			<value>$H$9E45lK6J8nLTSm9oJE5aNCSTFK9wqa/</value> +			<value>0</value> +			<value>0</value> +			<value>example@example.com</value> +			<value>0</value> +			<value>0</value> +			<value></value> +			<value></value> +			<value></value> +			<value></value> +		</row> +	</table> +</dataset> diff --git a/tests/auth/provider_apache_test.php b/tests/auth/provider_apache_test.php index d7509a72bf..1231c16a4c 100644 --- a/tests/auth/provider_apache_test.php +++ b/tests/auth/provider_apache_test.php @@ -26,13 +26,40 @@ class phpbb_auth_provider_apache_test extends phpbb_database_test_case  		$config = new \phpbb\config\config(array());  		$this->request = $this->getMock('\phpbb\request\request');  		$this->user = $this->getMock('\phpbb\user'); +		$driver_helper = new \phpbb\passwords\driver\helper($config); +		$passwords_drivers = array( +			'passwords.driver.bcrypt_2y'	=> new \phpbb\passwords\driver\bcrypt_2y($config, $driver_helper), +			'passwords.driver.bcrypt'		=> new \phpbb\passwords\driver\bcrypt($config, $driver_helper), +			'passwords.driver.salted_md5'	=> new \phpbb\passwords\driver\salted_md5($config, $driver_helper), +			'passwords.driver.phpass'		=> new \phpbb\passwords\driver\phpass($config, $driver_helper), +		); + +		$passwords_helper = new \phpbb\passwords\helper; +		// Set up passwords manager +		$passwords_manager = new \phpbb\passwords\manager($config, $passwords_drivers, $passwords_helper, array_keys($passwords_drivers)); + +		if (version_compare(PHP_VERSION, '5.3.7', '<')) +		{ +			$this->password_hash = '$2a$10$e01Syh9PbJjUkio66eFuUu4FhCE2nRgG7QPc1JACalsPXcIuG2bbi'; +		} +		else +		{ +			$this->password_hash = '$2y$10$4RmpyVu2y8Yf/lP3.yQBquKvE54TCUuEDEBJYY6FDDFN3LcbCGz9i'; +		} -		$this->provider = new \phpbb\auth\provider\apache($db, $config, $this->request, $this->user, $phpbb_root_path, $phpEx); +		$this->provider = new \phpbb\auth\provider\apache($db, $config, $passwords_manager, $this->request, $this->user, $phpbb_root_path, $phpEx);  	}  	public function getDataSet()  	{ -		return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/user.xml'); +		if ((version_compare(PHP_VERSION, '5.3.7', '<'))) +		{ +			return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/user_533.xml'); +		} +		else +		{ +			return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/user.xml'); +		}  	}  	/** @@ -79,7 +106,7 @@ class phpbb_auth_provider_apache_test extends phpbb_database_test_case  			'user_row'		=> array(  				'user_id' 				=> '1',  				'username' 				=> 'foobar', -				'user_password'			=> '$H$9E45lK6J8nLTSm9oJE5aNCSTFK9wqa/', +				'user_password'			=> $this->password_hash,  				'user_passchg' 			=> '0',  				'user_email' 			=> 'example@example.com',  				'user_type' 			=> '0', @@ -115,7 +142,7 @@ class phpbb_auth_provider_apache_test extends phpbb_database_test_case  			'user_regdate' => '0',  			'username' => 'foobar',  			'username_clean' => 'foobar', -			'user_password' => '$H$9E45lK6J8nLTSm9oJE5aNCSTFK9wqa/', +			'user_password' => $this->password_hash,  			'user_passchg' => '0',  			'user_pass_convert' => '0',  			'user_email' => 'example@example.com', diff --git a/tests/auth/provider_db_test.php b/tests/auth/provider_db_test.php index 45a893220b..91ffcdc2a7 100644 --- a/tests/auth/provider_db_test.php +++ b/tests/auth/provider_db_test.php @@ -14,7 +14,14 @@ class phpbb_auth_provider_db_test extends phpbb_database_test_case  {  	public function getDataSet()  	{ -		return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/user.xml'); +		if ((version_compare(PHP_VERSION, '5.3.7', '<'))) +		{ +			return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/user_533.xml'); +		} +		else +		{ +			return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/user.xml'); +		}  	}  	public function test_login() @@ -29,7 +36,27 @@ class phpbb_auth_provider_db_test extends phpbb_database_test_case  			));  		$request = $this->getMock('\phpbb\request\request');  		$user = $this->getMock('\phpbb\user'); -		$provider = new \phpbb\auth\provider\db($db, $config, $request, $user, $phpbb_root_path, $phpEx); +		$driver_helper = new \phpbb\passwords\driver\helper($config); +		$passwords_drivers = array( +			'passwords.driver.bcrypt_2y'	=> new \phpbb\passwords\driver\bcrypt_2y($config, $driver_helper), +			'passwords.driver.bcrypt'		=> new \phpbb\passwords\driver\bcrypt($config, $driver_helper), +			'passwords.driver.salted_md5'	=> new \phpbb\passwords\driver\salted_md5($config, $driver_helper), +			'passwords.driver.phpass'		=> new \phpbb\passwords\driver\phpass($config, $driver_helper), +		); + +		$passwords_helper = new \phpbb\passwords\helper; +		// Set up passwords manager +		$passwords_manager = new \phpbb\passwords\manager($config, $passwords_drivers, $passwords_helper, array_keys($passwords_drivers)); + +		$provider = new \phpbb\auth\provider\db($db, $config, $passwords_manager, $request, $user, $phpbb_root_path, $phpEx); +		if (version_compare(PHP_VERSION, '5.3.7', '<')) +		{ +			$password_hash = '$2a$10$e01Syh9PbJjUkio66eFuUu4FhCE2nRgG7QPc1JACalsPXcIuG2bbi'; +		} +		else +		{ +			$password_hash = '$2y$10$4RmpyVu2y8Yf/lP3.yQBquKvE54TCUuEDEBJYY6FDDFN3LcbCGz9i'; +		}  		$expected = array(  			'status'		=> LOGIN_SUCCESS, @@ -37,7 +64,7 @@ class phpbb_auth_provider_db_test extends phpbb_database_test_case  			'user_row'		=> array(  				'user_id' 				=> '1',  				'username' 				=> 'foobar', -				'user_password'			=> '$H$9E45lK6J8nLTSm9oJE5aNCSTFK9wqa/', +				'user_password'			=> $password_hash,  				'user_passchg' 			=> '0',  				'user_pass_convert' 	=> '0',  				'user_email' 			=> 'example@example.com', @@ -47,5 +74,10 @@ class phpbb_auth_provider_db_test extends phpbb_database_test_case  		);  		$this->assertEquals($expected, $provider->login('foobar', 'example')); + +		// Check if convert works +		$login_return = $provider->login('foobar2', 'example'); +		$password_start = (version_compare(PHP_VERSION, '5.3.7', '<')) ? '$2a$10$' : '$2y$10$'; +		$this->assertStringStartsWith($password_start, $login_return['user_row']['user_password']);  	}  } diff --git a/tests/extension/ext/vendor2/foo/acp/a_info.php b/tests/extension/ext/vendor2/foo/acp/a_info.php index 27e67c1556..e1eaa340b7 100644 --- a/tests/extension/ext/vendor2/foo/acp/a_info.php +++ b/tests/extension/ext/vendor2/foo/acp/a_info.php @@ -11,7 +11,7 @@ class a_info  			'title'		=> 'Foobar',  			'version'	=> '3.1.0-dev',  			'modes'		=> array( -				'config'		=> array('title' => 'Config',	'auth' => '', 'cat' => array('ACP_MODS')), +				'config'		=> array('title' => 'Config',	'auth' => 'ext_vendor2/foo', 'cat' => array('ACP_MODS')),  			),  		);  	} diff --git a/tests/extension/modules_test.php b/tests/extension/modules_test.php index 5dcb24c691..c0a136e173 100644 --- a/tests/extension/modules_test.php +++ b/tests/extension/modules_test.php @@ -12,6 +12,7 @@ require_once dirname(__FILE__) . '/ext/vendor2/foo/mcp/a_info.php';  require_once dirname(__FILE__) . '/ext/vendor2/foo/acp/fail_info.php';  require_once dirname(__FILE__) . '/ext/vendor2/bar/acp/a_info.php';  require_once dirname(__FILE__) . '/../../phpBB/includes/acp/acp_modules.php'; +require_once dirname(__FILE__) . '/../../phpBB/includes/functions_module.php';  class phpbb_extension_modules_test extends phpbb_test_case  { @@ -59,7 +60,7 @@ class phpbb_extension_modules_test extends phpbb_test_case  					'title'		=> 'Foobar',  					'version'	=> '3.1.0-dev',  					'modes'		=> array( -						'config'		=> array('title' => 'Config',	'auth' => '', 'cat' => array('ACP_MODS')), +						'config'		=> array('title' => 'Config',	'auth' => 'ext_vendor2/foo', 'cat' => array('ACP_MODS')),  					),  				),  				'acp_foobar' => array( @@ -133,7 +134,7 @@ class phpbb_extension_modules_test extends phpbb_test_case  					'title' => 'Foobar',  					'version' => '3.1.0-dev',  					'modes' => array ( -						'config'		=> array ('title' => 'Config', 'auth' => '', 'cat' => array ('ACP_MODS')), +						'config'		=> array ('title' => 'Config', 'auth' => 'ext_vendor2/foo', 'cat' => array ('ACP_MODS')),  					),  				),  			), $acp_modules); @@ -157,7 +158,7 @@ class phpbb_extension_modules_test extends phpbb_test_case  					'title'		=> 'Foobar',  					'version'	=> '3.1.0-dev',  					'modes'		=> array( -						'config'		=> array('title' => 'Config',	'auth' => '', 'cat' => array('ACP_MODS')), +						'config'		=> array('title' => 'Config',	'auth' => 'ext_vendor2/foo', 'cat' => array('ACP_MODS')),  					),  				),  				'acp_foobar' => array( @@ -191,4 +192,41 @@ class phpbb_extension_modules_test extends phpbb_test_case  				)  			), $acp_modules);  	} + +	public function module_auth_test_data() +	{ +		return array( +			// module_auth, expected result +			array('ext_foo', false), +			array('ext_foo/bar', false), +			array('ext_vendor3/bar', false), +			array('ext_vendor2/foo', true), +		); +	} + +	/** +	* @dataProvider module_auth_test_data +	*/ +	public function test_modules_auth($module_auth, $expected) +	{ +		global $phpbb_extension_manager; + +		$phpbb_extension_manager = $this->extension_manager = new phpbb_mock_extension_manager( +			dirname(__FILE__) . '/', +			array( +				'vendor2/foo' => array( +					'ext_name' => 'vendor2/foo', +					'ext_active' => '1', +					'ext_path' => 'ext/vendor2/foo/', +				), +				'vendor3/bar' => array( +					'ext_name' => 'vendor3/bar', +					'ext_active' => '0', +					'ext_path' => 'ext/vendor3/bar/', +				), +			) +		); + +		$this->assertEquals($expected, p_master::module_auth($module_auth, 0)); +	}  } diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index 37752b8fbb..4725301141 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -111,4 +111,32 @@ class phpbb_functional_extension_controller_test extends phpbb_functional_test_c  		$this->assert_response_html(404);  		$this->assertContains('No route found for "GET /does/not/exist"', $crawler->filter('body')->text());  	} + +	/** +	* Check the output of a controller using the template system +	*/ +	public function test_redirect() +	{ +		$filesystem = new \phpbb\filesystem(); +		$this->phpbb_extension_manager->enable('foo/bar'); +		$crawler = self::request('GET', 'app.php/foo/redirect'); + +		$nodes = $crawler->filter('div')->extract(array('id')); + +		foreach ($nodes as $redirect) +		{ +			if (strpos($redirect, 'redirect_expected') !== 0) +			{ +				continue; +			} + +			$row_num = str_replace('redirect_expected_', '', $redirect); + +			$redirect = $crawler->filter('#redirect_' . $row_num)->text(); +			$redirect = substr($redirect, 0, strpos($redirect, 'sid') - 1); +			$this->assertEquals($crawler->filter('#redirect_expected_' .  $row_num)->text(), $redirect); +		} + +		$this->phpbb_extension_manager->purge('foo/bar'); +	}  } diff --git a/tests/functional/fixtures/ext/foo/bar/config/routing.yml b/tests/functional/fixtures/ext/foo/bar/config/routing.yml index 09a30a8c67..9b1ce3cfd7 100644 --- a/tests/functional/fixtures/ext/foo/bar/config/routing.yml +++ b/tests/functional/fixtures/ext/foo/bar/config/routing.yml @@ -13,3 +13,7 @@ foo_template_controller:  foo_exception_controller:      pattern: /foo/exception      defaults: { _controller: foo_bar.controller:exception } + +foo_redirect_controller: +    pattern: /foo/redirect +    defaults: { _controller: foo_bar.controller:redirect } diff --git a/tests/functional/fixtures/ext/foo/bar/config/services.yml b/tests/functional/fixtures/ext/foo/bar/config/services.yml index 64e1163408..cec69f7807 100644 --- a/tests/functional/fixtures/ext/foo/bar/config/services.yml +++ b/tests/functional/fixtures/ext/foo/bar/config/services.yml @@ -3,7 +3,12 @@ services:          class: foo\bar\controller\controller          arguments:              - @controller.helper +            - @path_helper              - @template +            - @config +            - %core.root_path% +            - %core.php_ext% +      foo_bar.listener.permission:          class: foo\bar\event\permission          tags: @@ -12,4 +17,3 @@ services:          class: foo\bar\event\user_setup          tags:              - { name: event.listener } - diff --git a/tests/functional/fixtures/ext/foo/bar/controller/controller.php b/tests/functional/fixtures/ext/foo/bar/controller/controller.php index 259d548299..558b202948 100644 --- a/tests/functional/fixtures/ext/foo/bar/controller/controller.php +++ b/tests/functional/fixtures/ext/foo/bar/controller/controller.php @@ -7,11 +7,18 @@ use Symfony\Component\HttpFoundation\Response;  class controller  {  	protected $template; +	protected $helper; +	protected $path_helper; +	protected $config; -	public function __construct(\phpbb\controller\helper $helper, \phpbb\template\template $template) +	public function __construct(\phpbb\controller\helper $helper, \phpbb\path_helper $path_helper, \phpbb\template\template $template, \phpbb\config\config $config, $root_path, $php_ext)  	{  		$this->template = $template;  		$this->helper = $helper; +		$this->path_helper = $path_helper; +		$this->config = $config; +		$this->root_path = $root_path; +		$this->php_ext = $php_ext;  	}  	public function handle() @@ -35,4 +42,75 @@ class controller  	{  		throw new \phpbb\controller\exception('Exception thrown from foo/exception route');  	} + +	public function redirect() +	{ +		$url_root = generate_board_url(); + +		$rewrite_prefix = (!empty($this->config['enable_mod_rewrite'])) ? '' : 'app.php/'; + +		$redirects = array( +			array( +				append_sid($this->root_path . 'index.' . $this->php_ext), +				'index.php', +			), +			array( +				append_sid($this->root_path . 'foo/bar/index.' . $this->php_ext), +				'foo/bar/index.php', +			), +			array( +				append_sid($this->root_path . 'tests/index.' . $this->php_ext), +				'tests/index.php', +			), +			array( +				$this->helper->url('index'), +				$rewrite_prefix . 'index', +			), +			array( +				$this->helper->url('tests/index'), +				$rewrite_prefix . 'tests/index', +			), +			array( +				$this->helper->url('tests/../index'), +				$rewrite_prefix . 'index', +			), +			/* +			// helper URLs starting with  ../ are prone to failure. +			// Do not test them right now. +			array( +				$this->helper->url('../index'), +				'../index', +			), +			array( +				$this->helper->url('../../index'), +				'../index', +			), +			array( +				$this->helper->url('../tests/index'), +				$rewrite_prefix . '../tests/index', +			), +			array( +				$this->helper->url('../tests/../index'), +				'../index', +			), +			array( +				$this->helper->url('../../tests/index'), +				'../tests/index', +			), +			*/ +		); + +		foreach ($redirects as $redirect) +		{ +			$this->template->assign_block_vars('redirects', array( +				'URL'		=> redirect($redirect[0], true), +			)); + +			$this->template->assign_block_vars('redirects_expected', array( +				'URL'		=> $this->path_helper->clean_url($url_root . '/' . $redirect[1]), +			)); +		} + +		return $this->helper->render('redirect_body.html'); +	}  } diff --git a/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/redirect_body.html b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/redirect_body.html new file mode 100644 index 0000000000..2b70b0fe59 --- /dev/null +++ b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/redirect_body.html @@ -0,0 +1,8 @@ +<!-- INCLUDE overall_header.html --> +<!-- BEGIN redirects --> +<div id="redirect_{redirects.S_ROW_COUNT}">{redirects.URL}</div> +<!-- END redirects --> +<!-- BEGIN redirects_expected --> +<div id="redirect_expected_{redirects_expected.S_ROW_COUNT}">{redirects_expected.URL}</div> +<!-- END redirects_expected --> +<!-- INCLUDE overall_footer.html --> diff --git a/tests/functional/forum_password_test.php b/tests/functional/forum_password_test.php new file mode 100644 index 0000000000..40a8059ad1 --- /dev/null +++ b/tests/functional/forum_password_test.php @@ -0,0 +1,55 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @group functional +*/ +class phpbb_functional_forum_password_test extends phpbb_functional_test_case +{ +	public function test_setup_forum_with_password() +	{ +		$this->login(); +		$this->admin_login(); + +		$crawler = self::request('GET', "adm/index.php?i=acp_forums&mode=manage&sid={$this->sid}"); +		$form = $crawler->selectButton('addforum')->form(array( +			'forum_name'	=> 'Password protected', +		)); +		$crawler = self::submit($form); +		$form = $crawler->selectButton('update')->form(array( +			'forum_perm_from'		=> 2, +			'forum_password'		=> 'foobar', +			'forum_password_confirm'	=> 'foobar', +		)); +		$crawler = self::submit($form); +	} + +	public function data_enter_forum_with_password() +	{ +		return array( +			array('foowrong', 'WRONG_PASSWORD'), +			array('foobar', 'NO_TOPICS'), +		); +	} + +	/** +	* @dataProvider data_enter_forum_with_password +	*/ +	public function test_enter_forum_with_password($password, $message) +	{ +		$crawler = self::request('GET', "index.php?sid={$this->sid}"); +		preg_match('/.?f=([0-9])/', $crawler->selectLink('Password protected')->link()->getUri(), $match); +		$crawler = self::request('GET', "viewforum.php?f={$match[1]}&sid={$this->sid}"); +		$form = $crawler->selectButton('login')->form(array( +			'password'	=> $password, +		)); +		$crawler = self::submit($form); +		$this->assertContainsLang($message, $crawler->text()); +	} +} diff --git a/tests/functional/plupload_test.php b/tests/functional/plupload_test.php index 6dd9224839..a91e70c7bb 100644 --- a/tests/functional/plupload_test.php +++ b/tests/functional/plupload_test.php @@ -107,7 +107,7 @@ class phpbb_functional_plupload_test extends phpbb_functional_test_case  			else  			{  				$response = json_decode(self::$client->getResponse()->getContent(), true); -				$this->assertEquals('valid.jpg', $response[0]['real_filename']); +				$this->assertEquals('valid.jpg', $response['data'][0]['real_filename']);  			}  			unlink($this->path . 'chunk'); @@ -144,6 +144,6 @@ class phpbb_functional_plupload_test extends phpbb_functional_test_case  		);  		$response = json_decode(self::$client->getResponse()->getContent(), true); -		$this->assertEquals('valid.jpg', $response[0]['real_filename']); +		$this->assertEquals('valid.jpg', $response['data'][0]['real_filename']);  	}  } diff --git a/tests/lint_test.php b/tests/lint_test.php index eba117839b..b0149063bd 100644 --- a/tests/lint_test.php +++ b/tests/lint_test.php @@ -9,17 +9,30 @@  class phpbb_lint_test extends phpbb_test_case  { +	static protected $php_binary;  	static protected $exclude;  	static public function setUpBeforeClass()  	{ +		// Try to use PHP_BINARY constant if available so lint tests are run +		// using the same php binary as phpunit. If not available (pre PHP +		// 5.4), assume binary is called 'php' and is in PATH. +		self::$php_binary = defined('PHP_BINARY') ? escapeshellcmd(PHP_BINARY) : 'php'; +  		$output = array();  		$status = 1; -		exec('(php -v) 2>&1', $output, $status); +		exec(sprintf('(%s --version) 2>&1', self::$php_binary), $output, $status);  		if ($status)  		{  			$output = implode("\n", $output); -			self::markTestSkipped("php is not in PATH or broken: $output"); +			if (self::$php_binary === 'php') +			{ +				self::markTestSkipped(sprintf('php is not in PATH or broken. Output: %s', $output)); +			} +			else +			{ +				self::markTestSkipped(sprintf('Could not run PHP_BINARY %s. Output: %s', self::$php_binary, $output)); +			}  		}  		self::$exclude = array( @@ -65,13 +78,12 @@ class phpbb_lint_test extends phpbb_test_case  			}  			else if (substr($filename, strlen($filename)-4) == '.php')  			{ -				// assume php binary is called php and it is in PATH -				$cmd = '(php -l ' . escapeshellarg($path) . ') 2>&1'; +				$cmd = sprintf('(%s -l %s) 2>&1', self::$php_binary, escapeshellarg($path));  				$output = array();  				$status = 1;  				exec($cmd, $output, $status);  				$output = implode("\n", $output); -				$this->assertEquals(0, $status, "php -l failed for $path:\n$output"); +				$this->assertEquals(0, $status, "PHP lint failed for $path:\n$output");  			}  		}  	} diff --git a/tests/passwords/drivers_test.php b/tests/passwords/drivers_test.php new file mode 100644 index 0000000000..2d26be7da5 --- /dev/null +++ b/tests/passwords/drivers_test.php @@ -0,0 +1,81 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_passwords_helper_test extends PHPUnit_Framework_TestCase +{ +	public function setUp() +	{ +		// Prepare dependencies for drivers +		$config =  new \phpbb\config\config(array()); +		$this->driver_helper = new \phpbb\passwords\driver\helper($config); + +		$this->passwords_drivers = array( +			'passwords.driver.bcrypt_2y'	=> new \phpbb\passwords\driver\bcrypt_2y($config, $this->driver_helper), +			'passwords.driver.bcrypt'		=> new \phpbb\passwords\driver\bcrypt($config, $this->driver_helper), +			'passwords.driver.salted_md5'	=> new \phpbb\passwords\driver\salted_md5($config, $this->driver_helper), +			'passwords.driver.phpass'		=> new \phpbb\passwords\driver\phpass($config, $this->driver_helper), +		); +	} + +	public function data_helper_encode64() +	{ +		return array( +			array('foobar', 6, 'axqPW3aQ'), +			array('foobar', 7, 'axqPW3aQ..'), +			array('foobar', 5, 'axqPW34'), +		); +	} + +	/** +	* @dataProvider data_helper_encode64 +	*/ +	public function test_helper_encode64($input, $length, $output) +	{ +		$return = $this->driver_helper->hash_encode64($input, $length); +		$this->assertEquals($output, $return); +	} + +	public function data_get_random_salt() +	{ +		return array( +			array(24, false), +			array(24, '/dev/foobar'), +		); +	} + +	/** +	* @dataProvider data_get_random_salt +	*/ +	public function test_get_random_salt($length, $rand_seed) +	{ +		$rand_string = (empty($rand_seed)) ? $this->driver_helper->get_random_salt($length) : $this->driver_helper->get_random_salt($length, $rand_seed); +		$start = microtime(true); + +		// Run each test for max. 1 second +		while ((microtime(true) - $start) < 1) +		{ +			$urandom_string = (empty($rand_seed)) ? $this->driver_helper->get_random_salt($length) : $this->driver_helper->get_random_salt($length, $rand_seed); +			$this->assertEquals($length, strlen($urandom_string)); +			$this->assertNotEquals($rand_string, $urandom_string); +		} +	} + +	public function test_get_hash_settings_salted_md5() +	{ +		$settings = $this->passwords_drivers['passwords.driver.salted_md5']->get_hash_settings('$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1'); +		$this->assertEquals(array( +				'count'	=> pow(2, 11), +				'salt'	=> 'isfrtKXW', +				'full'	=> '$H$9isfrtKXW', +			), +			$settings +		); +		$this->assertEquals(false, $this->passwords_drivers['passwords.driver.salted_md5']->get_hash_settings(false)); +	} +} diff --git a/tests/passwords/manager_test.php b/tests/passwords/manager_test.php new file mode 100644 index 0000000000..ee295ff043 --- /dev/null +++ b/tests/passwords/manager_test.php @@ -0,0 +1,295 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_passwords_manager_test extends PHPUnit_Framework_TestCase +{ +	protected $passwords_drivers; + +	protected $pw_characters = '0123456789abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVXYZ.,_!?/\\'; + +	protected $default_pw = 'foobar'; + +	public function setUp() +	{ +		// Prepare dependencies for manager and driver +		$config =  new \phpbb\config\config(array()); +		$this->driver_helper = new \phpbb\passwords\driver\helper($config); + +		$this->passwords_drivers = array( +			'passwords.driver.bcrypt_2y'	=> new \phpbb\passwords\driver\bcrypt_2y($config, $this->driver_helper), +			'passwords.driver.bcrypt'		=> new \phpbb\passwords\driver\bcrypt($config, $this->driver_helper), +			'passwords.driver.salted_md5'	=> new \phpbb\passwords\driver\salted_md5($config, $this->driver_helper), +			'passwords.driver.phpass'		=> new \phpbb\passwords\driver\phpass($config, $this->driver_helper), +		); + +		$this->helper = new \phpbb\passwords\helper; +		// Set up passwords manager +		$this->manager = new \phpbb\passwords\manager($config, $this->passwords_drivers, $this->helper, array_keys($this->passwords_drivers)); +	} + +	public function hash_password_data() +	{ +		if (version_compare(PHP_VERSION, '5.3.7', '<')) +		{ +			return array( +				array('', '2a', 60), +				array('passwords.driver.bcrypt_2y', '2a', 60), +				array('passwords.driver.bcrypt', '2a', 60), +				array('passwords.driver.salted_md5', 'H', 34), +				array('passwords.driver.foobar', '', false), +			); +		} +		else +		{ +			return array( +				array('', '2y', 60), +				array('passwords.driver.bcrypt_2y', '2y', 60), +				array('passwords.driver.bcrypt', '2a', 60), +				array('passwords.driver.salted_md5', 'H', 34), +				array('passwords.driver.foobar', '', false), +			); +		} +	} + +	/** +	* @dataProvider hash_password_data +	*/ +	public function test_hash_password($type, $prefix, $length) +	{ +		$password = $this->default_pw; + +		if (!$length) +		{ +			$this->assertEquals(false, $hash = $this->manager->hash($password, $type)); +			return; +		} +		$time = microtime(true); + +		// Limit each test to 1 second +		while ((microtime(true) - $time) < 1) +		{ +			$hash = $this->manager->hash($password, $type); +			preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $hash, $match); +			$this->assertEquals($prefix, $match[1]); +			$this->assertEquals($length, strlen($hash)); +			$password .= $this->pw_characters[mt_rand(0, 66)]; +		} +	} + +	public function check_password_data() +	{ +		if (version_compare(PHP_VERSION, '5.3.7', '<')) +		{ +			return array( +				array('passwords.driver.bcrypt'), +				array('passwords.driver.salted_md5'), +				array('passwords.driver.phpass'), +			); +		} +		else +		{ +			return array( +				array('passwords.driver.bcrypt_2y'), +				array('passwords.driver.bcrypt'), +				array('passwords.driver.salted_md5'), +				array('passwords.driver.phpass'), +			); +		} +	} + +	/** +	* @dataProvider check_password_data +	*/ +	public function test_check_password($hash_type) +	{ +		$password = $this->default_pw; +		$time = microtime(true); +		// Limit each test to 1 second +		while ((microtime(true) - $time) < 1) +		{ +			$hash = $this->manager->hash($password, $hash_type); +			$this->assertEquals(true, $this->manager->check($password, $hash)); +			$password .= $this->pw_characters[mt_rand(0, 66)]; +			$this->assertEquals(false, $this->manager->check($password, $hash)); +		} + +		// Check if convert_flag is correctly set +		$default_type = (version_compare(PHP_VERSION, '5.3.7', '<')) ? 'passwords.driver.bcrypt' : 'passwords.driver.bcrypt_2y'; +		$this->assertEquals(($hash_type !== $default_type), $this->manager->convert_flag); +	} + + +	public function check_hash_exceptions_data() +	{ +		return array( +			array('foobar', '3858f62230ac3c915f300c664312c63f', true), +			array('foobar', '$S$b57a939fa4f2c04413a4eea9734a0903647b7adb93181295', false), +			array('foobar', '$2a\S$kkkkaakdkdiej39023903204j2k3490234jk234j02349', false), +			array('foobar', '$H$kklk938d023k//k3023', false), +			array('foobar', '$H$3PtYMgXb39lrIWkgoxYLWtRkZtY3AY/', false), +			array('foobar', '$2a$kwiweorurlaeirw', false), +		); +	} + +	/** +	* @dataProvider check_hash_exceptions_data +	*/ +	public function test_check_hash_exceptions($password, $hash, $expected) +	{ +		$this->assertEquals($expected, $this->manager->check($password, $hash)); +	} + +	public function data_hash_password_length() +	{ +		return array( +			array('passwords.driver.bcrypt', false), +			array('passwords.driver.bcrypt_2y', false), +			array('passwords.driver.salted_md5', '3858f62230ac3c915f300c664312c63f'), +			array('passwords.driver.phpass', '3858f62230ac3c915f300c664312c63f'), +		); +	} + +	/** +	* @dataProvider data_hash_password_length +	*/ +	public function test_hash_password_length($driver, $expected) +	{ +		$this->assertEquals($expected, $this->passwords_drivers[$driver]->hash('foobar', 'foobar')); +	} + +	public function test_hash_password_8bit_bcrypt() +	{ +		$this->assertEquals(false, $this->manager->hash('foobar𝄞', 'passwords.driver.bcrypt')); +		if (version_compare(PHP_VERSION, '5.3.7', '<')) +		{ +			$this->assertEquals(false, $this->manager->hash('foobar𝄞', 'passwords.driver.bcrypt_2y')); +		} +		else +		{ +			$this->assertNotEquals(false, $this->manager->hash('foobar𝄞', 'passwords.driver.bcrypt_2y')); +		} +	} + +	public function test_combined_hash_data() +	{ +		if (version_compare(PHP_VERSION, '5.3.7', '<')) +		{ +			return array( +				array( +					'passwords.driver.salted_md5', +					array('passwords.driver.bcrypt'), +				), +				array( +					'passwords.driver.phpass', +					array('passwords.driver.salted_md5'), +				), +				array( +					'passwords.driver.salted_md5', +					array('passwords.driver.phpass', 'passwords.driver.bcrypt'), +				), +				array( +					'passwords.driver.salted_md5', +					array('passwords.driver.salted_md5'), +					false, +				), +				array( +					'$H$', +					array('$2a$'), +				), +			); +		} +		else +		{ +			return array( +				array( +					'passwords.driver.salted_md5', +					array('passwords.driver.bcrypt_2y'), +				), +				array( +					'passwords.driver.salted_md5', +					array('passwords.driver.bcrypt'), +				), +				array( +					'passwords.driver.phpass', +					array('passwords.driver.salted_md5'), +				), +				array( +					'passwords.driver.salted_md5', +					array('passwords.driver.bcrypt_2y', 'passwords.driver.bcrypt'), +				), +				array( +					'passwords.driver.salted_md5', +					array('passwords.driver.salted_md5'), +					false, +				), +				array( +					'passwords.driver.bcrypt_2y', +					array('passwords.driver.salted_md4'), +					false, +				), +				array( +					'$H$', +					array('$2y$'), +				), +			); +		} +	} + +	/** +	* @dataProvider test_combined_hash_data +	*/ +	public function test_combined_hash_password($first_type, $second_type, $expected = true) +	{ +		$password = $this->default_pw; +		$time = microtime(true); +		// Limit each test to 1 second +		while ((microtime(true) - $time) < 1) +		{ +			$hash = $this->manager->hash($password, $first_type); +			$combined_hash = $this->manager->hash($hash, $second_type); +			$this->assertEquals($expected, $this->manager->check($password, $combined_hash)); +			$password .= $this->pw_characters[mt_rand(0, 66)]; +			$this->assertEquals(false, $this->manager->check($password, $combined_hash)); + +			// If we are expecting the check to fail then there is +			// no need to run this more than once +			if (!$expected) +			{ +				break; +			} +		} +	} + +	public function test_unique_id() +	{ +		$time = microtime(true); +		$first_id = $this->driver_helper->unique_id(); +		// Limit test to 1 second +		while ((microtime(true) - $time) < 1) +		{ +			$this->assertNotEquals($first_id, $this->driver_helper->unique_id()); +		} +	} + +	public function test_check_hash_with_large_input() +	{ +		// 16 MB password, should be rejected quite fast +		$start_time = time(); +		$this->assertFalse($this->manager->check(str_repeat('a', 1024 * 1024 * 16), '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); +		$this->assertLessThanOrEqual(5, time() - $start_time); +	} + +	public function test_hash_password_with_large_input() +	{ +		// 16 MB password, should be rejected quite fast +		$start_time = time(); +		$this->assertFalse($this->manager->hash(str_repeat('a', 1024 * 1024 * 16))); +		$this->assertLessThanOrEqual(5, time() - $start_time); +	} +} diff --git a/tests/path_helper/web_root_path_test.php b/tests/path_helper/web_root_path_test.php index 2e1a37e02b..2c22511402 100644 --- a/tests/path_helper/web_root_path_test.php +++ b/tests/path_helper/web_root_path_test.php @@ -146,4 +146,27 @@ class phpbb_path_helper_web_root_path_test extends phpbb_test_case  		$this->assertEquals($expected, $path_helper->update_web_root_path($input, $symfony_request));  	} + +	public function clean_url_data() +	{ +		return array( +			array('', ''), +			array('://', '://'), +			array('http://', 'http://'), +			array('http://one/two/three', 'http://one/two/three'), +			array('http://../one/two', 'http://../one/two'), +			array('http://one/../two/three', 'http://two/three'), +			array('http://one/two/../three', 'http://one/three'), +			array('http://one/two/../../three', 'http://three'), +			array('http://one/two/../../../three', 'http://../three'), +		); +	} + +	/** +	* @dataProvider clean_url_data +	*/ +	public function test_clean_url($input, $expected) +	{ +		$this->assertEquals($expected, $this->path_helper->clean_url($input)); +	}  } diff --git a/tests/profile/custom_test.php b/tests/profile/custom_test.php index 1f33b45ba9..e68f1f7c4b 100644 --- a/tests/profile/custom_test.php +++ b/tests/profile/custom_test.php @@ -7,49 +7,65 @@  *  */ -require_once dirname(__FILE__) . '/../../phpBB/includes/functions_profile_fields.php'; -  class phpbb_profile_custom_test extends phpbb_database_test_case  {  	public function getDataSet()  	{ -		return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/profile_fields.xml'); +		return $this->createXMLDataSet(dirname(__FILE__) . '/fixtures/profile_fields.xml');  	} -	static public function dropdownFields() +	static public function dropdown_fields()  	{  		return array(  			// note, there is an offset of 1 between option_id (0-indexed)  			// in the database and values (1-indexed) to avoid problems with  			// transmitting 0 in an HTML form  			//    required, value, expected -			array(1,        '0',   'FIELD_INVALID_VALUE', 'Required field should throw error for out-of-range value'), -			array(1,        '1',   'FIELD_REQUIRED',      'Required field should throw error for default value'), -			array(1,        '2',   false,                 'Required field should accept non-default value'), -			array(0,        '0',   'FIELD_INVALID_VALUE', 'Optional field should throw error for out-of-range value'), -			array(0,        '1',   false,                 'Optional field should accept default value'), -			array(0,        '2',   false,                 'Optional field should accept non-default value'), +			array(1,        '0',   'FIELD_INVALID_VALUE-field',	'Required field should throw error for out-of-range value'), +			array(1,        '1',   'FIELD_REQUIRED-field',		'Required field should throw error for default value'), +			array(1,        '2',   false,						'Required field should accept non-default value'), +			array(0,        '0',   'FIELD_INVALID_VALUE-field', 'Optional field should throw error for out-of-range value'), +			array(0,        '1',   false,						'Optional field should accept default value'), +			array(0,        '2',   false,						'Optional field should accept non-default value'),  		);  	}  	/** -	* @dataProvider dropdownFields +	* @dataProvider dropdown_fields  	*/  	public function test_dropdown_validate($field_required, $field_value, $expected, $description)  	{ -		global $db; +		global $db, $table_prefix;  		$db = $this->new_dbal();  		$field_data = array(  			'field_id'			=> 1,  			'lang_id'			=> 1, +			'lang_name'			=> 'field',  			'field_novalue'		=> 1,  			'field_required'	=> $field_required,  		); +		$user = $this->getMock('\phpbb\user'); +		$user->expects($this->any()) +			->method('lang') +			->will($this->returnCallback(array($this, 'return_callback_implode'))); + +		$request = $this->getMock('\phpbb\request\request'); +		$template = $this->getMock('\phpbb\template\template'); -		$cp = new custom_profile; -		$result = $cp->validate_profile_field(FIELD_DROPDOWN, $field_value, $field_data); +		$cp = new \phpbb\profilefields\type\type_dropdown( +			new \phpbb\profilefields\lang_helper($db, $table_prefix . 'profile_fields_lang'), +			$request, +			$template, +			$user +		); +		$result = $cp->validate_profile_field($field_value, $field_data);  		$this->assertEquals($expected, $result, $description);  	} + +	public function return_callback_implode() +	{ +		return implode('-', func_get_args()); +	}  } diff --git a/tests/profile/fixtures/profile_fields.xml b/tests/profile/fixtures/profile_fields.xml index 0b2929f625..e0c260bbf5 100644 --- a/tests/profile/fixtures/profile_fields.xml +++ b/tests/profile/fixtures/profile_fields.xml @@ -10,21 +10,21 @@  			<value>1</value>  			<value>1</value>  			<value>0</value> -			<value>5</value> +			<value>profilefields.type.dropdown</value>  			<value>Default Option</value>  		</row>  		<row>  			<value>1</value>  			<value>1</value>  			<value>1</value> -			<value>5</value> +			<value>profilefields.type.dropdown</value>  			<value>First Alternative</value>  		</row>  		<row>  			<value>1</value>  			<value>1</value>  			<value>2</value> -			<value>5</value> +			<value>profilefields.type.dropdown</value>  			<value>Third Alternative</value>  		</row>  	</table> diff --git a/tests/security/hash_test.php b/tests/security/hash_test.php index e226365ef3..bc1bebd87a 100644 --- a/tests/security/hash_test.php +++ b/tests/security/hash_test.php @@ -11,6 +11,31 @@ require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';  class phpbb_security_hash_test extends phpbb_test_case  { +	public function setUp() +	{ +		global $phpbb_container; + +		$config = new \phpbb\config\config(array()); +		$phpbb_container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); +		$driver_helper = new \phpbb\passwords\driver\helper($config); +		$passwords_drivers = array( +			'passwords.driver.bcrypt_2y'	=> new \phpbb\passwords\driver\bcrypt_2y($config, $driver_helper), +			'passwords.driver.bcrypt'		=> new \phpbb\passwords\driver\bcrypt($config, $driver_helper), +			'passwords.driver.salted_md5'	=> new \phpbb\passwords\driver\salted_md5($config, $driver_helper), +			'passwords.driver.phpass'		=> new \phpbb\passwords\driver\phpass($config, $driver_helper), +		); + +		$passwords_helper = new \phpbb\passwords\helper; +		// Set up passwords manager +		$passwords_manager = new \phpbb\passwords\manager($config, $passwords_drivers, $passwords_helper, array_keys($passwords_drivers)); + +		$phpbb_container +			->expects($this->any()) +			->method('get') +			->with('passwords.manager') +			->will($this->returnValue($passwords_manager)); +	} +  	public function test_check_hash_with_phpass()  	{  		$this->assertTrue(phpbb_check_hash('test', '$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1')); diff --git a/tests/security/redirect_test.php b/tests/security/redirect_test.php index 8e36780ca4..77dc955c26 100644 --- a/tests/security/redirect_test.php +++ b/tests/security/redirect_test.php @@ -13,19 +13,87 @@ require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';  class phpbb_security_redirect_test extends phpbb_security_test_base  { +	protected $path_helper; + +	protected $controller_helper; +  	public function provider()  	{ +		$this->controller_helper = $this->get_controller_helper();  		// array(Input -> redirect(), expected triggered error (else false), expected returned result url (else false))  		return array( -			array('data://x', false, 'http://localhost/phpBB'), -			array('bad://localhost/phpBB/index.php', 'INSECURE_REDIRECT', false), -			array('http://www.otherdomain.com/somescript.php', false, 'http://localhost/phpBB'), -			array("http://localhost/phpBB/memberlist.php\n\rConnection: close", 'INSECURE_REDIRECT', false), -			array('javascript:test', false, 'http://localhost/phpBB/../javascript:test'), -			array('http://localhost/phpBB/index.php;url=', 'INSECURE_REDIRECT', false), +			array('data://x', false, false, 'http://localhost/phpBB'), +			array('bad://localhost/phpBB/index.php', false, 'INSECURE_REDIRECT', false), +			array('http://www.otherdomain.com/somescript.php', false, false, 'http://localhost/phpBB'), +			array("http://localhost/phpBB/memberlist.php\n\rConnection: close", false, 'INSECURE_REDIRECT', false), +			array('javascript:test', false, false, 'http://localhost/phpBB/javascript:test'), +			array('http://localhost/phpBB/index.php;url=', false, 'INSECURE_REDIRECT', false), +			array('http://localhost/phpBB/app.php/foobar', false, false, 'http://localhost/phpBB/app.php/foobar'), +			array('./app.php/foobar', false, false, 'http://localhost/phpBB/app.php/foobar'), +			array('app.php/foobar', false, false, 'http://localhost/phpBB/app.php/foobar'), +			array('./../app.php/foobar', false, false, 'http://localhost/app.php/foobar'), +			array('./../app.php/foobar', true, false, 'http://localhost/app.php/foobar'), +			array('./../app.php/foo/bar', false, false, 'http://localhost/app.php/foo/bar'), +			array('./../app.php/foo/bar', true, false, 'http://localhost/app.php/foo/bar'), +			array('./../foo/bar', false, false, 'http://localhost/foo/bar'), +			array('./../foo/bar', true, false, 'http://localhost/foo/bar'), +			array('app.php/', false, false, 'http://localhost/phpBB/app.php/'), +			array($this->controller_helper->url('a'), false, false, 'http://localhost/phpBB/app.php/a'), +			array($this->controller_helper->url(''), false, false, 'http://localhost/phpBB/app.php/'), +			array('./app.php/', false, false, 'http://localhost/phpBB/app.php/'), +			array('foobar', false, false, 'http://localhost/phpBB/foobar'), +			array('./foobar', false, false, 'http://localhost/phpBB/foobar'), +			array('foo/bar', false, false, 'http://localhost/phpBB/foo/bar'), +			array('./foo/bar', false, false, 'http://localhost/phpBB/foo/bar'), +			array('./../index.php', false, false, 'http://localhost/index.php'), +			array('./../index.php', true, false, 'http://localhost/index.php'), +			array('../index.php', false, false, 'http://localhost/index.php'), +			array('../index.php', true, false, 'http://localhost/index.php'), +			array('./index.php', false, false, 'http://localhost/phpBB/index.php'),  		);  	} +	protected function get_path_helper() +	{ +		if (!($this->path_helper instanceof \phpbb\path_helper)) +		{ +			$this->path_helper = new \phpbb\path_helper( +				new \phpbb\symfony_request( +					new phpbb_mock_request() +				), +				new \phpbb\filesystem(), +				$this->phpbb_root_path, +				'php' +			); +		} +		return $this->path_helper; +	} + +	protected function get_controller_helper() +	{ +		if (!($this->controller_helper instanceof \phpbb\controller\helper)) +		{ +			global $phpbb_dispatcher; + +			$phpbb_dispatcher = new phpbb_mock_event_dispatcher; +			$this->user = $this->getMock('\phpbb\user'); +			$phpbb_path_helper = new \phpbb\path_helper( +				new \phpbb\symfony_request( +					new phpbb_mock_request() +				), +				new \phpbb\filesystem(), +				$phpbb_root_path, +				$phpEx +			); +			$this->template = new phpbb\template\twig\twig($phpbb_path_helper, $config, $this->user, new \phpbb\template\context()); + +			// We don't use mod_rewrite in these tests +			$config = new \phpbb\config\config(array('enable_mod_rewrite' => '0')); +			$this->controller_helper = new \phpbb\controller\helper($this->template, $this->user, $config, '', 'php'); +		} +		return $this->controller_helper; +	} +  	protected function setUp()  	{  		parent::setUp(); @@ -33,26 +101,41 @@ class phpbb_security_redirect_test extends phpbb_security_test_base  		$GLOBALS['config'] = array(  			'force_server_vars'	=> '0',  		); + +		$this->path_helper = $this->get_path_helper(); +		$this->controller_helper = $this->get_controller_helper();  	}  	/**  	* @dataProvider provider  	*/ -	public function test_redirect($test, $expected_error, $expected_result) +	public function test_redirect($test, $disable_cd_check, $expected_error, $expected_result)  	{ -		global $user; +		global $user, $phpbb_root_path, $phpbb_path_helper; + +		$phpbb_path_helper = $this->path_helper; + +		$temp_phpbb_root_path = $phpbb_root_path; +		$temp_page_dir = $user->page['page_dir']; +		// We need to hack phpbb_root_path and the user's page_dir here +		// so it matches the actual fileinfo of the testing script. +		// Otherwise the paths are returned incorrectly. +		$phpbb_root_path = ''; +		$user->page['page_dir'] = '';  		if ($expected_error !== false)  		{  			$this->setExpectedTriggerError(E_USER_ERROR, $expected_error);  		} -		$result = redirect($test, true); +		$result = redirect($test, true, $disable_cd_check);  		// only verify result if we did not expect an error  		if ($expected_error === false)  		{  			$this->assertEquals($expected_result, $result);  		} +		$phpbb_root_path = $temp_phpbb_root_path; +		$user->page['page_dir'] = $temp_page_dir;  	}  } diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index eba5a2dfdf..71d03746a9 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -503,6 +503,7 @@ class phpbb_functional_test_case extends phpbb_test_case  		set_config(null, null, null, $config);  		set_config_count(null, null, null, $config);  		$phpbb_dispatcher = new phpbb_mock_event_dispatcher(); +		$passwords_manager = $this->get_passwords_manager();  		$user_row = array(  			'username' => $username, @@ -512,7 +513,7 @@ class phpbb_functional_test_case extends phpbb_test_case  			'user_lang' => 'en',  			'user_timezone' => 0,  			'user_dateformat' => '', -			'user_password' => phpbb_hash($username . $username), +			'user_password' => $passwords_manager->hash($username . $username),  		);  		return user_add($user_row);  	} @@ -997,4 +998,29 @@ class phpbb_functional_test_case extends phpbb_test_case  		}  		return null;  	} + +	/** +	* Return a passwords manager instance +	* +	* @return phpbb\passwords\manager +	*/ +	public function get_passwords_manager() +	{ +		// Prepare dependencies for manager and driver +		$config = new \phpbb\config\config(array()); +		$driver_helper = new \phpbb\passwords\driver\helper($config); + +		$passwords_drivers = array( +			'passwords.driver.bcrypt_2y'	=> new \phpbb\passwords\driver\bcrypt_2y($config, $driver_helper), +			'passwords.driver.bcrypt'		=> new \phpbb\passwords\driver\bcrypt($config, $driver_helper), +			'passwords.driver.salted_md5'	=> new \phpbb\passwords\driver\salted_md5($config, $driver_helper), +			'passwords.driver.phpass'		=> new \phpbb\passwords\driver\phpass($config, $driver_helper), +		); + +		$passwords_helper = new \phpbb\passwords\helper; +		// Set up passwords manager +		$manager = new \phpbb\passwords\manager($config, $passwords_drivers, $passwords_helper, array_keys($passwords_drivers)); + +		return $manager; +	}  }  | 
