= 0 ) { $plugin_start = $curlyTags[0][$i][0]; $plugin = $curlyTags[1][$i][0]; // Work out where the plugin starts. This can not be done using the // positional data from $curlyTags since the position might have // changed since the last cycle. We therefore need to determine the // position direclty. - xing - Thursday Nov 01, 2007 22:55:10 CET //$pos = $curlyTags[0][$i][1]; $pos = strpos( $pData, $plugin_start ); $dataTag = strtolower( $plugin ); // hush up the return of this in case someone uses curly braces to enclose text if( !empty( $gLibertySystem->mDataTags[$dataTag] ) ) { $pluginInfo = $gLibertySystem->getPluginInfo( $gLibertySystem->mDataTags[$dataTag] ) ; // only process a standalone unpaired tag or the start tag for a paired tag $paired_close_tag_seen[$dataTag] = empty( $paired_close_tag_seen[$dataTag] ) || $paired_close_tag_seen[$dataTag] == 0 ? 1 : 0; $is_opening_tag = false; if(( empty( $pluginInfo['requires_pair'] ) && ( strtolower( $plugin_start ) != '{/'. $dataTag . '}' )) || ( strpos( $plugin_start, ' ' ) > 0 ) || ( strtolower( $plugin_start ) == '{'.$dataTag.'}' && !$paired_close_tag_seen[$dataTag] ) ) { $is_opening_tag = true; } } if( // when in CODE parsing mode, replace only CODE plugins ( ( $code_first && ( $dataTag == 'code' ) ) // when NOT in CODE parsing mode, replace all other plugins || ( !$code_first && ( $dataTag <> 'code' ) ) ) && !empty( $gLibertySystem->mDataTags[$dataTag] ) && !empty( $pluginInfo ) && ( $loadFunc = $gLibertySystem->getPluginFunction( $gLibertySystem->mDataTags[$dataTag], 'load_function' ) ) && $is_opening_tag ) { if( $pluginInfo['requires_pair'] ) { $plugin_end = '{/'.$plugin.'}'; $pos_end = strpos( strtolower( $pData ), strtolower( $plugin_end ), $pos ); // where plugin data ends $plugin_end2 = '{'.$plugin.'}'; $pos_end2 = strpos( strtolower( $pData ), strtolower( $plugin_end2 ), $pos + 1 ); // where plugin data ends if( ( $pos_end2 > 0 && $pos_end2 > 0 && $pos_end2 < $pos_end ) || $pos_end === false ) { $pos_end = $pos_end2; $plugin_end = $plugin_end2; } } else { $pos_end = $pos + strlen( $curlyTags[0][$i][0] ); $plugin_end = ''; } // Extract the plugin data $plugin_data_len = $pos_end - $pos - strlen( $curlyTags[0][$i][0] ); $plugin_data = substr( $pData, $pos + strlen( $plugin_start ), $plugin_data_len ); $arguments = []; // Construct argument list array $paramString = str_replace( '>', '>', trim( $curlyTags[2][$i][0] ) ); if( preg_match( '/^\(.*=>.*\)$/', $paramString ) ) { $paramString = preg_replace( '/[\(\)]/', '', $paramString ); //we have the old style parms like {CODE (in=>1)} $params = explode( ',', trim( $paramString ) ); foreach( $params as $param ) { // the following str_replace line is to decode the > char when html is turned off // perhaps the plugin syntax should be changed in 1.8 not to use any html special chars $parts = preg_split( '/=>?/', $param ); if( isset( $parts[0] ) && isset( $parts[1] ) ) { $name = trim( $parts[0] ); $arguments[$name] = trim( $parts[1] ); } } } else { $paramString = trim( $curlyTags[2][$i][0], " \t()" ); $paramString = str_replace(""", '"', $paramString); $arguments = KernelTools::parse_xml_attributes( $paramString ); } if( $ret = $loadFunc( $plugin_data, $arguments, $pCommonObject, $pParseHash )) { $key = "parseprotect".md5( mt_rand() ); $pReplace[] = [ 'key' => $key, 'data' => $ret, ]; // don't modify data if $pos is false if( $pos !== false ) { $pData = substr_replace( $pData, $key, $pos, $pos_end - $pos + strlen( $plugin_end )); } } } $i--; // if we are in CODE parsing mode and list is done, switch to 'parse other plugins' mode and start all over if( $code_first && ( $i < 0 ) ) { $i = count( $curlyTags[0] ) - 1; $code_first = false; } } // while } } /** * This function replaces pre- and no-parsed sections with unique keys and * saves the section contents for later reinsertion. It is needed by * parse_data_plugins() to extract sections that don't require parsing * * @param string $pData data that might contain ~np~ or ~pp~ strings * @param array $preparsed array that is updated by refrerence with key and data that needs to be substituted later * @param array $noparsed array that is updated by refrerence with key and data that needs to be substituted later * @access public * @return void */ function parse_protect( &$pData, &$pReplace ) { // Find all sections delimited by ~pp~ ... ~/pp~ preg_match_all( "/\~pp\~(.*?)\~\/pp\~/s", $pData, $preparse ); if( count( $preparse[0] )) { foreach( array_unique( $preparse[1] ) as $pp ) { $aux["key"] = md5( mt_rand() ); $aux["data"] = "
".htmlspecialchars( $pp )."
"; $pReplace[] = $aux; $pData = str_replace( "~pp~$pp~/pp~", $aux['key'], $pData ); } } // now remove
...
 sections
	preg_match_all( "!(]*>)(.*?)(]*>)!si", $pData, $preparse );
	if( count( $preparse[0] )) {
		foreach( $preparse[2] as $key => $pre ) {
			$aux["key"]  = md5( mt_rand() );
			$aux["data"] = $preparse[1][$key].htmlspecialchars( $pre ).$preparse[3][$key];
			$pReplace[]  = $aux;
			$pData       = str_replace( $preparse[1][$key].$pre.$preparse[3][$key], $aux['key'], $pData );
		}
	}

	// and now ~np~...~/np~ sections
	preg_match_all( "/\~np\~(.*?)\~\/np\~/s", $pData, $noparse );
	if( count( $noparse[0] )) {
		foreach( array_unique( $noparse[1] ) as $np ) {
			$aux["key"]  = md5( mt_rand() );
			$aux["data"] = htmlspecialchars( $np );
			$pReplace[]  = $aux;
			$pData       = str_replace( "~np~$np~/np~", $aux['key'], $pData );
		}
	}
}

