# HG changeset patch # User Dan # Date 1198207387 18000 # Node ID dc838fd61a06b0f078dc2990924a23d99c30058c # Parent c2f4c900c5075156c0c354035b208572e4c38ffa Added initial avatar support. Currently rather feature complete except for admin controls for avatar. diff -r c2f4c900c507 -r dc838fd61a06 includes/clientside/static/comments.js --- a/includes/clientside/static/comments.js Wed Dec 19 23:04:17 2007 -0500 +++ b/includes/clientside/static/comments.js Thu Dec 20 22:23:07 2007 -0500 @@ -185,6 +185,14 @@ if ( this_comment.user_id > 1 ) tplvars.NAME = '' + this_comment.name + ''; + // Avatar + if ( this_comment.user_has_avatar == '1' ) + { + tplvars.AVATAR_URL = scriptPath + '/' + data.avatar_directory + '/' + this_comment.user_id + '.' + this_comment.avatar_type; + tplvars.USERPAGE_LINK = makeUrlNS('User', this_comment.name); + tplvars.AVATAR_ALT = $lang.get('usercp_avatar_image_alt', { username: this_comment.name }); + } + // User level tplvars.USER_LEVEL = $lang.get('user_type_guest'); if ( this_comment.user_level >= data.user_level.member ) tplvars.USER_LEVEL = $lang.get('user_type_member'); @@ -217,6 +225,7 @@ tplbool.auth_mod = data.auth_mod_comments; tplbool.is_friend = ( this_comment.is_buddy == 1 && this_comment.is_friend == 1 ); tplbool.is_foe = ( this_comment.is_buddy == 1 && this_comment.is_friend == 0 ); + tplbool.user_has_avatar = ( this_comment.user_has_avatar == '1' ); if ( tplbool.is_friend ) tplvars.USER_LEVEL += '
' + $lang.get('comment_on_friend_list') + ''; @@ -444,6 +453,14 @@ if ( data.user_level >= data.user_level_list.mod ) tplvars.USER_LEVEL = $lang.get('user_type_mod'); if ( data.user_level >= data.user_level_list.admin ) tplvars.USER_LEVEL = $lang.get('user_type_admin'); + // Avatar + if ( data.user_has_avatar == '1' ) + { + tplvars.AVATAR_URL = scriptPath + '/' + data.avatar_directory + '/' + data.user_id + '.' + data.avatar_type; + tplvars.USERPAGE_LINK = makeUrlNS('User', data.name); + tplvars.AVATAR_ALT = $lang.get('usercp_avatar_image_alt', { username: data.name }); + } + // Send PM link tplvars.SEND_PM_LINK=(data.user_id>1)?'' + $lang.get('comment_btn_send_privmsg') + '
':''; @@ -468,6 +485,7 @@ tplbool.signature = ( data.signature == '' ) ? false : true; tplbool.can_edit = ( data.auth_edit_comments && ( ( data.user_id == data.user_id && data.logged_in ) || data.auth_mod_comments ) ); tplbool.auth_mod = data.auth_mod_comments; + tplbool.user_has_avatar = ( data.user_has_avatar == '1' ); parser.assign_vars(tplvars); parser.assign_bool(tplbool); diff -r c2f4c900c507 -r dc838fd61a06 includes/comment.php --- a/includes/comment.php Wed Dec 19 23:04:17 2007 -0500 +++ b/includes/comment.php Thu Dec 20 22:23:07 2007 -0500 @@ -97,6 +97,7 @@ } $ret = Array(); $ret['mode'] = $data['mode']; + $ret['avatar_directory'] = getConfig('avatar_directory'); switch ( $data['mode'] ) { case 'fetch': @@ -106,14 +107,14 @@ { $ret['template'] = file_get_contents(ENANO_ROOT . '/themes/' . $template->theme . '/comment.tpl'); } - $q = $db->sql_query('SELECT c.comment_id,c.name,c.subject,c.comment_data,c.time,c.approved,u.user_level,u.user_id,u.signature, b.buddy_id IS NOT NULL AS is_buddy, ( b.is_friend IS NOT NULL AND b.is_friend=1 ) AS is_friend FROM '.table_prefix.'comments AS c + $q = $db->sql_query('SELECT c.comment_id,c.name,c.subject,c.comment_data,c.time,c.approved,u.user_level,u.user_id,u.signature,u.user_has_avatar,u.avatar_type, b.buddy_id IS NOT NULL AS is_buddy, ( b.is_friend IS NOT NULL AND b.is_friend=1 ) AS is_friend FROM '.table_prefix.'comments AS c LEFT JOIN '.table_prefix.'users AS u ON (u.user_id=c.user_id) LEFT JOIN '.table_prefix.'buddies AS b ON ( ( b.user_id=' . $session->user_id.' AND b.buddy_user_id=c.user_id ) OR b.user_id IS NULL) WHERE page_id=\'' . $this->page_id . '\' AND namespace=\'' . $this->namespace . '\' - GROUP BY c.comment_id,c.name,c.subject,c.comment_data,c.time,c.approved,u.user_level,u.user_id,u.signature,b.buddy_id,b.is_friend + GROUP BY c.comment_id,c.name,c.subject,c.comment_data,c.time,c.approved,u.user_level,u.user_id,u.signature,u.user_has_avatar,u.avatar_type,b.buddy_id,b.is_friend ORDER BY c.time ASC;'); $count_appr = 0; $count_total = 0; @@ -302,7 +303,7 @@ $db->die_json(); // Re-fetch - $q = $db->sql_query('SELECT c.comment_id,c.name,c.subject,c.comment_data,c.time,c.approved,u.user_level,u.user_id,u.signature FROM '.table_prefix.'comments AS c + $q = $db->sql_query('SELECT c.comment_id,c.name,c.subject,c.comment_data,c.time,c.approved,u.user_level,u.user_id,u.signature,u.user_has_avatar,u.avatar_type FROM '.table_prefix.'comments AS c LEFT JOIN '.table_prefix.'users AS u ON (u.user_id=c.user_id) WHERE page_id=\'' . $this->page_id . '\' @@ -334,7 +335,7 @@ $ret['user_level_list']['member'] = USER_LEVEL_MEMBER; $ret['user_level_list']['mod'] = USER_LEVEL_MOD; $ret['user_level_list']['admin'] = USER_LEVEL_ADMIN; - + $ret['avatar_directory'] = getConfig('avatar_directory'); } break; diff -r c2f4c900c507 -r dc838fd61a06 includes/constants.php --- a/includes/constants.php Wed Dec 19 23:04:17 2007 -0500 +++ b/includes/constants.php Thu Dec 20 22:23:07 2007 -0500 @@ -73,6 +73,7 @@ define('MAX_PMS_PER_BATCH', 7); // The maximum number of users that users can send PMs to in one go; restriction does not apply to users with mod_misc rights define('SEARCH_RESULTS_PER_PAGE', 10); define('MYSQL_MAX_PACKET_SIZE', 1048576); // 1MB; this is the default in MySQL 4.x I think +define('ENANO_SUPPORT_AVATARS', 1); // Sidebar @@ -460,3 +461,38 @@ } unset($k, $s, $x); + +/****************************************************************************** +* Global Variable: JPEG_Segment_Names +* +* Contents: The names of the JPEG segment markers, indexed by their marker number. This data is from the PHP JPEG Metadata Toolkit. Licensed under the GPLv2 or later. +* +******************************************************************************/ + +$GLOBALS[ "JPEG_Segment_Names" ] = array( + 0xC0 => "SOF0", 0xC1 => "SOF1", 0xC2 => "SOF2", 0xC3 => "SOF4", + 0xC5 => "SOF5", 0xC6 => "SOF6", 0xC7 => "SOF7", 0xC8 => "JPG", + 0xC9 => "SOF9", 0xCA => "SOF10", 0xCB => "SOF11", 0xCD => "SOF13", + 0xCE => "SOF14", 0xCF => "SOF15", + 0xC4 => "DHT", 0xCC => "DAC", + + 0xD0 => "RST0", 0xD1 => "RST1", 0xD2 => "RST2", 0xD3 => "RST3", + 0xD4 => "RST4", 0xD5 => "RST5", 0xD6 => "RST6", 0xD7 => "RST7", + + 0xD8 => "SOI", 0xD9 => "EOI", 0xDA => "SOS", 0xDB => "DQT", + 0xDC => "DNL", 0xDD => "DRI", 0xDE => "DHP", 0xDF => "EXP", + + 0xE0 => "APP0", 0xE1 => "APP1", 0xE2 => "APP2", 0xE3 => "APP3", + 0xE4 => "APP4", 0xE5 => "APP5", 0xE6 => "APP6", 0xE7 => "APP7", + 0xE8 => "APP8", 0xE9 => "APP9", 0xEA => "APP10", 0xEB => "APP11", + 0xEC => "APP12", 0xED => "APP13", 0xEE => "APP14", 0xEF => "APP15", + + + 0xF0 => "JPG0", 0xF1 => "JPG1", 0xF2 => "JPG2", 0xF3 => "JPG3", + 0xF4 => "JPG4", 0xF5 => "JPG5", 0xF6 => "JPG6", 0xF7 => "JPG7", + 0xF8 => "JPG8", 0xF9 => "JPG9", 0xFA => "JPG10", 0xFB => "JPG11", + 0xFC => "JPG12", 0xFD => "JPG13", + + 0xFE => "COM", 0x01 => "TEM", 0x02 => "RES", +); + diff -r c2f4c900c507 -r dc838fd61a06 includes/functions.php --- a/includes/functions.php Wed Dec 19 23:04:17 2007 -0500 +++ b/includes/functions.php Thu Dec 20 22:23:07 2007 -0500 @@ -3407,6 +3407,405 @@ return false; } +/** + * Determines whether a GIF file is animated or not. Credit goes to ZeBadger from . + * Modified to conform to Enano coding standards. + * @param string Path to GIF file + * @return bool If animated, returns true + */ + + +function is_gif_animated($filename) +{ + $filecontents = @file_get_contents($filename); + if ( empty($filecontents) ) + return false; + + $str_loc = 0; + $count = 0; + while ( $count < 2 ) // There is no point in continuing after we find a 2nd frame + { + $where1 = strpos($filecontents,"\x00\x21\xF9\x04", $str_loc); + if ( $where1 === false ) + { + break; + } + else + { + $str_loc = $where1 + 1; + $where2 = strpos($filecontents,"\x00\x2C", $str_loc); + if ( $where2 === false ) + { + break; + } + else + { + if ( $where1 + 8 == $where2 ) + { + $count++; + } + $str_loc = $where2 + 1; + } + } + } + + return ( $count > 1 ) ? true : false; +} + +/** + * Retrieves the dimensions of a GIF image. + * @param string The path to the GIF file. + * @return array Key 0 is width, key 1 is height + */ + +function gif_get_dimensions($filename) +{ + $filecontents = @file_get_contents($filename); + if ( empty($filecontents) ) + return false; + if ( strlen($filecontents) < 10 ) + return false; + + $width = substr($filecontents, 6, 2); + $height = substr($filecontents, 8, 2); + $width = unpack('v', $width); + $height = unpack('v', $height); + return array($width[1], $height[1]); +} + +/** + * Determines whether a PNG image is animated or not. Based on some specification information from . + * @param string Path to PNG file. + * @return bool If animated, returns true + */ + +function is_png_animated($filename) +{ + $filecontents = @file_get_contents($filename); + if ( empty($filecontents) ) + return false; + + $parsed = parse_png($filecontents); + if ( !$parsed ) + return false; + + if ( !isset($parsed['fdAT']) ) + return false; + + if ( count($parsed['fdAT']) > 1 ) + return true; +} + +/** + * Gets the dimensions of a PNG image. + * @param string Path to PNG file + * @return array Key 0 is width, key 1 is length. + */ + +function png_get_dimensions($filename) +{ + $filecontents = @file_get_contents($filename); + if ( empty($filecontents) ) + return false; + + $parsed = parse_png($filecontents); + if ( !$parsed ) + return false; + + $ihdr_stream = $parsed['IHDR'][0]; + $width = substr($ihdr_stream, 0, 4); + $height = substr($ihdr_stream, 4, 4); + $width = unpack('N', $width); + $height = unpack('N', $height); + $x = $width[1]; + $y = $height[1]; + return array($x, $y); +} + +/** + * Internal function to parse out the streams of a PNG file. Based on the W3 PNG spec: http://www.w3.org/TR/PNG/ + * @param string The contents of the PNG + * @return array Associative array containing the streams + */ + +function parse_png($data) +{ + // Trim off first 8 bytes to check for PNG header + $header = substr($data, 0, 8); + if ( $header != "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" ) + { + return false; + } + $return = array(); + $data = substr($data, 8); + while ( strlen($data) > 0 ) + { + $chunklen_bin = substr($data, 0, 4); + $chunk_type = substr($data, 4, 4); + $chunklen = unpack('N', $chunklen_bin); + $chunklen = $chunklen[1]; + $chunk_data = substr($data, 8, $chunklen); + + // If the chunk type is not valid, this may be a malicious PNG with bad offsets. Break out of the loop. + if ( !preg_match('/^[A-z]{4}$/', $chunk_type) ) + break; + + if ( !isset($return[$chunk_type]) ) + $return[$chunk_type] = array(); + $return[$chunk_type][] = $chunk_data; + + $offset_next = 4 // Length + + 4 // Type + + $chunklen // Data + + 4; // CRC + $data = substr($data, $offset_next); + } + return $return; +} + +/** + * Retreives information about the intrinsic characteristics of the jpeg image, such as Bits per Component, Height and Width. This function is from the PHP JPEG Metadata Toolkit. Licensed under the GPLv2 or later. + * @param array The JPEG header data, as retrieved from the get_jpeg_header_data function + * @return array An array containing the intrinsic JPEG values FALSE - if the comment segment couldnt be found + * @license GNU General Public License + * @copyright Copyright Evan Hunter 2004 + */ + +function get_jpeg_intrinsic_values( $jpeg_header_data ) +{ + // Create a blank array for the output + $Outputarray = array( ); + + //Cycle through the header segments until Start Of Frame (SOF) is found or we run out of segments + $i = 0; + while ( ( $i < count( $jpeg_header_data) ) && ( substr( $jpeg_header_data[$i]['SegName'], 0, 3 ) != "SOF" ) ) + { + $i++; + } + + // Check if a SOF segment has been found + if ( substr( $jpeg_header_data[$i]['SegName'], 0, 3 ) == "SOF" ) + { + // SOF segment was found, extract the information + + $data = $jpeg_header_data[$i]['SegData']; + + // First byte is Bits per component + $Outputarray['Bits per Component'] = ord( $data{0} ); + + // Second and third bytes are Image Height + $Outputarray['Image Height'] = ord( $data{ 1 } ) * 256 + ord( $data{ 2 } ); + + // Forth and fifth bytes are Image Width + $Outputarray['Image Width'] = ord( $data{ 3 } ) * 256 + ord( $data{ 4 } ); + + // Sixth byte is number of components + $numcomponents = ord( $data{ 5 } ); + + // Following this is a table containing information about the components + for( $i = 0; $i < $numcomponents; $i++ ) + { + $Outputarray['Components'][] = array ( 'Component Identifier' => ord( $data{ 6 + $i * 3 } ), + 'Horizontal Sampling Factor' => ( ord( $data{ 7 + $i * 3 } ) & 0xF0 ) / 16, + 'Vertical Sampling Factor' => ( ord( $data{ 7 + $i * 3 } ) & 0x0F ), + 'Quantization table destination selector' => ord( $data{ 8 + $i * 3 } ) ); + } + } + else + { + // Couldn't find Start Of Frame segment, hence can't retrieve info + return FALSE; + } + + return $Outputarray; +} + +/** + * Reads all the JPEG header segments from an JPEG image file into an array. This function is from the PHP JPEG Metadata Toolkit. Licensed under the GPLv2 or later. Modified slightly for Enano coding standards and to remove unneeded capability. + * @param string the filename of the file to JPEG file to read + * @return string Array of JPEG header segments, or FALSE - if headers could not be read + * @license GNU General Public License + * @copyright Copyright Evan Hunter 2004 + */ + +function get_jpeg_header_data( $filename ) +{ + // Attempt to open the jpeg file - the at symbol supresses the error message about + // not being able to open files. The file_exists would have been used, but it + // does not work with files fetched over http or ftp. + $filehnd = @fopen($filename, 'rb'); + + // Check if the file opened successfully + if ( ! $filehnd ) + { + // Could't open the file - exit + return FALSE; + } + + + // Read the first two characters + $data = fread( $filehnd, 2 ); + + // Check that the first two characters are 0xFF 0xDA (SOI - Start of image) + if ( $data != "\xFF\xD8" ) + { + // No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return; + fclose($filehnd); + return FALSE; + } + + + // Read the third character + $data = fread( $filehnd, 2 ); + + // Check that the third character is 0xFF (Start of first segment header) + if ( $data{0} != "\xFF" ) + { + // NO FF found - close file and return - JPEG is probably corrupted + fclose($filehnd); + return FALSE; + } + + // Flag that we havent yet hit the compressed image data + $hit_compressed_image_data = FALSE; + + + // Cycle through the file until, one of: 1) an EOI (End of image) marker is hit, + // 2) we have hit the compressed image data (no more headers are allowed after data) + // 3) or end of file is hit + + while ( ( $data{1} != "\xD9" ) && (! $hit_compressed_image_data) && ( ! feof( $filehnd ) )) + { + // Found a segment to look at. + // Check that the segment marker is not a Restart marker - restart markers don't have size or data after them + if ( ( ord($data{1}) < 0xD0 ) || ( ord($data{1}) > 0xD7 ) ) + { + // Segment isn't a Restart marker + // Read the next two bytes (size) + $sizestr = fread( $filehnd, 2 ); + + // convert the size bytes to an integer + $decodedsize = unpack ("nsize", $sizestr); + + // Save the start position of the data + $segdatastart = ftell( $filehnd ); + + // Read the segment data with length indicated by the previously read size + $segdata = fread( $filehnd, $decodedsize['size'] - 2 ); + + + // Store the segment information in the output array + $headerdata[] = array( "SegType" => ord($data{1}), + "SegName" => $GLOBALS[ "JPEG_Segment_Names" ][ ord($data{1}) ], + "SegDataStart" => $segdatastart, + "SegData" => $segdata ); + } + + // If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows + if ( $data{1} == "\xDA" ) + { + // Flag that we have hit the compressed image data - exit loop as no more headers available. + $hit_compressed_image_data = TRUE; + } + else + { + // Not an SOS - Read the next two bytes - should be the segment marker for the next segment + $data = fread( $filehnd, 2 ); + + // Check that the first byte of the two is 0xFF as it should be for a marker + if ( $data{0} != "\xFF" ) + { + // NO FF found - close file and return - JPEG is probably corrupted + fclose($filehnd); + return FALSE; + } + } + } + + // Close File + fclose($filehnd); + + // Return the header data retrieved + return $headerdata; +} + +/** + * Returns the dimensions of a JPEG image in the same format as {php,gif}_get_dimensions(). + * @param string JPEG file to check + * @return array Key 0 is width, key 1 is height + */ + +function jpg_get_dimensions($filename) +{ + if ( !file_exists($filename) ) + { + echo "Doesn't exist
"; + return false; + } + + $headers = get_jpeg_header_data($filename); + if ( !$headers ) + { + echo "Bad headers
"; + return false; + } + + $metadata = get_jpeg_intrinsic_values($headers); + if ( !$metadata ) + { + echo "Bad metadata:
" . print_r($metadata, true) . "