// ================== Liberty Plugin Helper ==================
/**
 * pass in the plugin parameters and out comes a hash with usable styling information
 *
 * @param array $pParamHash
 * @access public
 * @return array hash full of styling goodies
 */
function liberty_plugins_wrapper_style( $pParamHash ) {
	global $gBitSystem;

	$ret = [];
	$ret['style'] = $ret['description'] = '';

	if( !empty( $pParamHash ) && is_array( $pParamHash )) {
		// if align is right and text-align isn't set, we'll align that right as well
		if( empty( $pParamHash['text-align'] ) && ( !empty( $pParamHash['align'] ) && $pParamHash['align'] == 'right' || !empty( $pParamHash['align'] ) && $pParamHash['align'] == 'right' )) {
			$pParamHash['text-align'] = 'right';
		}

		// this defines what the wrapper should be - div or span
		// if someone sets this value manually, they know what they are doing
		if( empty( $pParamHash['wrapper'] )) {
			$pParamHash['wrapper'] = 'div';

			if( $gBitSystem->isFeatureActive( 'liberty_use_span_wrapper' )) {
				// set to 'span' if desired
				$pParamHash['wrapper'] = 'span';

				// force display:block to the "div" if not specified otherwise
				if( empty( $pParamHash['display'] )) {
					$pParamHash['display'] = "inline-block";
				}
			}
		}

		foreach( $pParamHash as $key => $value ) {
			if( !empty( $value )) {
				switch( $key ) {
					// description
					case 'desc':
						$key = 'description';
					case 'description':
						$ret[$key] = $value;
						break;
					// styling
					case 'width':
					case 'height':
						if( preg_match( "/^\d+(em|px|%|pt)$/", trim( $value ))) {
							$ret['style'] .= "{$key}:{$value};";
						} elseif( preg_match( "/^\d+$/", $value )) {
							$ret['style'] .= "{$key}:{$value}px;";
						}
						break;
					case 'background':
					case 'background-color':
					case 'border':
					case 'color':
					case 'display':
					case 'float':
					case 'font':
					case 'font-family':
					case 'font-size':
					case 'font-weight':
					case 'margin':
					case 'overflow':
					case 'padding':
					case 'text-align':
						$ret['style'] .= "{$key}:{$value};";
						break;
					// align and float are special
					case 'align':
						if( $value == 'center' || $value == 'middle' ) {
							$ret['style'] .= 'text-align:center;';
						} else {
							$ret['style'] .= "float:{$value};";
						}
						break;
					// default just gets re-assigned
					default:
						$ret[$key] = $value;
						break;
				}
			}
		}
	}

	return $ret;
}

// ================== Liberty Service Functions ==================
/**
 * liberty_content_load_sql
 *
 * @access public
 * @return string content load sql
 */
function liberty_content_load_sql( $pObject, $pParamHash=null ) {
	global $gBitSystem, $gBitUser;
	$ret = [];

	$hasPerm = ( is_object( $pObject ) && isset( $pObject->hasUserPermission )) ? $pObject->hasUserPermission( 'p_liberty_edit_all_status' ) : $gBitUser->hasPermission( 'p_liberty_edit_all_status' );

	if( $gBitSystem->isFeatureActive( 'liberty_display_status' ) && !$hasPerm ) {
		if(( is_object( $pObject ) && !empty( $pObject->mType['content_type_guid'] ) && $pObject->mType['content_type_guid'] == 'bitcomment' )
			|| ( !empty( $pParamHash['include_comments'] ) && $pParamHash['include_comments']  == 'y' )) {
			// if we are getting a list of comments then lets check the owner of the comment root and the owner of the content
				$ret['join_sql'] = "
					INNER JOIN `".BIT_DB_PREFIX."liberty_comments` lcoms ON (lc.`content_id` = lcoms.`content_id`)
					INNER JOIN `".BIT_DB_PREFIX."liberty_content` rlcs ON( rlcs.`content_id`=lcoms.`root_id` )";
			$ret['where_sql'] = " AND lc.`content_status_id` < 100 AND ( ( (rlcs.`user_id` = '".$gBitUser->getUserId()."' OR lc.`user_id` = '".$gBitUser->getUserId()."') AND lc.`content_status_id` > -100) OR lc.`content_status_id` > 0 )";
		} else {
			// let owner see any of their own content with a status > -100
			$ret['where_sql'] = " AND lc.`content_status_id` < 100 AND ( (lc.`user_id` = '".$gBitUser->getUserId()."' AND lc.`content_status_id` > -100) OR lc.`content_status_id` > 0 )";
		}
	}

	// Make sure owner comes out properly for all content
	if ($gBitSystem->isFeatureActive('liberty_allow_change_owner') && $gBitUser->hasPermission('p_liberty_edit_content_owner')) {
		$ret['select_sql'] = " , lc.`user_id` AS owner_id";
	}
	return $ret;
}

/**
 * liberty_content_list_sql
 *
 * @param array $pParamHash
 * @param array $pParamHash['enforce_status'] will add joins to status_id even if user is admin
 * @param array $pParamHash['min_status_id'] one less than the minimum status a content can have to be included
 * @param array $pParamHash['max_status_id'] one more than the maximum status a content can have to be included
 * @param array $pParamHash['min_owner_status_id'] one less than the mimimum status a content can have to be included that is owned by the requester
 * @access public
 * @return array content list sql
 */