"; + return false; + } + + if ( !isset($metadata['Image Width']) || !isset($metadata['Image Height']) ) + { + echo "No metadata
"; + return false; + } + + return array($metadata['Image Width'], $metadata['Image Height']); +} + +/** + * Generates a URL for the avatar for the given user ID and avatar type. + * @param int User ID + * @param string Image type - must be one of jpg, png, or gif. + * @return string + */ + +function make_avatar_url($user_id, $avi_type) +{ + if ( !is_int($user_id) ) + return false; + if ( !in_array($avi_type, array('png', 'gif', 'jpg')) ) + return false; + return scriptPath . '/' . getConfig('avatar_directory') . '/' . $user_id . '.' . $avi_type; +} + +/** + * Determines an image's filetype based on its signature. + * @param string Path to image file + * @return string One of gif, png, or jpg, or false if none of these. + */ + +function get_image_filetype($filename) +{ + $filecontents = @file_get_contents($filename); + if ( empty($filecontents) ) + return false; + + if ( substr($filecontents, 0, 8) == "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" ) + return 'png'; + + if ( substr($filecontents, 0, 6) == 'GIF87a' || substr($filecontents, 0, 6) == 'GIF89a' ) + return 'gif'; + + if ( substr($filecontents, 0, 2) == "\xFF\xD8" ) + return 'jpg'; + + return false; +} + //die('
Original:  01010101010100101010100101010101011010'."\nProcessed: ".uncompress_bitfield(compress_bitfield('01010101010100101010100101010101011010')).'
'); ?> diff -r c2f4c900c507 -r dc838fd61a06 includes/http.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/includes/http.php Thu Dec 20 22:23:07 2007 -0500 @@ -0,0 +1,792 @@ +method == GET. + * @var array (associative) + */ + + var $parms_post = array(); + + /** + * The list of cookies that will be sent. + * @var array (associative) + */ + + var $cookies_out = array(); + + /** + * Additional request headers. + * @var array (associative) + */ + + var $headers = array(); + + /** + * Cached response. + * @var string, or bool:false if the request hasn't been sent yet + */ + + var $response = false; + + /** + * Cached response code + * @var int set to -1 if request hasn't been sent yet + */ + + var $response_code = -1; + + /** + * Cached response code string + * @var string or bool:false if the request hasn't been sent yet + */ + + var $response_string = false; + + /** + * Resource for the socket. False if a connection currently isn't going. + * @var resource + */ + + var $socket = false; + + /** + * The state of our request. 0 means it hasn't been made yet. 1 means the socket is open, 2 means the socket is open and the request has been written, 3 means the headers have been fetched, and 4 means the request is completed. + * @var int + */ + + var $state = 0; + + /** + * Constructor. + * @param string Hostname to send to + * @param string URI (/index.php) + * @param string Request method - GET or POST. + * @param int Optional. The port to open the request on. Defaults to 80. + */ + + function Request_HTTP($host, $uri, $method = 'GET', $port = 80) + { + if ( !preg_match('/^(([a-z0-9-]+\.)*?)([a-z0-9-]+)$/', $host) ) + die(__CLASS__ . ': Invalid hostname'); + $this->host = $host; + $this->uri = $uri; + if ( is_int($port) && $port >= 1 && $port <= 65535 ) + $this->port = $port; + else + die(__CLASS__ . ': Invalid port'); + $method = strtoupper($method); + if ( $method == 'GET' || $method == 'POST' ) + $this->method = $method; + else + die(__CLASS__ . ': Invalid request method'); + + $newline = "\r\n"; + $php_ver = PHP_VERSION; + $this->add_header('User-Agent', "PHP/$php_ver (Server: {$_SERVER['SERVER_SOFTWARE']}; automated bot request)"); + } + + /** + * Sets one or more cookies to be sent to the server. + * @param string or array If a string, the cookie name. If an array, associative array in the form of cookiename => cookievalue + * @param string or bool If a string, the cookie value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. + */ + + function add_cookie($cookiename, $cookievalue = false) + { + if ( is_array($cookiename) && !$cookievalue ) + { + foreach ( $cookiename as $name => $value ) + { + $this->cookies_out[$name] = $value; + } + } + else if ( is_string($cookiename) && is_string($cookievalue) ) + { + $this->cookies_out[$cookiename] = $cookievalue; + } + else + { + die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)'); + } + } + + /** + * Sets one or more request header values. + * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue + * @param string or bool If a string, the header value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. + */ + + function add_header($headername, $headervalue = false) + { + if ( is_array($headername) && !$headervalue ) + { + foreach ( $headername as $name => $value ) + { + $this->headers[$name] = $value; + } + } + else if ( is_string($headername) && is_string($headervalue) ) + { + $this->headers[$headername] = $headervalue; + } + else + { + die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)'); + } + } + + /** + * Adds one or more values to be passed on GET. + * @param string or array If a string, the parameter name. If an array, associative array in the form of parametername => parametervalue + * @param string or bool If a string, the parameter value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. + */ + + function add_get($getname, $getvalue = false) + { + if ( is_array($getname) && !$getvalue ) + { + foreach ( $getname as $name => $value ) + { + $this->parms_get[$name] = $value; + } + } + else if ( is_string($getname) && is_string($getvalue) ) + { + $this->parms_get[$getname] = $getvalue; + } + else + { + die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)'); + } + } + + /** + * Adds one or more values to be passed on POST. + * @param string or array If a string, the header name. If an array, associative array in the form of headername => headervalue + * @param string or bool If a string, the header value. If boolean, defaults to false, param 1 should be an array, and this should not be passed. + */ + + function add_post($postname, $postvalue = false) + { + if ( is_array($postname) && !$postvalue ) + { + foreach ( $postname as $name => $value ) + { + $this->parms_post[$name] = $value; + } + } + else if ( is_string($postname) && is_string($postvalue) ) + { + $this->parms_post[$postname] = $postvalue; + } + else + { + die(__CLASS__ . '::' . __METHOD__ . ': Invalid argument(s)'); + } + } + + /** + * Internal function to open up the socket. + * @access private + */ + + function _sock_open(&$connection) + { + if ( $this->debug ) + { + echo '
'; + echo '

' . __CLASS__ . ': Sending request

Request parameters:

'; + echo "

Headers:

$headers
"; + echo "

Cookies: $cookies

"; + echo "

GET URI: " . htmlspecialchars($get) . "

"; + echo "

POST DATA: " . htmlspecialchars($post) . "

"; + } + + // Open connection + $connection = fsockopen($this->host, $this->port); + if ( !$connection ) + die(__CLASS__ . '::' . __METHOD__ . ': Could not make connection'); + + if ( $this->debug ) + echo '

Connection opened. Writing main request to socket. Raw socket data follows.

';
+    
+    // 1 = socket open
+    $this->state = 1;
+  }
+  
+  /**
+   * Internal function to actually write the request into the socket.
+   * @access private
+   */
+  
+  function _write_request(&$connection, &$headers, &$cookies, &$get, &$post)
+  {
+    $newline = "\r\n";
+    
+    $this->_fputs($connection, "{$this->method} {$this->uri}{$get} HTTP/1.1{$newline}");
+    $this->_fputs($connection, "Host: {$this->host}{$newline}");
+    $this->_fputs($connection, $headers);
+    $this->_fputs($connection, $cookies);
+    
+    if ( $this->method == 'POST' )
+    {
+      // POST-specific parameters
+      $post_length = strlen($post);
+      $this->_fputs($connection, "Content-type: application/x-www-form-urlencoded{$newline}");
+      $this->_fputs($connection, "Content-length: {$post_length}{$newline}");
+    }
+    
+    $this->_fputs($connection, "Connection: close{$newline}");
+    $this->_fputs($connection, "{$newline}");
+    
+    if ( $this->method == 'POST' )
+    {
+      $this->_fputs($connection, $post);
+    }
+    
+    if ( $this->debug )
+      echo '

Request written. Fetching response.

'; + + // 2 = request written + $this->state = 2; + } + + /** + * Wrap up and close the socket. Nothing more than a call to fsockclose() except in debug mode. + * @access private + */ + + function sock_close(&$connection) + { + if ( $this->debug ) + { + echo '

Response fetched. Closing connection. Response text follows.

';
+      echo htmlspecialchars($buffer);
+      echo '

'; + } + + fclose($connection); + } + + /** + * Internal function to grab the response code and status string + * @access string + */ + + function _parse_response_code($buffer) + { + // Retrieve response code and status + $pos_newline = strpos($buffer, "\n"); + $pos_carriage_return = strpos($buffer, "\r"); + $pos_end_first_line = ( $pos_carriage_return < $pos_newline && $pos_carriage_return > 0 ) ? $pos_carriage_return : $pos_newline; + + // First line is in format of: + // HTTP/1.1 ### Blah blah blah(\r?)\n + $response_code = substr($buffer, 9, 3); + $response_string = substr($buffer, 13, ( $pos_end_first_line - 13 ) ); + $this->response_code = intval($response_code); + $this->response_string = $response_string; + } + + /** + * Internal function to send the request. + * @access private + */ + + function _send_request() + { + $this->concat_headers($headers, $cookies, $get, $post); + + if ( $this->state < 1 ) + { + $this->_sock_open($this->socket); + } + if ( $this->state < 2 ) + { + $this->_write_request($this->socket, $headers, $cookies, $get, $post); + } + if ( $this->state == 2 ) + { + $buffer = $this->_read_until_newlines($this->socket); + $this->state = 3; + $this->_parse_response_code($buffer); + $this->response = $buffer; + } + if ( $this->state == 3 ) + { + // Determine transfer encoding + $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response); + + $buffer = ''; + while ( !feof($this->socket) ) + { + $part = fgets($this->socket, 1024); + if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) ) + { + $chunklen = hexdec($match[1]); + $part = ( $chunklen > 0 ) ? fread($this->socket, $chunklen) : ''; + // remove the last newline from $part + $part = preg_replace("/\r?\n\$/m", "", $part); + } + $buffer .= $part; + } + $this->response .= $buffer; + } + $this->state = 4; + + $this->sock_close($this->socket); + $this->socket = false; + } + + /** + * Internal function to send the request but only fetch the headers. Leaves a connection open for a finish-up function. + * @access private + */ + + function _send_request_headers_only() + { + $this->concat_headers($headers, $cookies, $get, $post); + + if ( $this->state < 1 ) + { + $this->_sock_open($this->socket); + } + if ( $this->state < 2 ) + { + $this->_write_request($this->socket, $headers, $cookies, $get, $post); + } + if ( $this->state == 2 ) + { + $buffer = $this->_read_until_newlines($this->socket); + $this->state = 3; + $this->_parse_response_code($buffer); + $this->response = $buffer; + } + } + + /** + * Internal function to read from a socket until two consecutive newlines are hit. + * @access private + */ + + function _read_until_newlines($sock) + { + $prev_char = ''; + $prev1_char = ''; + $prev2_char = ''; + $buf = ''; + while ( !feof($sock) ) + { + $chr = fread($sock, 1); + $buf .= $chr; + if ( ( $chr == "\n" && $prev_char == "\n" ) || + ( $chr == "\n" && $prev_char == "\r" && $prev1_char == "\n" && $prev2_char == "\r" ) ) + { + return $buf; + } + $prev2_char = $prev1_char; + $prev1_char = $prev_char; + $prev_char = $chr; + } + return $buf; + } + + /** + * Returns the response text. If the request hasn't been sent, it will be sent here. + * @return string + */ + + function get_response() + { + if ( $this->state == 4 ) + return $this->response; + $this->_send_request(); + return $this->response; + } + + /** + * Writes the response body to a file. This is good for conserving memory when downloading large files. If the file already exists it will be overwritten. + * @param string File to write to + * @param int Chunk size in KB to read from the socket. Optional and should only be needed in circumstances when extreme memory conservation is needed. Defaults to 768. + * @param int Maximum file size. Defaults to 0, which means no limit. + * @return bool True on success, false on failure + */ + + function write_response_to_file($file, $chunklen = 768, $max_file_size = 0) + { + if ( !is_writeable( dirname($file) ) || !file_exists( dirname($file) ) ) + { + return false; + } + $handle = @fopen($file, 'w'); + if ( !$handle ) + return false; + $chunklen = intval($chunklen); + if ( $chunklen < 1 ) + return false; + if ( $this->state == 4 ) + { + // we already have the response, so cheat + $response = $this->get_response_body(); + fwrite($handle, $response); + } + else + { + // read data from the socket, write it immediately, and unset to free memory + $headers = $this->get_response_headers(); + $transferred_bytes = 0; + $bandwidth_exceeded = false; + // if transfer-encoding is chunked, read using chunk sizes the server specifies + $is_chunked = preg_match("/Transfer-Encoding: (chunked)\r?\n/", $this->response); + if ( $is_chunked ) + { + $buffer = ''; + while ( !feof($this->socket) ) + { + $part = fgets($this->socket, ( 1024 * $chunklen )); + // Theoretically if the encoding is really chunked then this should always match. + if ( $is_chunked && preg_match("/^([a-f0-9]+)\x0D\x0A$/", $part, $match) ) + { + $chunk_length = hexdec($match[1]); + $part = ( $chunk_length > 0 ) ? fread($this->socket, $chunk_length) : ''; + // remove the last newline from $part + $part = preg_replace("/\r?\n\$/m", "", $part); + } + + $transferred_bytes += strlen($part); + if ( $max_file_size && $transferred_bytes > $max_file_size ) + { + // truncate output to $max_file_size bytes + $partlen = $max_file_size - ( $transferred_bytes - strlen($part) ); + $part = substr($part, 0, $partlen); + $bandwidth_exceeded = true; + } + fwrite($handle, $part); + if ( $bandwidth_exceeded ) + { + break; + } + } + } + else + { + $first_chunk = fread($this->socket, ( 1024 * $chunklen )); + fwrite($handle, $first_chunk); + while ( !feof($this->socket) ) + { + $chunk = fread($this->socket, ( 1024 * $chunklen )); + + $transferred_bytes += strlen($chunk); + if ( $max_file_size && $transferred_bytes > $max_file_size ) + { + // truncate output to $max_file_size bytes + $partlen = $max_file_size - ( $transferred_bytes - strlen($chunk) ); + $chunk = substr($chunk, 0, $partlen); + $bandwidth_exceeded = true; + } + + fwrite($handle, $chunk); + unset($chunk); + + if ( $bandwidth_exceeded ) + { + break; + } + } + } + } + fclose($handle); + // close socket and reset state, since we haven't cached the response + $this->sock_close($this->socket); + $this->state = 0; + return ($bandwidth_exceeded) ? false : true; + } + + /** + * Returns only the response headers. + * @return string + */ + + function get_response_headers() + { + if ( $this->state == 3 ) + { + return $this->response; + } + else if ( $this->state == 4 ) + { + $pos_end = strpos($this->response, "\r\n\r\n"); + $data = substr($this->response, 0, $pos_start); + return $data; + } + else + { + $this->_send_request_headers_only(); + return $this->response; + } + } + + /** + * Returns only the response headers, as an associative array. + * @return array + */ + + function get_response_headers_array() + { + $data = $this->get_response_headers(); + preg_match_all("/(^|\n)([A-z0-9_-]+?): (.+?)(\r|\n|\$)/", $data, $matches); + $headers = array(); + for ( $i = 0; $i < count($matches[0]); $i++ ) + { + $headers[ $matches[2][$i] ] = $matches[3][$i]; + } + return $headers; + } + + /** + * Returns only the body (not the headers) of the response. If the request hasn't been sent, it will be sent here. + * @return string + */ + + function get_response_body() + { + $data = $this->get_response(); + $pos_start = strpos($data, "\r\n\r\n") + 4; + $data = substr($data, $pos_start); + return $data; + } + + /** + * Returns all cookies requested to be set by the server as an associative array. If the request hasn't been sent, it will be sent here. + * @return array + */ + + function get_cookies() + { + $data = $this->get_response(); + $data = str_replace("\r\n", "\n", $data); + $pos = strpos($data, "\n\n"); + $headers = substr($data, 0, $pos); + preg_match_all("/Set-Cookie: ([a-z0-9_]+)=([^;]+);( expires=([^;]+);)?( path=(.*?))?\n/", $headers, $cookiematch); + if ( count($cookiematch[0]) < 1 ) + return array(); + $cookies = array(); + foreach ( $cookiematch[0] as $i => $cookie ) + { + $cookies[$cookiematch[1][$i]] = $cookiematch[2][$i]; + } + return $cookies; + } + + /** + * Internal method to write data to a socket with debugging possibility. + * @access private + */ + + function _fputs($socket, $data) + { + if ( $this->debug ) + echo htmlspecialchars($data); + return fputs($socket, $data); + } + + /** + * Internal function to stringify cookies, headers, get, and post. + * @access private + */ + + function concat_headers(&$headers, &$cookies, &$get, &$post) + { + $headers = ''; + $cookies = ''; + foreach ( $this->headers as $name => $value ) + { + $value = str_replace('\\n', '\\\\n', $value); + $value = str_replace("\n", '\\n', $value); + $headers .= "$name: $value\r\n"; + } + unset($value); + if ( count($this->cookies_out) > 0 ) + { + $i = 0; + $cookie_header = 'Cookie: '; + foreach ( $this->cookies_out as $name => $value ) + { + $i++; + if ( $i > 1 ) + $cookie_header .= '; '; + $value = str_replace(';', rawurlencode(';'), $value); + $value = str_replace('\\n', '\\\\n', $value); + $value = str_replace("\n", '\\n', $value); + $cookie_header .= "$name=$value"; + } + $cookie_header .= "\r\n"; + $cookies = $cookie_header; + unset($value, $cookie_header); + } + if ( count($this->parms_get) > 0 ) + { + $get = '?'; + $i = 0; + foreach ( $this->parms_get as $name => $value ) + { + $i++; + if ( $i > 1 ) + $get .= '&'; + $value = urlencode($value); + if ( !empty($value) ) + $get .= "$name=$value"; + else + $get .= "$name"; + } + } + if ( count($this->parms_post) > 0 ) + { + $post = ''; + $i = 0; + foreach ( $this->parms_post as $name => $value ) + { + $i++; + if ( $i > 1 ) + $post .= '&'; + $value = urlencode($value); + $post .= "$name=$value"; + } + } + } + +} + +?> diff -r c2f4c900c507 -r dc838fd61a06 includes/pageprocess.php --- a/includes/pageprocess.php Wed Dec 19 23:04:17 2007 -0500 +++ b/includes/pageprocess.php Thu Dec 20 22:23:07 2007 -0500 @@ -805,6 +805,7 @@ { global $db, $session, $paths, $template, $plugins; // Common objects global $email; + global $lang; $page_urlname = dirtify_page_id($this->page_id); if ( $this->page_id == $paths->page_id && $this->namespace == $paths->namespace ) @@ -839,14 +840,14 @@ $template->tpl_strings['PAGE_NAME'] = htmlspecialchars($page_name); - $q = $db->sql_query('SELECT u.username, u.user_id AS authoritative_uid, u.real_name, u.email, u.reg_time, x.*, COUNT(c.comment_id) AS n_comments + $q = $db->sql_query('SELECT u.username, u.user_id AS authoritative_uid, u.real_name, u.email, u.reg_time, u.user_has_avatar, u.avatar_type, x.*, COUNT(c.comment_id) AS n_comments FROM '.table_prefix.'users u LEFT JOIN '.table_prefix.'users_extra AS x ON ( u.user_id = x.user_id OR x.user_id IS NULL ) LEFT JOIN '.table_prefix.'comments AS c ON ( ( c.user_id=u.user_id AND c.name=u.username AND c.approved=1 ) OR ( c.comment_id IS NULL AND c.approved IS NULL ) ) WHERE u.username=\'' . $db->escape($target_username) . '\' - GROUP BY u.username, u.user_id, u.real_name, u.email, u.reg_time,x.user_id, x.user_aim, x.user_yahoo, x.user_msn, x.user_xmpp, x.user_homepage, x.user_location, x.user_job, x.user_hobbies, x.email_public;'); + GROUP BY u.username, u.user_id, u.real_name, u.email, u.reg_time, u.user_has_avatar, u.avatar_type, x.user_id, x.user_aim, x.user_yahoo, x.user_msn, x.user_xmpp, x.user_homepage, x.user_location, x.user_job, x.user_hobbies, x.email_public;'); if ( !$q ) $db->_die(); @@ -891,6 +892,10 @@ // Basic user info echo 'All about ' . htmlspecialchars($target_username) . ''; + if ( $userdata['user_has_avatar'] == '1' ) + { + echo '' . $lang->get('usercp_avatar_image_alt', array('username' => $userdata['username'])) . ''; + } echo 'Joined: ' . date('F d, Y h:i a', $userdata['reg_time']) . ''; echo 'Total comments: ' . $userdata['n_comments'] . ''; diff -r c2f4c900c507 -r dc838fd61a06 includes/pageutils.php --- a/includes/pageutils.php Wed Dec 19 23:04:17 2007 -0500 +++ b/includes/pageutils.php Thu Dec 20 22:23:07 2007 -0500 @@ -1005,7 +1005,7 @@ if(!$e) $db->_die('The comment text data could not be selected.'); $num_app = $db->numrows(); $db->free_result(); - $lq = $db->sql_query('SELECT c.comment_id,c.subject,c.name,c.comment_data,c.approved,c.time,c.user_id,u.user_level,u.signature + $lq = $db->sql_query('SELECT c.comment_id,c.subject,c.name,c.comment_data,c.approved,c.time,c.user_id,u.user_level,u.signature,u.user_has_avatar,u.avatar_type FROM ' . table_prefix.'comments AS c LEFT JOIN ' . table_prefix.'users AS u ON c.user_id=u.user_id @@ -1123,6 +1123,15 @@ $strings['SIGNATURE'] = ''; if($row['signature'] != '') $strings['SIGNATURE'] = RenderMan::render($row['signature']); + // Avatar + if ( $row['user_has_avatar'] == 1 ) + { + $bool['user_has_avatar'] = true; + $strings['AVATAR_ALT'] = $lang->get('usercp_avatar_image_alt', array('username' => $row['name'])); + $strings['AVATAR_URL'] = make_avatar_url(intval($row['user_id']), $row['avatar_type']); + $strings['USERPAGE_LINK'] = makeUrlNS('User', $row['name']); + } + $bool['auth_mod'] = ($session->get_permissions('mod_comments')) ? true : false; $bool['can_edit'] = ( ( $session->user_logged_in && $row['name'] == $session->username && $session->get_permissions('edit_comments') ) || $session->get_permissions('mod_comments') ) ? true : false; $bool['signature'] = ( $strings['SIGNATURE'] == '' ) ? false : true; diff -r c2f4c900c507 -r dc838fd61a06 language/english/enano.json --- a/language/english/enano.json Wed Dec 19 23:04:17 2007 -0500 +++ b/language/english/enano.json Thu Dec 20 22:23:07 2007 -0500 @@ -18,7 +18,7 @@ var enano_lang = { categories: [ 'adm', 'meta', 'user', 'page', 'comment', 'onpage', 'etc', 'editor', 'history', 'catedit', 'tags', 'delvote', 'ajax', 'sidebar', 'acl', - 'perm', + 'perm', 'usercp', ], strings: { meta: { @@ -151,6 +151,33 @@ reg_coppa_link_atleast13: 'I was born on or before %yo13_date% and am at least 13 years of age', reg_coppa_link_not13: 'I was born after %yo13_date% and am less than 13 years of age', }, + usercp: { + avatar_err_disabled_title: 'Avatar support is disabled.', + avatar_err_disabled_body: 'The administrator has not enabled avatar support for this site.', + avatar_table_title: 'Avatar settings', + avatar_label_current: 'Current avatar:', + avatar_image_alt: '%username%\'s avatar', + avatar_image_none: 'You don\'t have an avatar currently.', + avatar_lbl_change: 'Change your avatar:', + avatar_lbl_keep: 'Keep my current avatar', + avatar_lbl_remove: 'Delete my avatar', + avatar_lbl_set_http: 'Upload a new avatar from the Web', + avatar_lbl_set_file: 'Upload a new avatar from my computer', + avatar_lbl_url: 'URL to image:', + avatar_lbl_url_desc: 'This must start with the http:// prefix and must be a valid URL. The image will be copied from the existing URL to this server - dynamic avatars are not supported.', + avatar_lbl_file: 'Upload file:', + avatar_lbl_file_desc: 'Your browser needs to support file uploads for this option to work.', + avatar_limits: 'The image cannot be more than %config.avatar_max_size% bytes in size. The maximum dimensions are %config.avatar_max_width% × %config.avatar_max_height% pixels. Allowed formats are PNG, GIF, and JPEG.', + avatar_delete_success: 'Your avatar has been deleted.', + avatar_bad_write: 'Either the remote server had trouble finding the image, or your image exceeded the allowed file size.', + avatar_bad_filetype: 'The file you selected is invalid. You must choose a file in PNG, JPEG, or GIF format.', + avatar_disallowed_animation: 'You have chosen an animated image, which is not allowed. Please choose a non-animated image.', + avatar_corrupt_image: 'The image you selected is corrupt. Please choose another image.', + avatar_too_large: 'The image you uploaded exceeds the maximum dimensions (%config.avatar_max_width% × %config.avatar_max_height%px) allowed on this site. Please choose another image.', + avatar_move_failed: 'Your image was accepted, but there was a problem moving the image file to the correct location.', + avatar_upload_success: 'Your avatar has been updated.', + avatar_file_too_large: 'The image you uploaded exceeds the maximum file size allowed for avatars on this site.', + }, onpage: { lbl_pagetools: 'Page tools', lbl_page_article: 'article', diff -r c2f4c900c507 -r dc838fd61a06 plugins/SpecialAdmin.php --- a/plugins/SpecialAdmin.php Wed Dec 19 23:04:17 2007 -0500 +++ b/plugins/SpecialAdmin.php Thu Dec 20 22:23:07 2007 -0500 @@ -219,6 +219,35 @@ if ( in_array($_POST['lockout_policy'], array('disable', 'captcha', 'lockout')) ) setConfig('lockout_policy', $_POST['lockout_policy']); + // Avatar settings + setConfig('avatar_enable', ( isset($_POST['avatar_enable']) ? '1' : '0' )); + // for these next three values, set the config value if it's a valid integer; this is + // done by using strval(intval($foo)) === $foo, which flattens $foo to an integer and + // then converts it back to a string. This effectively verifies that var $foo is both + // set and that it's a valid string representing an integer. + setConfig('avatar_max_size', ( strval(intval($_POST['avatar_max_size'])) === $_POST['avatar_max_size'] ? $_POST['avatar_max_size'] : '10240' )); + setConfig('avatar_max_width', ( strval(intval($_POST['avatar_max_width'])) === $_POST['avatar_max_width'] ? $_POST['avatar_max_width'] : '96' )); + setConfig('avatar_max_height', ( strval(intval($_POST['avatar_max_height'])) === $_POST['avatar_max_height'] ? $_POST['avatar_max_height'] : '96' )); + setConfig('avatar_enable_anim', ( isset($_POST['avatar_enable_anim']) ? '1' : '0' )); + setConfig('avatar_upload_file', ( isset($_POST['avatar_upload_file']) ? '1' : '0' )); + setConfig('avatar_upload_http', ( isset($_POST['avatar_upload_http']) ? '1' : '0' )); + + if ( is_dir(ENANO_ROOT . '/' . $_POST['avatar_directory']) ) + { + if ( preg_match('/^([A-z0-9_-]+)(\/([A-z0-9_-]+))*$/', $_POST['avatar_directory']) ) + { + setConfig('avatar_directory', $_POST['avatar_directory']); + } + else + { + echo '
You have entered an invalid avatar directory.
'; + } + } + else + { + echo '
You have entered an invalid avatar directory.
'; + } + echo '
Your changes to the site configuration have been saved.

'; } @@ -245,7 +274,7 @@ - Wiki mode + Wiki mode @@ -278,7 +307,7 @@ - Statistics and hit counting + Statistics and hit counting Enano has the ability to show statistics for every page on the site. This allows you to keep very close track of who is visiting your site, and from where.

Unfortunately, some users don't like being logged. For this reason, you should state clearly what is logged (usually the username or IP address, current time, page name, and referer URL) in your privacy policy. If your site is primarily geared towards children, and you are a United States citizen, you are required to have a privacy policy stating exactly what is being logged under the terms of the Childrens' Online Privacy Protection Act. @@ -287,7 +316,7 @@ - Comment system + Comment system /> /> Guest comment posting allowed @@ -308,28 +337,9 @@ --> - - - - Promote Enano - - - - If you think Enano is nice, or if you want to show your support for the Enano team, you can do so by placing a link to the Enano - homepage in your Links sidebar block. You absolutely don't have to do this, and you won't get degraded support if you don't. Because - Enano is still relatively new in the CMS world, it needs all the attention it can get - and you can easily help to spread the word - using this link. - - - - - - - Disable all site access + Disable all site access Disabling the site allows you to work on the site without letting non-administrators see or use it. @@ -343,10 +353,20 @@ + + + + +
+ + + + + - + - + @@ -404,7 +424,7 @@ - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Users and communication
User account activation
User account activation
@@ -357,7 +377,7 @@
Account activation: + Account activation: Disable registration
'; echo ''; @@ -369,7 +389,7 @@ -
Account lockouts
Account lockouts
Configure Enano to prevent or restrict logins for a specified period of time if a user enters an incorrect password a specific number of times.
Password strength
Password strength
@@ -428,22 +448,125 @@ -
E-mail sent from the site
E-mail sending method:
Try using the built-in e-mail method first. If that doesn't work, you will need to enter valid SMTP information here.

-
SMTP hostname:
This option only applies to the external SMTP mode.
SMTP credentials:
This option only applies to the external SMTP mode.
Username:
- Password:
E-mail sent from the site
E-mail sending method:
Try using the built-in e-mail method first. If that doesn't work, you will need to enter valid SMTP information here.

+
SMTP hostname:
This option only applies to the external SMTP mode.
SMTP credentials:
This option only applies to the external SMTP mode.
Username:
+ Password:
Avatars
+ Avatars are small images that users can display on their profiles and in comments. + +
+ Enable avatar support:
+ Supported formats are JPEG, PNG, and GIF™. +
+ +
+ Maximum avatar file size:
+ For smaller sites, the highest value for this should be about 50KB, 51200. Larger sites with more visitors will likely want to use something much smaller, such as 10KB. +
+ /> bytes +
+ Maximum avatar dimensions:
+ The format is width × height. Typically you want to have this square (the same width and height). These are only maximum dimensions; users are not prevented from having smaller images. +
+ /> × + /> pixels +
+ Allow animated avatars:
+ If this is checked, users can upload APNG and Animated GIF™ avatars. Sometimes such images can be specifically made to be distracting, like rapidly flashing images. If this is unchecked, these formats will be blocked, and only still PNGs and GIFs will be allowed. +
+ +
+ Allowed upload methods:
+ +
+
+ +
+ Avatar storage directory:
+ This should be relative to your Enano root and should contain only alphanumeric characters and forward slashes, even if your server runs Windows. +
+ /> +
+
+ +
+ + + + + + + + + + + + + + + - + - + + @@ -500,13 +623,19 @@ - + + +
Sidebar links
Promote Enano
+ If you think Enano is nice, or if you want to show your support for the Enano team, you can do so by placing a link to the Enano + homepage in your Links sidebar block. You absolutely don't have to do this, and you won't get degraded support if you don't. Because + Enano is still relatively new in the CMS world, it needs all the attention it can get - and you can easily help to spread the word + using this link. + + +
SourceForge.net logo
SourceForge.net logo
@@ -488,8 +611,8 @@ -
W3C compliance logos
Enano generates (by default) Valid XHTML 1.1 code, plus valid CSS. If you want to show this off, check the appropriate boxes below.
W3C compliance logos
Enano generates (by default) Valid XHTML 1.1 code, plus valid CSS. If you want to show this off, check the appropriate boxes below.
id="w3c-vh32" name="w3c-vh32" />
id="w3c-vh40" name="w3c-vh40" />
Defective By Design Anti-DRM button
Defective By Design Anti-DRM button
The Enano project is strongly against Digital Restrictions Management. DRM removes the freedoms that every consumer should have: to freely copy and use digital media items they legally purchased to their own devices. Showing your opposition to DRM is as easy as checking the box below to place a link to DefectiveByDesign.org on your sidebar.
/>
+
+ +
+ - +
diff -r c2f4c900c507 -r dc838fd61a06 plugins/SpecialUserPrefs.php --- a/plugins/SpecialUserPrefs.php Wed Dec 19 23:04:17 2007 -0500 +++ b/plugins/SpecialUserPrefs.php Thu Dec 20 22:23:07 2007 -0500 @@ -103,6 +103,10 @@ userprefs_menu_add('Profile/membership', 'Edit e-mail address and password', makeUrlNS('Special', 'Preferences/EmailPassword') . '" onclick="ajaxLoginNavTo(\'Special\', \'Preferences/EmailPassword\', '.USER_LEVEL_CHPREF.'); return false;'); userprefs_menu_add('Profile/membership', 'Edit signature', makeUrlNS('Special', 'Preferences/Signature')); userprefs_menu_add('Profile/membership', 'Edit public profile', makeUrlNS('Special', 'Preferences/Profile')); + if ( getConfig('avatar_enable') == '1' ) + { + userprefs_menu_add('Profile/membership', 'Avatar settings', makeUrlNS('Special', 'Preferences/Avatar')); + } userprefs_menu_add('Private messages', 'Inbox', makeUrlNS('Special', 'PrivateMessages/Folder/Inbox')); userprefs_menu_add('Private messages', 'Outbox', makeUrlNS('Special', 'PrivateMessages/Folder/Outbox')); userprefs_menu_add('Private messages', 'Sent items', makeUrlNS('Special', 'PrivateMessages/Folder/Sent')); @@ -124,6 +128,7 @@ function page_Special_Preferences() { global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; // We need a login to continue if ( !$session->user_logged_in ) @@ -594,6 +599,273 @@ '; break; + case 'Avatar': + if ( getConfig('avatar_enable') != '1' ) + { + echo '
' . $lang->get('usercp_avatar_err_disabled_title') . '
' . $lang->get('usercp_avatar_err_disabled_body') . '
'; + } + + // Determine current avatar + $q = $db->sql_query('SELECT user_has_avatar, avatar_type FROM ' . table_prefix . 'users WHERE user_id = ' . $session->user_id . ';'); + if ( !$q ) + $db->_die('Avatar CP selecting user\'s avatar data'); + + list($has_avi, $avi_type) = $db->fetchrow_num(); + + if ( isset($_POST['submit']) ) + { + $action = ( isset($_POST['avatar_action']) ) ? $_POST['avatar_action'] : 'keep'; + $avi_path = ENANO_ROOT . '/' . getConfig('avatar_directory') . '/' . $session->user_id . '.' . $avi_type; + switch($action) + { + case 'keep': + default: + break; + case 'remove': + if ( $has_avi ) + { + // First switch the avatar off + $q = $db->sql_query('UPDATE ' . table_prefix . 'users SET user_has_avatar = 0 WHERE user_id = ' . $session->user_id . ';'); + if ( !$q ) + $db->_die('Avatar CP switching user avatar off'); + + if ( @unlink($avi_path) ) + { + echo '
' . $lang->get('usercp_avatar_delete_success') . '
'; + } + $has_avi = 0; + } + break; + case 'set_http': + case 'set_file': + // Hackish way to preserve the UNIX philosophy of reusing as much code as possible + if ( $action == 'set_http' ) + { + // Check if this action is enabled + if ( getConfig('avatar_upload_http') !== '1' ) + { + // non-localized, only appears on hack attempt + echo '
Uploads over HTTP are disabled.
'; + break; + } + // Download the file + require_once( ENANO_ROOT . '/includes/http.php' ); + + if ( !preg_match('/^http:\/\/([a-z0-9-\.]+)(:([0-9]+))?\/(.+)$/', $_POST['avatar_http_url'], $match) ) + { + echo '
' . $lang->get('usercp_avatar_invalid_url') . '
'; + break; + } + + $hostname = $match[1]; + $uri = '/' . $match[4]; + $port = ( $match[3] ) ? intval($match[3]) : 80; + $max_size = intval(getConfig('avatar_max_size')); + + // Get temporary file + $tempfile = tempnam(false, "enanoavatar_{$session->user_id}"); + if ( !$tempfile ) + echo '
Error getting temp file.
'; + + @unlink($tempfile); + $request = new Request_HTTP($hostname, $uri, 'GET', $port); + $result = $request->write_response_to_file($tempfile, 50, $max_size); + if ( !$result || $request->response_code != HTTP_OK ) + { + @unlink($tempfile); + echo '
' . $lang->get('usercp_avatar_bad_write') . '
'; + break; + } + + // Response written. Proceed to validation... + } + else + { + // Check if this action is enabled + if ( getConfig('avatar_upload_file') !== '1' ) + { + // non-localized, only appears on hack attempt + echo '
Uploads from the browser are disabled.
'; + break; + } + + $max_size = intval(getConfig('avatar_max_size')); + + $file =& $_FILES['avatar_file']; + $tempfile =& $file['tmp_name']; + if ( filesize($tempfile) > $max_size ) + { + @unlink($tempfile); + echo '
' . $lang->get('usercp_avatar_file_too_large') . '
'; + break; + } + } + $file_type = get_image_filetype($tempfile); + if ( !$file_type ) + { + unlink($tempfile); + echo '
' . $lang->get('usercp_avatar_bad_filetype') . '
'; + break; + } + + // The file type is good - validate dimensions and animation + switch($file_type) + { + case 'png': + $is_animated = is_png_animated($tempfile); + $dimensions = png_get_dimensions($tempfile); + break; + case 'gif': + $is_animated = is_gif_animated($tempfile); + $dimensions = gif_get_dimensions($tempfile); + break; + case 'jpg': + $is_animated = false; + $dimensions = jpg_get_dimensions($tempfile); + break; + default: + echo '
API mismatch
'; + break 2; + } + // Did we get invalid size data? If so the image is probably corrupt. + if ( !$dimensions ) + { + @unlink($tempfile); + echo '
' . $lang->get('usercp_avatar_corrupt_image') . '
'; + break; + } + // Is the image animated? + if ( $is_animated && getConfig('avatar_enable_anim') !== '1' ) + { + @unlink($tempfile); + echo '
' . $lang->get('usercp_avatar_disallowed_animation') . '
'; + break; + } + // Check image dimensions + list($image_x, $image_y) = $dimensions; + $max_x = intval(getConfig('avatar_max_width')); + $max_y = intval(getConfig('avatar_max_height')); + if ( $image_x > $max_x || $image_y > $max_y ) + { + @unlink($tempfile); + echo '
' . $lang->get('usercp_avatar_too_large') . '
'; + break; + } + // All good! + if ( rename($tempfile, $avi_path) ) + { + $q = $db->sql_query('UPDATE ' . table_prefix . "users SET user_has_avatar = 1, avatar_type = '$file_type' WHERE user_id = {$session->user_id};"); + if ( !$q ) + $db->_die('Avatar CP updating users table after successful avatar upload'); + $has_avi = 1; + $avi_type = $file_type; + echo '
' . $lang->get('usercp_avatar_upload_success') . '
'; + } + else + { + echo '
' . $lang->get('usercp_avatar_move_failed') . '
'; + } + break; + } + } + + ?> + + fullpage) . '" method="post" enctype="multipart/form-data">'; + echo '
'; + echo ''; + echo ' + + '; + + echo ' + + + '; + + echo ' + + + '; + + echo ' + + '; + + echo '
+ ' . $lang->get('usercp_avatar_table_title') . ' +
+ ' . $lang->get('usercp_avatar_label_current') . ' + '; + + if ( $has_avi == 1 ) + { + echo '' . $lang->get('usercp_avatar_image_alt', array('username' => $session->username)) . ''; + } + else + { + echo $lang->get('usercp_avatar_image_none'); + } + + echo '
+ ' . $lang->get('usercp_avatar_lbl_change') . ' + +
+
'; + if ( getConfig('avatar_upload_http') == '1' ) + { + echo '
+ '; + } + else + { + echo ' '; + } + if ( getConfig('avatar_upload_file') == '1' ) + { + echo ' + '; + } + else + { + echo ' '; + } + echo '
+ +
+
'; + + break; default: $good = false; $code = $plugins->setHook('userprefs_body'); diff -r c2f4c900c507 -r dc838fd61a06 themes/oxygen/comment.tpl --- a/themes/oxygen/comment.tpl Wed Dec 19 23:04:17 2007 -0500 +++ b/themes/oxygen/comment.tpl Thu Dec 20 22:23:07 2007 -0500 @@ -10,6 +10,13 @@ {NAME}
{USER_LEVEL} + +
+ + {AVATAR_ALT} + +
+ diff -r c2f4c900c507 -r dc838fd61a06 themes/oxygen/css/bleu.css --- a/themes/oxygen/css/bleu.css Wed Dec 19 23:04:17 2007 -0500 +++ b/themes/oxygen/css/bleu.css Thu Dec 20 22:23:07 2007 -0500 @@ -264,11 +264,11 @@ .catCheck:hover { padding: 3px; background-color: #F0F0F0; } /* Information, warning, question, error, and wait boxes */ -div.error-box { background-image: url(../../../images/error.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #FFF4F4; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 1em 0 0 1em; min-height: 25px; } -div.info-box { background-image: url(../../../images/info.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #F4F4FF; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 1em 0 0 1em; min-height: 25px; } -div.warning-box { background-image: url(../../../images/warning.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #FFFFF4; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 1em 0 0 1em; min-height: 25px; } -div.question-box { background-image: url(../../../images/question.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #F4FFF4; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 1em 0 0 1em; min-height: 25px; } -div.wait-box { background-image: url(../../../images/wait.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #FFF4FF; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 1em 0 0 1em; min-height: 25px; } +div.error-box { background-image: url(../../../images/error.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #FFF4F4; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 0.5em 0 0 0; min-height: 25px; } +div.info-box { background-image: url(../../../images/info.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #F4F4FF; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 0.5em 0 0 0; min-height: 25px; } +div.warning-box { background-image: url(../../../images/warning.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #FFFFF4; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 0.5em 0 0 0; min-height: 25px; } +div.question-box { background-image: url(../../../images/question.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #F4FFF4; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 0.5em 0 0 0; min-height: 25px; } +div.wait-box { background-image: url(../../../images/wait.png); background-position: 8px 8px; background-repeat: no-repeat; background-color: #FFF4FF; border: 1px dashed #406080; padding: 10px 10px 10px 50px; margin: 0.5em 0 0 0; min-height: 25px; } /* This stuff is mostly unused, left in for compatibility */ div#ajaxEditContainer table { border: 0px solid #FFFFFF; } diff -r c2f4c900c507 -r dc838fd61a06 upgrade.sql --- a/upgrade.sql Wed Dec 19 23:04:17 2007 -0500 +++ b/upgrade.sql Thu Dec 20 22:23:07 2007 -0500 @@ -12,6 +12,8 @@ CREATE TABLE {{TABLE_PREFIX}}language( lang_id smallint(5) NOT NULL auto_increment, lang_code varchar(16) NOT NULL, lang_name_default varchar(64) NOT NULL, lang_name_native varchar(64) NOT NULL, last_changed int(12) NOT NULL DEFAULT 0, PRIMARY KEY ( lang_id ) ) CHARACTER SET `utf8`; CREATE TABLE {{TABLE_PREFIX}}language_strings( string_id bigint(15) NOT NULL auto_increment, lang_id smallint(5) NOT NULL, string_category varchar(32) NOT NULL, string_name varchar(64) NOT NULL, string_content longtext NOT NULL, PRIMARY KEY ( string_id ) ); ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_lang smallint(5) NOT NULL; +ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_has_avatar tinyint(1) NOT NULL; +ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN avatar_type ENUM('jpg', 'png', 'gif') NOT NULL; ---END Stable1.0ToUnstable1.1--- ---BEGIN 1.0.2--- -- No DB changes in this release