function liberty_content_list_sql( $pObject, $pParamHash=null ) {
	global $gBitSystem, $gBitUser;
	$ret = [];

	$hasPerm = false;
	// enforce_status will require the status limit on everyone including admin and thus we can ignore permission checks
	if( !isset( $pParamHash['enforce_status'] )) {
		$hasPerm = ( is_object( $pObject ) && method_exists( $pObject, 'hasUserPermission' )) ? $pObject->hasUserPermission( 'p_liberty_edit_all_status', false ) : $gBitUser->hasPermission( 'p_liberty_edit_all_status' );
	}

	// default show content with status between 0 and 100;
	$min_status_id = BitBase::verifyId( $pParamHash['min_status_id'] ?? 0 ) ? $pParamHash['min_status_id'] : 0;
	$max_status_id = BitBase::verifyId( $pParamHash['max_status_id'] ?? 0 ) ? $pParamHash['max_status_id'] : 100;
	// let owner see any of their own content with a status > -100
	$min_owner_status_id = BitBase::verifyId( $pParamHash['min_owner_status_id'] ?? 0 ) ? $pParamHash['min_owner_status_id'] : -100;

	if( $gBitSystem->isFeatureActive('liberty_display_status') && !$hasPerm ) {
		if(( is_object( $pObject ) && !empty( $pObject->mType['content_type_guid'] ) && $pObject->mType['content_type_guid'] == 'bitcomment' )
			|| ( !empty( $pParamHash['include_comments'] ) && $pParamHash['include_comments']  == 'y' )) {
			// if we are getting a list of comments then lets check the owner of the comment root and the owner of the content
			$ret['join_sql'] = "
					LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_comments` lcoms ON (lc.`content_id` = lcoms.`content_id`)
					LEFT OUTER JOIN `".BIT_DB_PREFIX."liberty_content` rlcs ON( rlcs.`content_id`=lcoms.`root_id` )";
			$ret['where_sql'] =
				" AND lc.`content_status_id` < ".$max_status_id.
				" AND (
					( (rlcs.`user_id` = '".$gBitUser->getUserId()."' OR lc.`user_id` = '".$gBitUser->getUserId()."') AND lc.`content_status_id` > ".$min_owner_status_id.")
					OR lc.`content_status_id` > ".$min_status_id."
				)";
		} else {
			$ret['where_sql'] =
				" AND lc.`content_status_id` < ".$max_status_id.
				" AND (
					(lc.`user_id` = '".$gBitUser->getUserId()."' AND lc.`content_status_id` > ".$min_owner_status_id.")
					OR lc.`content_status_id` > ".$min_status_id."
				)";
		}
	}

	return $ret;
}

/**
 * liberty_content_preview
 *
 * @param object $pObject
 * @access public
 * @return void
 */
function liberty_content_preview( $pObject ) {
	global $gBitSystem, $gBitUser;
	if( $gBitSystem->isFeatureActive( 'liberty_display_status' )
		&& ( $gBitUser->hasPermission( 'p_liberty_edit_content_status' ) || $gBitUser->hasPermission( 'p_libert_edit_all_status' ))
		&& BitBase::verifyId( $_REQUEST['content_status_id'] )) {
		$pObject->mInfo['content_status_id'] = $_REQUEST['content_status_id'];
	}
	if( $gBitSystem->isFeatureActive( 'liberty_allow_change_owner' )
		&& $gBitUser->hasPermission( 'p_liberty_edit_content_owner' )
		&& BitBase::verifyId( $_REQUEST['owner_id'] )) {
		$pObject->mInfo['owner_id'] = $_REQUEST['owner_id'];
	}
	include_once LIBERTY_PKG_INCLUDE_PATH.'edit_help_inc.php';
}

/**
 * liberty_content_display
 *
 * @param object $pObject
 * @param array $pParamHash
 * @access public
 * @return void
 */
function liberty_content_display( $pObject, &$pParamHash ) {
	if( $pObject->isValid() ) {
		global $gBitUser, $gBitSystem;

		// make sure user has appropriate permissions to view this content
		if( !empty( $pParamHash['perm_name'] )) {
			$pObject->verifyViewPermission();
		}
	}
}

/**
 * liberty_content_edit
 *
 * @param array $pObject
 * @param array $pParamHash
 * @access public
 * @return void
 */
function liberty_content_edit( $pObject, $pParamHash ) {
	include_once LIBERTY_PKG_INCLUDE_PATH.'edit_help_inc.php';
	include_once LIBERTY_PKG_INCLUDE_PATH."edit_storage_inc.php";
}

// ================== Liberty File Processing Functions ==================

/**
 * Process uploaded files. Will automagically generate thumbnails for images
 *
 * @param array $pFileHash Data require to process the files
 * @param array $pFileHash['name'] (required) Name of the uploaded file
 * @param array $pFileHash['type'] (required) Mime type of the file uploaded
 * @param array $pFileHash['dest_branch'] (required) Relative path where you want to store the file (trailing slash required)
 * @param array $pFileHash['tmp_name'] (required) Absolute path to file including file name
 * @param boolean $pFileHash['thumbnail'] (optional) Set to false if you don't want to generate thumbnails
 * @param array $pFileHash['thumbnail_sizes'] (optional) Decide what sizes thumbnails you want to create: icon, avatar, small, medium, large
 * @param boolean $pMoveFile (optional) specify if you want to move or copy the original file
 * @access public
 * @return bool true on success, false on failure - mErrors will contain reason for failure
 */
function liberty_process_upload( &$pFileHash, $pMoveFile = true ) {
	global $gBitSystem;

	// Check for evil file extensions that could be execed on the server
	if( preg_match( EVIL_EXTENSION_PATTERN, $pFileHash['name'] )) {
		$pFileHash['type'] = 'text/plain';
		$pFileHash['name'] .= '.txt';
	}

	if ( !KernelTools::is_windows() ) {
		list( $pFileHash['name'], $pFileHash['type'] ) = $gBitSystem->verifyFileExtension( $pFileHash['tmp_name'], $pFileHash['name'] );
	} else {
		$pFileHash['type'] = $gBitSystem->verifyMimeType( $pFileHash['tmp_name'] );
	}

	$ext = strrpos( $pFileHash['name'], '.' );

	// clean out crap that can make life difficult in server maintenance
	$cleanedBaseName = preg_replace( '/[&\%:\/\\\]/', '', substr( $pFileHash['name'], 0, $ext ) );
	$pFileHash['dest_base_name'] = $cleanedBaseName;
	$pFileHash['source_file'] = $pFileHash['tmp_name'];
	// lowercase all file extensions

	$pFileHash['name'] = $cleanedBaseName.strtolower( substr( $pFileHash['name'], $ext ) );

	// Thumbs.db is a windows My Photos/ folder file, and seems to really piss off imagick
	$canThumbFunc = liberty_get_function( 'can_thumbnail' );
	$ret =  !empty( $canThumbFunc ) && $canThumbFunc( $pFileHash['type'] ) && $pFileHash['name'] != 'Thumbs.db'
		? liberty_process_image( $pFileHash, $pMoveFile )
		: liberty_process_generic( $pFileHash, $pMoveFile );

	return $ret;
}

/**
 * liberty_process_archive
 *
 * @param array $pFileHash
 * @access public
 * @return string|bool true on success, false on failure
 */
function liberty_process_archive( &$pFileHash ) {
	// sanity check: make sure tmp_name isn't empty. will scan / if it is
	if( !is_array( $pFileHash ) || empty( $pFileHash['tmp_name'] ) || empty( $pFileHash['name'] ) ) {
		return false;
	}

	$cwd = getcwd();
	// if the file has been uploaded using a form, we'll process the uploaded
	// file directly. if it's been ftp uploaded or some other method used,
	// we'll copy the file. in the case of xuploaded files, the files have been
	// processed but don't have to be copied
	if( empty( $pFileHash['preprocessed'] ) && !is_uploaded_file( $pFileHash['tmp_name'] ) && is_file( $pFileHash['tmp_name'] ) ) {
		$tmpDir = KernelTools::get_temp_dir();
		$copyFile = tempnam( !empty( $tmpDir ) ? $tmpDir : '/tmp', $pFileHash['name'] );
		copy( $pFileHash['tmp_name'], $copyFile );
		$pFileHash['tmp_name'] = $copyFile;
	}

	$dir = dirname( $pFileHash['tmp_name'] );
	$upExt = strtolower( substr( $pFileHash['name'], strrpos( $pFileHash['name'], '.' ) + 1 ) );
	$baseDir = $dir.'/';
	if( is_file( $pFileHash['tmp_name'] ) ) {
		global $gBitUser;
		$baseDir .= $gBitUser->mUserId;
	}

	$destDir = $baseDir.'/'.basename( $pFileHash['tmp_name'] );
	// this if is very important logic back so subdirs get processed properly
	if( ( is_dir( $baseDir ) || mkdir( $baseDir ) ) && @mkdir( $destDir ) ) {
		// Some commands don't nicely support extracting to other directories
		chdir( $destDir );
		list( $mimeType, $mimeExt ) = explode( '/', strtolower( $pFileHash['type'] ) );
		switch( $mimeExt ) {
			case 'x-rar-compressed':
			case 'x-rar':
				$shellResult = shell_exec( "unrar x \"{$pFileHash['tmp_name']}\" \"$destDir\"" );
				break;
			case 'x-bzip2':
			case 'bzip2':
			case 'x-gzip':
			case 'gzip':
			case 'x-tgz':
			case 'x-tar':
			case 'tar':
				switch( $upExt ) {
					case 'gz':
					case 'tgz': $compressFlag = '-z'; break;
					case 'bz2': $compressFlag = '-j'; break;
					default: $compressFlag = ''; break;
				}
				$shellResult = shell_exec( "tar -x $compressFlag -f \"{$pFileHash['tmp_name']}\"  -C \"$destDir\"" );
				break;
			case 'x-zip-compressed':
			case 'x-zip':
			case 'zip':
				$shellResult = shell_exec( "unzip \"{$pFileHash['tmp_name']}\" -d \"$destDir\"" );
				break;
			case 'x-stuffit':
			case 'stuffit':
				$shellResult = shell_exec( "unstuff -d=\"$destDir\" \"{$pFileHash['tmp_name']}\" " );
				break;
			default:
				if( $upExt == 'zip' ) {
					$shellResult = shell_exec( "unzip \"{$pFileHash['tmp_name']}\" -d \"$destDir\"" );
				} elseif( $upExt == 'rar' ) {
					$shellResult = shell_exec( "unrar x \"{$pFileHash['tmp_name']}\" \"$destDir\"" );
				} elseif( $upExt == 'sit' || $upExt == 'sitx' ) {
					print( "unstuff -d=\"$destDir\" \"{$pFileHash['tmp_name']}\" " );
					$shellResult = shell_exec( "unstuff -d=\"$destDir\" \"{$pFileHash['tmp_name']}\" " );
				} else {
					$destDir = null;
				}
				break;
		}
	}

	chdir( $cwd );

	// if we created a copy of the original, we remove it
	if( !empty( $copyFile ) ) {
		@unlink( $copyFile );
	}

	if( preg_match( "!^/+$!", $destDir || '' )) {
		// obviously something went horribly wrong
		return false;
	}
		return $destDir;

}

/**
 * liberty_process_generic
 *
 * @param array $pFileHash
 * @param bool $pMoveFile
 * @access public
 * @return bool true on success, false on failure - mErrors will contain reason for failure
 */
function liberty_process_generic( &$pFileHash, $pMoveFile = true ) {
	global $gBitSystem;
	$ret = null;
	if( !empty( $pFileHash['dest_file'] ) ) {
		$destFile = $pFileHash['dest_file'];
	} else {
		$destFile =  $gBitSystem->isFeatureActive( 'liberty_originalize_file_names' )
			? STORAGE_PKG_PATH.$pFileHash['dest_branch'].liberty_mime_get_default_file_name( $pFileHash['name'], $pFileHash['type'] )
			: STORAGE_PKG_PATH.$pFileHash['dest_branch'].$pFileHash['name'];
		if ( KernelTools::is_windows() ) {
			$destFile = str_replace( '//', '\\', str_replace( "\\", '\\', $destFile ) );
		}
	}

	KernelTools::mkdir_p( dirname( $destFile ) );

	if( is_file( $pFileHash['source_file']) ) {
		if( $pFileHash['source_file'] == $destFile ) {
			// do nothing if source and dest are the same
		} elseif( $pMoveFile ) {
			if( is_uploaded_file( $pFileHash['source_file'] ) ) {
				move_uploaded_file( $pFileHash['source_file'], $destFile );
			} else {
				rename( $pFileHash['source_file'], $destFile );
			}
		} else {
			copy( $pFileHash['source_file'], $destFile );
		}
		$ret = $destFile;
	}
	$pFileHash['size'] = filesize( $destFile );

	return $ret;
}

/**
 * liberty_process_image
 *
 * @param array $pFileHash
 * @access public
 * @return bool true on success, false on failure - mErrors will contain reason for failure
 */
function liberty_process_image( &$pFileHash, $pMoveFile = true ) {
	global $gBitSystem;
	$ret = null;

	list($type, $ext) = explode( '/', strtolower( $pFileHash['type'] ) );
	if( $resizePath = liberty_process_generic( $pFileHash, $pMoveFile )) {
		$pFileHash['source_file'] = $resizePath;

		//set permissions if possible - necessary for some wonky shared hosting environments
		if(chmod($pFileHash['source_file'], 0644)){
			//does nothing, but fails elegantly
		}
		$nameHold = $pFileHash['name'];
		$sizeHold = $pFileHash['size'];
		$ret = $pFileHash['source_file'];

		// do not thumbnail only if intentionally set to false
		if( !isset( $pFileHash['thumbnail'] ) || $pFileHash['thumbnail']==true ) {
			liberty_generate_thumbnails( $pFileHash );
		}
		$pFileHash['name'] = $nameHold;
		$pFileHash['size'] = $sizeHold;
	}
	return $ret;
}

/**
 * liberty_clear_thumbnails will clear all thummbnails found in a given directory
 *
 * @param array $pFileHash['dest_branch'] should contain the path to the dir where we should remove thumbnails
 * @access public
 * @return bool true on success, false on failure
 */
function liberty_clear_thumbnails( &$pFileHash ) {
	if( !empty( $pFileHash['dest_branch'] )) {
		$thumbHash = [
			'source_file' => $pFileHash['dest_branch'],
			'mime_image'   => false,
		];

		// get thumbnails we want to remove
		if( $thumbs = liberty_fetch_thumbnails( $thumbHash )) {
			foreach( $thumbs as $thumb ) {
				$thumb = BIT_ROOT_PATH.$thumb;
				if( is_writable( $thumb )) {
					unlink( $thumb );
				}
			}
			// if this was the thumbs subdirectory, we'll remove it if it's empty
			if( basename( dirname( $thumb )) == 'thumbs' ) {
				@rmdir( dirname( $thumb ));
			}
		}
	}
	return true;
}

/**
 * liberty_get_function
 *
 * @param string $pType
 * @access public
 * @return string
 */
function liberty_get_function( $pType ) {
	global $gBitSystem;
	$processor = $gBitSystem->getConfig( 'image_processor', 'gd' );
	$ret = "\\Bitweaver\\Liberty\\liberty_{$processor}_{$pType}_image";
	return function_exists( $ret ) ? $ret : false;
}

/**
 * liberty_generate_thumbnails
 *
 * @param array $pFileHash
 * @access public
 * @return bool true on success, false on failure - mErrors will contain reason for failure
 */
function liberty_generate_thumbnails( $pFileHash ) {
	global $gBitSystem, $gThumbSizes;
	$ret = false;

	if( $resizeFunc = liberty_get_function( 'resize' ) ) {

		// allow custom selection of thumbnail sizes
		if( empty( $pFileHash['thumbnail_sizes'] )) {
			$pFileHash['thumbnail_sizes'] = !empty( $gThumbSizes ) && is_array( $gThumbSizes )
				? array_keys( $gThumbSizes )
				: [ 'extra-large','large', 'medium', 'small', 'avatar', 'icon' ];
		}

		if( ( !preg_match( '#image/(gif|jpe?g|png)#i', $pFileHash['type'] ) && $gBitSystem->isFeatureActive( 'liberty_jpeg_originals' )) || in_array( 'original', $pFileHash['thumbnail_sizes'] ) ) {
			// jpeg version of original
			if( preg_match( '/pdf/i', $pFileHash['type'] ) ) {
				// has a customer pdf rasterization function been defined?
				if( function_exists( 'liberty_rasterize_pdf' ) && $rasteredFile = liberty_rasterize_pdf( $pFileHash['source_file'] ) ) {
					$pFileHash['source_file'] = $rasteredFile;
				}
/*					$magickWand = NewMagickWand();
					if( !$pFileHash['error'] = liberty_magickwand_check_error( MagickReadImage( $magickWand, $pFileHash['source_file'] ), $magickWand )) {
						MagickSetFormat( $magickWand, 'JPG' );
						if( MagickGetImageColorspace( $magickWand ) == MW_CMYKColorspace ) {
							MagickProfileImage( $magickWand,"ICC", UTIL_PKG_PATH.'icc/srgb.icm' );
							MagickSetImageColorspace( $magickWand, MW_sRGBColorspace );
						}

						$imgWidth = MagickGetImageWidth( $magickWand );
						$imgHeight = MagickGetImageHeight( $magickWand );

						MagickSetImageUnits( $magickWand, MW_PixelsPerInchResolution );
						MagickSetResolution( $magickWand, 300, 300 );
						$rasteredFile = dirname( $pFileHash['source_file'] ).'/original.jpg';
						if( !$pFileHash['error'] = liberty_magickwand_check_error( MagickWriteImage( $magickWand, $rasteredFile ), $magickWand )) {
							$pFileHash['source_file'] = $rasteredFile;
						}
					}
*/

			} else {
				$pFileHash['dest_base_name'] = 'original';
				$pFileHash['name'] = 'original.jpg';
				$pFileHash['max_width'] = MAX_THUMBNAIL_DIMENSION;
				$pFileHash['max_height'] = MAX_THUMBNAIL_DIMENSION;
				if( $convertedFile = $resizeFunc( $pFileHash )) {
					$pFileHash['source_file'] = $convertedFile;
					$ret = true;
				}
			}
			$pFileHash['type'] = $gBitSystem->verifyMimeType( $pFileHash['source_file'] );
		}

		// override $mimeExt if we have a custom setting for it
		if( $gBitSystem->isFeatureActive( 'liberty_thumbnail_format' )) {
			$mimeExt = $gBitSystem->getConfig( 'liberty_thumbnail_format' );
		} else {
			list( $type, $mimeExt ) = preg_split( '#/#', strtolower( $pFileHash['type'] ));
		}

		$destExt = preg_match( "!(png|gif)!", $mimeExt ) ? '.'.$mimeExt : '.jpg';

		$initialDestPath = $pFileHash['dest_branch'];
		foreach( $pFileHash['thumbnail_sizes'] as $thumbSize ) {
			if( isset( $gThumbSizes[$thumbSize] )) {
				$pFileHash['dest_base_name'] = $thumbSize;
				$pFileHash['name'] = $thumbSize.$destExt;
				if( !empty( $gThumbSizes[$thumbSize]['width'] )) {
					$pFileHash['max_width'] = $gThumbSizes[$thumbSize]['width'];
				} else {
					// Have to unset since we reuse $pFileHash
					unset( $pFileHash['max_width'] );
				}

				// reset dest_branch for created thumbs
				if( !empty( $pFileHash['thumb_path'] ) ) {
					$pFileHash['dest_file'] = $pFileHash['thumb_path'].$pFileHash['name'];
				} else {
					// create a subdirectory for the thumbs
					$pFileHash['dest_branch'] = $initialDestPath.'thumbs/';
					clearstatcache();
					if( !is_dir( STORAGE_PKG_PATH.$pFileHash['dest_branch'] )) {
						@mkdir( STORAGE_PKG_PATH.$pFileHash['dest_branch'], 0775, true );
						clearstatcache();
					}
				}

				if( !empty( $gThumbSizes[$thumbSize]['height'] )) {
					$pFileHash['max_height'] = $gThumbSizes[$thumbSize]['height'];
				} else {
					// Have to unset since we reuse $pFileHash
					unset( $pFileHash['max_height'] );
				}
				if( $pFileHash['icon_thumb_path'] = $resizeFunc( $pFileHash )) {
					$ret = true;
					// use the previous thumb as the source for the next, decreasingly smaller thumb as this GREATLY increases speed
					$pFileHash['source_file'] = $pFileHash['icon_thumb_path'];
				}
			}
		}

		// to keep everything in bitweaver working smoothly, we need to remove the thumbs/ subdir again
		$pFileHash['dest_branch'] = $initialDestPath;
	}

	return $ret;
}

/**
 * fetch all available thumbnails for a given item. if no thumbnails are present, get thumbnailing image or the appropriate mime type icon
 *
 * @param array   $pParamHash Hash of all settings used to fetch thumbnails, including source_file, default_image, thumbnail_sizes, and mime_image
 * @access public
 * @return array of available thumbnails or mime icons
 * TODO: individual options are only for legacy reasons - remove options and deprecated() soon - xing - Monday Jun 23, 2008   22:36:53 CEST
 */
function liberty_fetch_thumbnails( $pParamHash ) {
	global $gBitSystem, $gThumbSizes;
	$ret = [];

	if( !empty( $pParamHash['source_file'] )) {
		if( empty( $pParamHash['thumbnail_sizes'] )) {
			$pParamHash['thumbnail_sizes'] = array_keys( $gThumbSizes );
		}

		// liberty file processors automatically pick the best format for us. we can force a format though.
		// using array_unique will give us the best order in which to look for the thumbnails
		$exts = array_unique( [ $gBitSystem->getConfig( 'liberty_thumbnail_format', 'jpg' ), 'jpg', 'png', 'gif', 'x-jpeg' ]);

		// short hand
		$path = &$pParamHash['source_file'];
		// $path might already be the absolute path or it might already contain BIT_ROOT_URL
		if( !( $path = preg_replace( "!^".preg_quote( STORAGE_PKG_PATH, "!" )."!", "", $path ))) {
			$path = preg_replace( "!^".preg_quote( STORAGE_PKG_URL, "!" )."!", "", $path );
		}

		// remove the filename if there is one (we can't just use dirname() becuase we might only have the path to the dir)
		$dir = substr( $path, 0, strrpos( $path, '/' ) + 1 );
		// assume thumb sizes are from largest to smallest. reverse so smaller can be used if larger don't exist
		$lastSize = null;
		foreach( array_reverse( $pParamHash['thumbnail_sizes'] ) as $size ) {
			foreach( $exts as $ext ) {
				$image = $size.'.'.$ext;
				$thumbDir = is_dir( STORAGE_PKG_PATH.$dir.'thumbs/' ) ?  $dir.'thumbs/' :  $dir;
				if( is_readable( STORAGE_PKG_PATH.$thumbDir.$image )) {
					$ret[$size] = (empty( $_REQUEST['uri_mode'] ) ? STORAGE_PKG_URL : STORAGE_BASE_URI).$thumbDir.$image;
				}
			}
			// fetch mime image unless we set this to false
			if(( !isset( $pParamHash['mime_image'] ) || $pParamHash['mime_image'] === true ) && empty( $ret[$size] )) {
				if( $lastSize && !empty( $ret[$lastSize] ) ) {
					$ret[$size] = $ret[$lastSize];
				}
			}
			$lastSize = $size;
		}

		// default if nothing else is available
		foreach( array_reverse( $pParamHash['thumbnail_sizes'] ) as $size ) {
			if( empty( $ret[$size] ) ) {
				if( !empty( $pParamHash['default_image'] )) {
					$ret[$size] = $pParamHash['default_image'];
				} else {
					// we need to make sure we have an image name that we can look up the mime type
					$path .= strrpos( $dir, '/' ) == strlen( $path ) ? 'dummy.jpg' : basename( $path );
					$ret[$size] = LibertySystem::getMimeThumbnailURL( $gBitSystem->lookupMimeType( $path ), substr( $path, strrpos( $path, '.' ) + 1 ));
				}
			}
		}
	}

	return $ret;
}

/**
 * fetch a single available thumbnail for a given item. if no thumbnail is present, return null
 *
 * @param array   $pParamHash Hash of all settings used to fetch thumbnails including: size, source_file, default_image, and mime_image
 * @access public
 * @return string url
 * TODO: individual options are only for legacy reasons - remove options and deprecated() soon - xing - Monday Jun 23, 2008   22:36:53 CEST
 */
function liberty_fetch_thumbnail_url( $pParamHash ) {
	if( !empty( $pParamHash['source_file'] )) {
		if( empty( $pParamHash['size'] )) {
			$pParamHash['size'] = 'small';
		}

		$pParamHash['thumbnail_sizes'] = [ $pParamHash['size'] ];
		$ret = liberty_fetch_thumbnails( $pParamHash );
	}
	return !empty( $ret[$pParamHash['size']] ) ? $ret[$pParamHash['size']] : null;
}

/**
 * get a set of image size options based on $gThumbSizes
 *
 * @param string $pEmptyOption string to use as empty option - if set to false no empty string is eincluded - Note that string is KernelTools::tra()'d
 * @access public
 * @return array of image size options suitable for use in a form
 */
function get_image_size_options( $pEmptyOption = 'Disable this feature' ) {
	global $gThumbSizes;
	$ret = [];
	if( !empty( $pEmptyOption )) {
		$ret[''] = KernelTools::tra( $pEmptyOption );
	}
	foreach( $gThumbSizes as $key => $size ) {
		$ret[$key] = KernelTools::tra( ucfirst( $key ))." ( ". ( empty( $size['width'] ) ? KernelTools::tra( 'unlimited' ) : $size['width'] ) ." x ". ( empty($size['height'] ) ? KernelTools::tra('unlimited') : $size['height'] ) ." ".KernelTools::tra( 'pixels' )." )";
	}
	return $ret;
}

/**
 * get_leadtitle will fetch the string before the liberty_subtitle_delimiter
 *
 * @param string $pString string that should be checked for the delimiter
 * @access public
 * @return string
 */
function get_leadtitle( $pString ) {
	global $gBitSystem;
	return substr( $pString, 0, strpos( $pString, $gBitSystem->getConfig( 'liberty_subtitle_delimiter', ':' )));
}

/**
 * get_subtitle will fetch the string after the liberty_subtitle_delimiter
 *
 * @param string $pString string that should be checked for the delimiter
 * @access public
 * @return string
 */
function get_subtitle( $pString ) {
	global $gBitSystem;
	if(( $start = strpos( $pString, $gBitSystem->getConfig( 'liberty_subtitle_delimiter', ':' ))) !== false ) {
		return substr( $pString, $start + 1 );
	}
	return '';
}