diff --git a/classes/ajax_start.php b/classes/ajax_start.php index 2c6e0487..07e6de6e 100644 --- a/classes/ajax_start.php +++ b/classes/ajax_start.php @@ -13,11 +13,11 @@ if (isset($_COOKIE['session'])) { $LoginCookie=$Enc->decrypt($_COOKIE['session']); } if(isset($LoginCookie)) { list($SessionID, $UserID)=explode("|~|",$Enc->decrypt($LoginCookie)); - + if(!$UserID || !$SessionID) { die('Not logged in!'); } - + if(!$Enabled = $Cache->get_value('enabled_'.$UserID)){ require(SERVER_ROOT.'/classes/class_mysql.php'); //Require the database wrapper $DB=NEW DB_MYSQL; //Load the database wrapper diff --git a/classes/class_artist.php b/classes/class_artist.php index 547ef9af..a31986bb 100644 --- a/classes/class_artist.php +++ b/classes/class_artist.php @@ -1,19 +1,3 @@ ID = $ID; - $this->NameLength = mb_strlen($Name, 'utf8'); - $this->Name = display_str($Name); - } - -} +// Placeholder for if we ever decide to actaully have a model for an artist. ?> diff --git a/classes/class_artists.php b/classes/class_artists.php new file mode 100644 index 00000000..a04f7685 --- /dev/null +++ b/classes/class_artists.php @@ -0,0 +1,274 @@ + { + * [ArtistType] => { + * id, name, aliasid + * } + * } + * ArtistType is an int. It can be: + * 1 => Main artist + * 2 => Guest artist + * 4 => Composer + * 5 => Conductor + * 6 => DJ + */ + public static function get_artists($GroupIDs) { + global $Cache, $DB; + $Results = array(); + $DBs = array(); + foreach($GroupIDs as $GroupID) { + if(!is_number($GroupID)) { + continue; + } + $Artists = $Cache->get_value('groups_artists_'.$GroupID); + if(is_array($Artists)) { + $Results[$GroupID] = $Artists; + } else { + $DBs[] = $GroupID; + } + } + if(count($DBs) > 0) { + $IDs = implode(',', $DBs); + if(empty($IDs)) { + $IDs = "null"; + } + $DB->query("SELECT ta.GroupID, + ta.ArtistID, + aa.Name, + ta.Importance, + ta.AliasID + FROM torrents_artists AS ta + JOIN artists_alias AS aa ON ta.AliasID = aa.AliasID + WHERE ta.GroupID IN ($IDs) + ORDER BY ta.GroupID ASC, + ta.Importance ASC, + aa.Name ASC;"); + while(list($GroupID,$ArtistID,$ArtistName,$ArtistImportance,$AliasID) = $DB->next_record(MYSQLI_BOTH, false)) { + $Results[$GroupID][$ArtistImportance][] = array('id' => $ArtistID, 'name' => $ArtistName, 'aliasid' => $AliasID); + $New[$GroupID][$ArtistImportance][] = array('id' => $ArtistID, 'name' => $ArtistName, 'aliasid' => $AliasID); + } + foreach($DBs as $GroupID) { + if(isset($New[$GroupID])) { + $Cache->cache_value('groups_artists_'.$GroupID, $New[$GroupID]); + } + else { + $Cache->cache_value('groups_artists_'.$GroupID, array()); + } + } + $Missing = array_diff($GroupIDs, array_keys($Results)); + if(!empty($Missing)) { + $Results += array_fill_keys($Missing, array()); + } + } + return $Results; + } + + + /** + * Convenience function for get_artists, when you just need one group. + * + * @param int $GroupID + * @return array - see get_artists + */ + public static function get_artist($GroupID) { + $Results = Artists::get_artists(array($GroupID)); + return $Results[$GroupID]; + } + + + /** + * Format an array of artists for display. + * TODO: Revisit the logic of this, see if we can helper-function the copypasta. + * + * @param array Artists an array of the form output by get_artists + * @param boolean $MakeLink if true, the artists will be links, if false, they will be text. + * @param boolean $IncludeHyphen if true, appends " - " to the end. + * @param $Escape if true, output will be escaped. Think carefully before setting it false. + */ + public static function display_artists($Artists, $MakeLink = true, $IncludeHyphen = true, $Escape = true) { + if(!empty($Artists)) { + $ampersand = ($Escape) ? ' & ' : ' & '; + $link = ''; + + $MainArtists = $Artists[1]; + $Guests = $Artists[2]; + $Composers = $Artists[4]; + $Conductors = $Artists[5]; + $DJs = $Artists[6]; + + if ((count($MainArtists) + count($Conductors) + count($DJs) == 0) && (count($Composers) == 0)) { + return ''; + } + + // Various Composers is not needed and is ugly and should die + switch(count($Composers)) { + case 0: + break; + case 1: + $link .= Artists::display_artist($Composers[0], $MakeLink, $Escape); + break; + case 2: + $link .= Artists::display_artist($Composers[0], $MakeLink, $Escape).$ampersand.Artists::display_artist($Composers[1], $MakeLink, $Escape); + break; + } + + if ((count($Composers) > 0) && (count($Composers) < 3) && (count($MainArtists) > 0)) { + $link .= ' performed by '; + } + + $ComposerStr .= $link; + + switch(count($MainArtists)) { + case 0: + break; + case 1: + $link .= Artists::display_artist($MainArtists[0], $MakeLink, $Escape); + break; + case 2: + $link .= Artists::display_artist($MainArtists[0], $MakeLink, $Escape).$ampersand.Artists::display_artist($MainArtists[1], $MakeLink, $Escape); + break; + default: + $link .= 'Various Artists'; + } + + /*if(!empty($Guests) && (count($MainArtists) + count($Composers) > 0) && (count($MainArtists) + count($Composers) + count($Conductors) < 3)) { + switch(count($Guests)) { + case 1: + $link .= ' with '.Artists::display_artist($Guests[0], $MakeLink, $Escape); + break; + case 2: + $link .= ' with '.Artists::display_artist($Guests[0], $MakeLink, $Escape).$ampersand.Artists::display_artist($Guests[1], $MakeLink, $Escape); + break; + } + }*/ + + if ((count($Conductors) > 0) && (count($MainArtists) + count($Composers) > 0) && (count($Composers) < 3 || count($MainArtists) > 0)) { + $link .= ' under '; + } + switch(count($Conductors)) { + case 0: + break; + case 1: + $link .= Artists::display_artist($Conductors[0], $MakeLink, $Escape); + break; + case 2: + $link .= Artists::display_artist($Conductors[0], $MakeLink, $Escape).$ampersand.Artists::display_artist($Conductors[1], $MakeLink, $Escape); + break; + default: + $link .= ' Various Conductors'; + } + + if ((count($Composers) > 0) && (count($MainArtists) + count($Conductors) > 3) && (count($MainArtists) > 1) && (count($Conductors) > 1)) { + $link = $ComposerStr . 'Various Artists'; + } elseif ((count($Composers) > 2) && (count($MainArtists) + count($Conductors) == 0)) { + $link = 'Various Composers'; + } + + // DJs override everything else + switch(count($DJs)) { + case 0: + break; + case 1: + $link = Artists::display_artist($DJs[0], $MakeLink, $Escape); + break; + case 2: + $link = Artists::display_artist($DJs[0], $MakeLink, $Escape).$ampersand.Artists::display_artist($DJs[1], $MakeLink, $Escape); + break; + default : + $link = 'Various DJs'; + } + + return $link.($IncludeHyphen?' - ':''); + } else { + return ''; + } + } + + + /** + * Formats a single artist name. + * + * @param array $Artist an array of the form ('id'=>ID, 'name'=>Name) + * @param boolean $MakeLink If true, links to the artist page. + * @param boolean $Escape If false and $MakeLink is false, returns the unescaped, unadorned artist name. + * @return string Formatted artist name. + */ + public static function display_artist($Artist, $MakeLink = true, $Escape = true) { + if ($MakeLink && !$Escape) { + error('Invalid parameters to Artists::display_artist()'); + } elseif ($MakeLink) { + return ''.display_str($Artist['name']).''; + } elseif ($Escape) { + return display_str($Artist['name']); + } else { + return $Artist['name']; + } + } + + /** + * Deletes an artist and their requests, wiki, and tags. + * Does NOT delete their torrents. + * + * @param int $ArtistID + */ + public static function delete_artist($ArtistID) { + global $DB, $LoggedUser, $Cache; + + $DB->query("SELECT Name FROM artists_group WHERE ArtistID = ".$ArtistID); + list($Name) = $DB->next_record(MYSQLI_NUM, false); + + // Delete requests + $DB->query("SELECT RequestID FROM requests_artists WHERE ArtistID=".$ArtistID." AND ArtistID != 0"); + $Requests = $DB->to_array(); + foreach($Requests AS $Request) { + list($RequestID) = $Request; + $DB->query('DELETE FROM requests WHERE ID='.$RequestID); + $DB->query('DELETE FROM requests_votes WHERE RequestID='.$RequestID); + $DB->query('DELETE FROM requests_tags WHERE RequestID='.$RequestID); + $DB->query('DELETE FROM requests_artists WHERE RequestID='.$RequestID); + } + + // Delete artist + $DB->query('DELETE FROM artists_group WHERE ArtistID='.$ArtistID); + $DB->query('DELETE FROM artists_alias WHERE ArtistID='.$ArtistID); + $Cache->decrement('stats_artist_count'); + + // Delete wiki revisions + $DB->query('DELETE FROM wiki_artists WHERE PageID='.$ArtistID); + + // Delete tags + $DB->query('DELETE FROM artists_tags WHERE ArtistID='.$ArtistID); + + $Cache->delete_value('artist_'.$ArtistID); + // Record in log + + if(!empty($LoggedUser['Username'])) { + $Username = $LoggedUser['Username']; + } else { + $Username = 'System'; + } + Misc::write_log('Artist '.$ArtistID.' ('.$Name.') was deleted by '.$Username); + } + + + /** + * Remove LRM (left-right-marker) and trims, because people copypaste carelessly. + * If we don't do this, we get seemingly duplicate artist names. + * TODO: make stricter, e.g. on all whitespace characters or Unicode normalisation + * + * @param string $ArtistName + */ + public static function normalise_artist_name($ArtistName) { + // \u200e is ‎ + $ArtistName = trim($ArtistName); + $ArtistName = preg_replace('/^(\xE2\x80\x8E)+/', '', $ArtistName); + $ArtistName = preg_replace('/(\xE2\x80\x8E)+$/', '', $ArtistName); + return trim(preg_replace('/ +/', ' ', $ArtistName)); + } +} +?> diff --git a/classes/class_artists_similar.php b/classes/class_artists_similar.php index f5464847..aa2c5a9a 100644 --- a/classes/class_artists_similar.php +++ b/classes/class_artists_similar.php @@ -1,35 +1,52 @@ ID = $ID; + $this->NameLength = mb_strlen($Name, 'utf8'); + $this->Name = display_str($Name); + } +} + class ARTISTS_SIMILAR extends ARTIST{ var $Artists = array(); var $TotalScore = 0; - + var $xValues = array(WIDTH=>1); var $yValues = array(HEIGHT=>1); - + var $LargestDecimal = 0; var $LowestDecimal = 1; - - - + + + function dump_data(){ return serialize(array(time(), $this->Name, $this->x, $this->y, serialize($this->Artists), serialize($this->Similar))); } - + function load_data($Data){ list($LastUpdated, $this->Name, $this->x, $this->y, $this->Artists, $this->Similar) = unserialize($Data); $this->Artists = unserialize($this->Artists); $this->Similar = unserialize($this->Similar); } - + function set_up(){ $this->x = ceil(WIDTH/2); $this->y = ceil(HEIGHT/2); - + $this->xValues[$this->x] = $this->ID; $this->yValues[$this->y] = $this->ID; - + global $DB; - + // Get artists that are directly similar to the artist $ArtistIDs = array(); $DB->query(" @@ -44,11 +61,11 @@ function set_up(){ WHERE s1.ArtistID=".$this->ID." ORDER BY ass.Score DESC LIMIT 14"); - + if($DB->record_count() == 0){ return; } - + // Build into array. Each artist is its own object in $this->Artists while(list($ArtistID, $Name, $Score) = $DB->next_record(MYSQLI_NUM, false)){ if($Score<0){ @@ -59,7 +76,7 @@ function set_up(){ $this->TotalScore+=$Score; $ArtistIDs[]=$ArtistID; } - + // Get similarities between artists on the map $DB->query("SELECT s1.ArtistID, @@ -71,17 +88,17 @@ function set_up(){ WHERE s1.ArtistID IN(".implode(',',$ArtistIDs).") AND s2.ArtistID IN(".implode(',',$ArtistIDs).") "); - + // Build into array while(list($Artist1ID, $Artist2ID) = $DB->next_record()){ $this->Artists[$Artist1ID]->Similar[$Artist2ID] = array('ID'=>$Artist2ID); } - + // Calculate decimal point scores between artists foreach($this->Similar as $SimilarArtist) { list($ArtistID, $Similar) = array_values($SimilarArtist); $this->Similar[$ArtistID]['Decimal'] = $this->similarity($Similar['Score'], $this->TotalScore); - + if($this->Similar[$ArtistID]['Decimal'] < $this->LowestDecimal){ $this->LowestDecimal = $this->Similar[$ArtistID]['Decimal']; } @@ -91,28 +108,28 @@ function set_up(){ } reset($this->Artists); } - + function set_positions(){ $xValues = array(); // Possible x values $Root = ceil(WIDTH/4); // Half-way into half of the image $Offset = 4; // Distance from the root (a quarter of the way into the image) to the x value - + // The number of artists placed in the top or the bottom $NumTop = 0; $NumBottom = 0; - + // The number of artists placed in the left or the right $NumLeft = 0; $NumRight = 0; - + $Multiplier = 0; - + // Build up an impressive list of possible x values // We later iterate through these, and pick out the ones we want - + // These x values are all below WIDTH/2 (all on the left) // The script later chooses which side to put them on - + // We create more very low x values because they're more likely to be skipped for($i = 0; $i<=count($this->Artists)*4; $i++){ if($Offset>=((WIDTH/4))){ @@ -120,20 +137,20 @@ function set_positions(){ } $Plus = $Root+$Offset; // Point on the right of the root $Minus = abs($Root-$Offset); // Point on the left of the root - + $xValues[$Plus]=$Plus; - + $xValues[$Minus]=$Minus; - + // Throw in an extra x value closer to the edge, because they're more likely to be skipped - + if($Minus>30){ // $xValues[$Minus-30]=$Minus-30; } - + $Offset = $Offset+rand(5,20); // Increase offset, and go again } - + foreach($this->Artists as $Artist){ $ArtistID = $Artist->ID; if($Artist->Displayed == true){ @@ -142,14 +159,14 @@ function set_positions(){ $this->Similar[$ArtistID]['Decimal'] = $this->Similar[$ArtistID]['Decimal'] * (1/($this->LargestDecimal))-0.1; // Calculate the distance away from the center, based on similarity $IdealDistance = $this->calculate_distance($this->Similar[$ArtistID]['Decimal'], $this->x, $this->y); - + $this->Similar[$ArtistID]['Distance'] = $IdealDistance; - + // 1 = left, 2 = right $Horizontal = 0; $Vertical = 0; - - // See if any similar artists have been placed yet. If so, place artist in that half + + // See if any similar artists have been placed yet. If so, place artist in that half // (provided that there are enough in the other half to visually balance out) reset($Artist->Similar); foreach($Artist->Similar as $SimilarArtist) { @@ -163,17 +180,17 @@ function set_positions(){ break; } } - + shuffle($xValues); - + while($xValue = array_shift($xValues)){ if(abs($this->x - $xValue) <= $IdealDistance) { - if(hypot(abs($this->x - $xValue), ($this->y - 50)) > $IdealDistance + if(hypot(abs($this->x - $xValue), ($this->y - 50)) > $IdealDistance || ceil(sqrt(pow($IdealDistance, 2) - pow($this->x - $xValue, 2))) > (HEIGHT/2)){ $xValue = $this->x - ceil(sqrt(pow($IdealDistance, 2) - pow($IdealDistance*0.1*rand(5,9), 2))); //echo "Had to change x value for ".$Artist->Name." to ".$xValue."\n"; } - // Found a match (Is close enough to the center to satisfy $IdealDistance), + // Found a match (Is close enough to the center to satisfy $IdealDistance), // Now it's time to choose which half to put it on if(!$Horizontal) { // No similar artists displayed @@ -185,11 +202,11 @@ function set_positions(){ } else { $NumLeft++; } - + $Artist->x = $xValue; $this->xValues[$xValue] = $ArtistID; unset($xValues[$xValue]); - + break; } } @@ -200,14 +217,14 @@ function set_positions(){ $this->xValues[$xValue] = $ArtistID; unset($xValues[$xValue]); } - - + + // Pythagoras. $yValue is the vertical distance from the center to the y value $yValue = sqrt(pow($IdealDistance, 2) - pow(abs($this->x - $Artist->x), 2)); - - + + // Now we pick if it should go on the top or bottom - + if($NumTop>$NumBottom){ // Send it to the bottom half $yValue=(HEIGHT/2)+$yValue; $NumBottom++; @@ -215,20 +232,20 @@ function set_positions(){ $yValue=(HEIGHT/2)-$yValue; $NumTop++; } - + $yValue = ceil($yValue); - + // $yValue is now a proper y coordinate // Now time to do some spacing out - + if($yValue < 10){ $yValue+=(10+abs($yValue))+rand(10,20); } - + if($yValue > (HEIGHT - 10)){ $yValue-=((HEIGHT/2)-rand(10,20)); } - + $i = 1; while($Conflict = $this->scan_array_range($this->yValues, abs($yValue-13), $yValue+13)) { if($i > 10){ @@ -237,7 +254,7 @@ function set_positions(){ if(!$this->scan_array_range($this->yValues, abs($yValue-5), $yValue-20)){ $yValue -= 20; } - + $yValue=$Conflict + rand(10, 20); if($yValue>HEIGHT-10){ $yValue-=ceil(HEIGHT/2.5); @@ -246,16 +263,16 @@ function set_positions(){ } $i++; } - + $Artist->y = $yValue; $this->yValues[$yValue] = $ArtistID; } reset($this->Artists); reset($this->xValues); reset($this->yValues); - + } - + // Calculate the ideal distance from the center point ($Rootx, $Rooty) to the artist's point on the board // Pythagoras as fun! function calculate_distance($SimilarityCoefficient, $Rootx, $Rooty){ @@ -265,13 +282,13 @@ function calculate_distance($SimilarityCoefficient, $Rootx, $Rooty){ $y = $MaxHeight - ($SimilarityCoefficient*$MaxHeight); // Possible y value $Hypot = hypot($Rootx - $x, $Rooty - $y); return $MaxWidth - $Hypot; - + } - + function similarity($Score, $TotalArtistScore){ return (pow(($Score/($TotalArtistScore+1)), (1/1))); } - + function scan_array_range($Array, $Start, $Finish){ if($Start<0){ die($Start); @@ -283,15 +300,15 @@ function scan_array_range($Array, $Start, $Finish){ } return false; } - + function write_artists(){ ?>
- Name?> + Name?>
Artists as $Artist){ if($Artist->ID == $this->ID){ continue; @@ -303,7 +320,7 @@ function write_artists(){ } $Decimal = $this->Similar[$Artist->ID]['Decimal']; - + if($Decimal<0.2){ $FontSize = 8; } elseif($Decimal<0.3){ @@ -321,7 +338,7 @@ function write_artists(){ } reset($this->Artists); } - + function background_image(){ global $Img; reset($this->Similar); @@ -330,9 +347,9 @@ function background_image(){ $Artist = $this->Artists[$ArtistID]; $Decimal = $this->Similar[$ArtistID]['Decimal']; $Width = ceil($Decimal*4)+1; - + $Img->line($this->x, $this->y, $Artist->x, $Artist->y,$Img->color(199,218,255), $Width); - + unset($Artist->Similar[$this->ID]); reset($Artist->Similar); foreach($Artist->Similar as $SimilarArtist2) { @@ -345,10 +362,10 @@ function background_image(){ } reset($this->xValues); } - + $Img->make_png(SERVER_ROOT.'/static/similar/'.$this->ID.'.png'); } - + function dump(){ echo "Similarities:\n"; foreach($this->Artists as $Artist){ @@ -362,10 +379,10 @@ function dump(){ //print_r($Artist->Similar); echo "\n\n---\n\n"; } - + } - - + + } diff --git a/classes/class_cache.php b/classes/class_cache.php index d3bc5a21..aae3b8cb 100644 --- a/classes/class_cache.php +++ b/classes/class_cache.php @@ -40,7 +40,7 @@ class CACHE extends Memcache { 'top10tor_*', 'query_lock_*' ); - + public $CanClear = false; public $InternalCache = true; @@ -72,6 +72,14 @@ public function cache_value($Key, $Value, $Duration=2592000) { $this->Time+=(microtime(true)-$StartTime)*1000; } + // Wrapper for Memcache::add, with the zlib option removed and default duration of 30 days + public function add_value($Key, $Value, $Duration=2592000) { + $StartTime=microtime(true); + $Added=$this->add($Key, $Value, 0, $Duration); + $this->Time+=(microtime(true)-$StartTime)*1000; + return $Added; + } + public function replace_value($Key, $Value, $Duration=2592000) { $StartTime=microtime(true); $this->replace($Key, $Value, false, $Duration); @@ -87,13 +95,13 @@ public function get_value($Key, $NoCache=false) { trigger_error("Cache retrieval failed for empty key"); } - if (isset($_GET['clearcache']) && $this->CanClear && !in_array_partial($Key, $this->PersistentKeys)) { + if (isset($_GET['clearcache']) && $this->CanClear && !Misc::in_array_partial($Key, $this->PersistentKeys)) { if ($_GET['clearcache'] == 1) { //Because check_perms isn't true until loggeduser is pulled from the cache, we have to remove the entries loaded before the loggeduser data //Because of this, not user cache data will require a secondary pageload following the clearcache to update if (count($this->CacheHits) > 0) { foreach (array_keys($this->CacheHits) as $HitKey) { - if (!in_array_partial($HitKey, $this->PersistentKeys)) { + if (!Misc::in_array_partial($HitKey, $this->PersistentKeys)) { $this->delete($HitKey); unset($this->CacheHits[$HitKey]); } @@ -308,23 +316,22 @@ public function update($Key, $Rows, $Values, $Time=2592000) { } - // Built-in increment/decrement functions are said not to be thread safe -/* Supposedly fixed in v1.4.6 - public function increment($Key, $Value=1) { - if(($OldValue = $this->get($Key)) === false || !is_number($Value)) { - return false; - } - $this->replace_value($Key, $OldValue+$Value); + /** + * Tries to set a lock. Expiry time is one hour to avoid indefinite locks + * + * @param string $LockName name on the lock + * @return true if lock was acquired + */ + public function get_query_lock($LockName) { + return $this->add_value('query_lock_'.$LockName, 1, 3600); } - public function decrement($Key, $Value=1) { - if(($OldValue = $this->get($Key)) === false || !is_number($Value) || !is_number($OldValue)) { - return false; - } - if($Value > $OldValue) { - $OldValue = $Value = 0; - } - $this->replace_value($Key, $OldValue-$Value); + /** + * Remove lock + * + * @param string $LockName name on the lock + */ + public function clear_query_lock($LockName) { + $this->delete_value('query_lock_'.$LockName); } -*/ } diff --git a/classes/class_charts.php b/classes/class_charts.php index dfb4b690..683f1532 100644 --- a/classes/class_charts.php +++ b/classes/class_charts.php @@ -171,7 +171,7 @@ public function __construct () { public function add($Label, $Data) { if ($Label !== false) { - $this->Labels[] = cut_string($Label,35); + $this->Labels[] = Format::cut_string($Label,35); } $this->Data[] = $Data; } diff --git a/classes/class_debug.php b/classes/class_debug.php index be630bad..e0ae41d3 100644 --- a/classes/class_debug.php +++ b/classes/class_debug.php @@ -36,7 +36,7 @@ public function profile($Automatic='') { */ $Ram = memory_get_usage(true); if ($Ram > MAX_MEMORY && !defined('MEMORY_EXCEPTION')) { - $Reason[] = get_size($Ram).' Ram Used'; + $Reason[] = Format::get_size($Ram).' Ram Used'; } if (isset($_REQUEST['profile'])) { @@ -57,7 +57,7 @@ public function analysis($Message, $Report='', $Time=43200) { if (empty($Report)) { $Report = $Message; } - $Identifier = make_secret(5); + $Identifier = Users::make_secret(5); $Cache->cache_value( 'analysis_'.$Identifier, array( @@ -245,14 +245,14 @@ public function get_sphinx_time() { } public function get_sphinxql_queries() { - if(class_exists(SPHINXQL)) { - return SPHINXQL::$Queries; + if(class_exists(SphinxQL)) { + return SphinxQL::$Queries; } } public function get_sphinxql_time() { - if(class_exists(SPHINXQL)) { - return SPHINXQL::$Time; + if(class_exists(SphinxQL)) { + return SphinxQL::$Time; } } @@ -354,7 +354,7 @@ public function flag_table($Flags=false) { ms - + $Length) { + if ($Hard == 0) { + // Not hard, cut at closest word + $CutDesc = mb_substr($Str, 0, $Length, 'UTF-8'); + $DescArr = explode(' ', $CutDesc); + if (count($DescArr) > 1) { + array_pop($DescArr); + $CutDesc = implode(' ', $DescArr); + } + if ($ShowDots) { $CutDesc .= '...'; } + } else { + $CutDesc = mb_substr($Str, 0, $Length, 'UTF-8'); + if ($ShowDots) { $CutDesc .= '...'; } + } + return $CutDesc; + } else { + return $Str; + } + } + + + /** + * Gets the CSS class corresponding to a ratio + * + * @param $Ratio ratio to get the css class for + * @return string the CSS class corresponding to the ratio range + */ + public static function get_ratio_color($Ratio) { + if ($Ratio < 0.1) { return 'r00'; } + if ($Ratio < 0.2) { return 'r01'; } + if ($Ratio < 0.3) { return 'r02'; } + if ($Ratio < 0.4) { return 'r03'; } + if ($Ratio < 0.5) { return 'r04'; } + if ($Ratio < 0.6) { return 'r05'; } + if ($Ratio < 0.7) { return 'r06'; } + if ($Ratio < 0.8) { return 'r07'; } + if ($Ratio < 0.9) { return 'r08'; } + if ($Ratio < 1) { return 'r09'; } + if ($Ratio < 2) { return 'r10'; } + if ($Ratio < 5) { return 'r20'; } + return 'r50'; + } + + + /** + * Calculates and formats a ratio. + * + * @param int $Dividend AKA numerator + * @param int $Divisor + * @param boolean $Color if true, ratio will be coloured. + * @return formatted ratio HTML + */ + public static function get_ratio_html($Dividend, $Divisor, $Color = true) { + if ($Divisor == 0 && $Dividend == 0) { + return '--'; + } elseif ($Divisor == 0) { + return ''; + } + $Ratio = number_format(max($Dividend/$Divisor-0.005,0), 2); //Subtract .005 to floor to 2 decimals + if ($Color) { + $Class = Format::get_ratio_color($Ratio); + if ($Class) { + $Ratio = ''.$Ratio.''; + } + } + return $Ratio; + } + + + /** + * Gets the query string of the current page, minus the parameters in $Exclude + * + * @param array $Exclude Query string parameters to leave out, or blank to include all parameters. + * @return An HTML sanatized query string + */ + public static function get_url($Exclude = false) { + if ($Exclude !== false) { + $QueryItems = array(); + parse_str($_SERVER['QUERY_STRING'], $QueryItems); + + foreach ($QueryItems AS $Key => $Val) { + if (!in_array(strtolower($Key),$Exclude)) { + $Query[$Key] = $Val; + } + } + if (empty($Query)) { + return; + } + return display_str(http_build_query($Query)); + } else { + return display_str($_SERVER['QUERY_STRING']); + } + } + + + /** + * Finds what page we're on and gives it to us, as well as the LIMIT clause for SQL + * Takes in $_GET['page'] as an additional input + * + * @param $PerPage Results to show per page + * @param $DefaultResult Optional, which result's page we want if no page is specified + * If this parameter is not specified, we will default to page 1 + * + * @return array(int,string) What page we are on, and what to use in the LIMIT section of a query + * i.e. "SELECT [...] LIMIT $Limit;" + */ + public static function page_limit($PerPage, $DefaultResult = 1) { + if (!isset($_GET['page'])) { + $Page = ceil($DefaultResult/$PerPage); + if ($Page == 0) $Page = 1; + $Limit=$PerPage; + } else { + if (!is_number($_GET['page'])) { + error(0); + } + $Page = $_GET['page']; + if ($Page <= 0) { $Page = 1; } + $Limit=$PerPage*$Page-$PerPage . ', ' . $PerPage; + } + return array($Page,$Limit); + } + + // A9 magic. Some other poor soul can write the phpdoc. + // For data stored in memcached catalogues (giant arrays), eg. forum threads + public static function catalogue_limit($Page, $PerPage, $CatalogueSize=500) { + $CatalogueID = floor(($PerPage*$Page-$PerPage)/$CatalogueSize);; + $CatalogueLimit = ($CatalogueID*$CatalogueSize).', '.$CatalogueSize; + return array($CatalogueID, $CatalogueLimit); + } + + public static function catalogue_select($Catalogue, $Page, $PerPage, $CatalogueSize=500) { + return array_slice($Catalogue,(($PerPage*$Page-$PerPage)%$CatalogueSize),$PerPage,true); + } + + + /* Get pages + * Returns a page list, given certain information about the pages. + * + * @param int $StartPage: The current record the page you're on starts with. + * eg. if you're on page 2 of a forum thread with 25 posts per page, $StartPage is 25. + * If you're on page 1, $StartPage is 0. + * @param int $TotalRecords: The total number of records in the result set. + * eg. if you're on a forum thread with 152 posts, $TotalRecords is 152. + * @param int $ItemsPerPage: Self-explanatory. The number of records shown on each page + * eg. if there are 25 posts per forum page, $ItemsPerPage is 25. + * @param int $ShowPages: The number of page links that are shown. + * eg. If there are 20 pages that exist, but $ShowPages is only 11, only 11 links will be shown. + * @param string $Anchor A URL fragment to attach to the links. + * eg. '#comment12' + * @return A sanitized HTML page listing. + */ + public static function get_pages($StartPage,$TotalRecords,$ItemsPerPage,$ShowPages=11,$Anchor='') { + global $Document, $Method, $Mobile; + $Location = $Document.'.php'; + $StartPage = ceil($StartPage); + $TotalPages = 0; + if ($TotalRecords > 0) { + $StartPage = min($StartPage, ceil($TotalRecords/$ItemsPerPage)); + + $ShowPages--; + $TotalPages = ceil($TotalRecords/$ItemsPerPage); + + if ($TotalPages > $ShowPages) { + $StartPosition = $StartPage-round($ShowPages/2); + + if ($StartPosition <= 0) { + $StartPosition = 1; + } else { + if ($StartPosition >= ($TotalPages-$ShowPages)) { + $StartPosition = $TotalPages-$ShowPages; + } + } + + $StopPage = $ShowPages+$StartPosition; + + } else { + $StopPage = $TotalPages; + $StartPosition = 1; + } + + $StartPosition = max($StartPosition, 1); + + $QueryString = Format::get_url(array('page','post')); + if ($QueryString != '') { + $QueryString = '&'.$QueryString; + } + + $Pages = ''; + + if ($StartPage > 1) { + $Pages .= '<< First '; + $Pages .= '< Prev | '; + } + //End change + + if (!$Mobile) { + for ($i = $StartPosition; $i <= $StopPage; $i++) { + if ($i != $StartPage) { + $Pages .= ''; + } + $Pages .= ""; + if ($i*$ItemsPerPage > $TotalRecords) { + $Pages .= ((($i-1)*$ItemsPerPage)+1).'-'.($TotalRecords); + } else { + $Pages .= ((($i-1)*$ItemsPerPage)+1).'-'.($i*$ItemsPerPage); + } + + $Pages .= ""; + if ($i != $StartPage) { + $Pages.=''; + } + if ($i < $StopPage) { + $Pages.=" | "; + } + } + } else { + $Pages .= $StartPage; + } + + if ($StartPage && $StartPage < $TotalPages) { + $Pages .= ' | Next > '; + $Pages .= ' Last >>'; + } + } + if ($TotalPages > 1) { + return $Pages; + } + } + + + /** + * Format a size in bytes as a human readable string in KiB/MiB/... + * + * @param int $Size + * @param int $Levels Number of decimal places. Defaults to 2, unless the size >= 1TB, in which case it defaults to 4. + * @return string formatted number. + */ + public static function get_size($Size, $Levels = 2) { + $Units = array(' B',' KB',' MB',' GB',' TB',' PB',' EB',' ZB',' YB'); + $Size = (double) $Size; + for($Steps = 0; abs($Size) >= 1024; $Size /= 1024, $Steps++) {} + if (func_num_args() == 1 && $Steps >= 4) { + $Levels++; + } + return number_format($Size,$Levels) . $Units[$Steps]; + } + + + /** + * Format a number as a multiple of its highest power of 1000 (eg. 10035 -> '10.04k') + * + * @param int $Number + * @return string formatted number. + */ + public static function human_format($Number) { + $Steps = 0; + while($Number>=1000) { + $Steps++; + $Number=$Number/1000; + } + switch ($Steps) { + case 0: return round($Number); break; + case 1: return round($Number,2).'k'; break; + case 2: return round($Number,2).'M'; break; + case 3: return round($Number,2).'G'; break; + case 4: return round($Number,2).'T'; break; + case 5: return round($Number,2).'P'; break; + default: + return round($Number,2).'E + '.$Steps*3; + } + } + + + /** + * Given a formatted string of a size, get the number of bytes it represents. + * + * @param string $Size formatted size string, eg. 123.45k + * @return Number of bytes it represents, eg. (123.45 * 1024) + */ + public static function get_bytes($Size) { + list($Value,$Unit) = sscanf($Size, "%f%s"); + $Unit = ltrim($Unit); + if (empty($Unit)) { + return $Value ? round($Value) : 0; + } + switch(strtolower($Unit[0])) { + case 'k': return round($Value * 1024); + case 'm': return round($Value * 1048576); + case 'g': return round($Value * 1073741824); + case 't': return round($Value * 1099511627776); + default: return 0; + } + } + + + /** + * Reverse the effects of display_str - un-sanitize HTML. + * Use sparingly. + * + * @param string $Str the string to unsanitize + * @return unsanitized string + */ + // Use sparingly + public static function undisplay_str($Str) { + return mb_convert_encoding($Str, 'UTF-8', 'HTML-ENTITIES'); + } + + + /** + * Echo data sent in a GET form field, useful for text areas. + * + * @param string $Index the name of the form field + * @param boolean $Return if set to true, value is returned instead of echoed. + * @return Sanitized value of field index if $Return == true + */ + public static function form($Index, $Return = false) { + if (!empty($_GET[$Index])) { + if ($Return) { + return display_str($_GET[$Index]); + } else { + echo display_str($_GET[$Index]); + } + } + } + + + /** + * Convenience function to echo out selected='selected' and checked='checked' so you don't have to. + * + * @param $Name the name of the option in the select (or field in $Array) + * @param $Value the value that the option must be for the option to be marked as selected or checked + * @param $Attribute The value returned/echoed is $Attribute="$Attribute" + * @param $Array The array the option is in, defaults to GET. + * @return + */ + public static function selected($Name, $Value, $Attribute='selected', $Array = array()) { + if (empty($Array)) { + $Array == $_GET; + } + if (isset($Array[$Name]) && $Array[$Name]!=='') { + if ($Array[$Name] == $Value) { + echo ' '.$Attribute.'="'.$Attribute.'"'; + } + } + } + + /** + * Detect the encoding of a string and transform it to UTF-8. + * + * @param string $Str + * @return UTF-8 encoded version of $Str + */ + public static function make_utf8($Str) { + if ($Str!="") { + if (Format::is_utf8($Str)) { $Encoding="UTF-8"; } + if (empty($Encoding)) { $Encoding=mb_detect_encoding($Str,'UTF-8, ISO-8859-1'); } + if (empty($Encoding)) { $Encoding="ISO-8859-1"; } + if ($Encoding=="UTF-8") { return $Str; } + else { return @mb_convert_encoding($Str,"UTF-8",$Encoding); } + } + } + + /** + * Magical function. + * + * @param string $Str function to detect encoding on. + * @return true if the string is in UTF-8. + */ + public static function is_utf8($Str) { + return preg_match('%^(?: + [\x09\x0A\x0D\x20-\x7E] // ASCII + | [\xC2-\xDF][\x80-\xBF] // non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] // excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} // straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] // excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} // planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} // planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} // plane 16 + )*$%xs', $Str + ); + } + +} +?> diff --git a/classes/class_invite_tree.php b/classes/class_invite_tree.php index 26a96c40..d2b005dc 100644 --- a/classes/class_invite_tree.php +++ b/classes/class_invite_tree.php @@ -118,15 +118,15 @@ function make_tree(){ echo "
  • "; } ?> - + -  Uploaded: -  Downloaded: -  Ratio: +  Uploaded: +  Downloaded: +  Ratio: $ClassCount) { if($ClassCount == 0) { continue; } - $LastClass = make_class_string($ClassID); + $LastClass = Users::make_class_string($ClassID); if($ClassCount>1) { if($LastClass == "Torrent Celebrity") { $LastClass = 'Torrent Celebrities'; @@ -186,15 +186,15 @@ function make_tree(){ echo '.

    '; echo '

    '; - echo 'The total amount uploaded by the entire tree was '.get_size($TotalUpload); - echo '; the total amount downloaded was '.get_size($TotalDownload); - echo '; and the total ratio is '.ratio($TotalUpload, $TotalDownload).'. '; + echo 'The total amount uploaded by the entire tree was '.Format::get_size($TotalUpload); + echo '; the total amount downloaded was '.Format::get_size($TotalDownload); + echo '; and the total ratio is '.Format::get_ratio_html($TotalUpload, $TotalDownload).'. '; echo '

    '; echo '

    '; - echo 'The total amount uploaded by direct invitees (the top level) was '.get_size($TopLevelUpload); - echo '; the total amount downloaded was '.get_size($TopLevelDownload); - echo '; and the total ratio is '.ratio($TopLevelUpload, $TopLevelDownload).'. '; + echo 'The total amount uploaded by direct invitees (the top level) was '.Format::get_size($TopLevelUpload); + echo '; the total amount downloaded was '.Format::get_size($TopLevelDownload); + echo '; and the total ratio is '.Format::get_ratio_html($TopLevelUpload, $TopLevelDownload).'. '; echo 'These numbers include the stats of paranoid users, and will be factored in to the invitation giving script.

    '; diff --git a/classes/class_irc.php b/classes/class_irc.php index da036162..4046dd51 100644 --- a/classes/class_irc.php +++ b/classes/class_irc.php @@ -9,7 +9,7 @@ function halt($Msg) { abstract class IRC_BOT { abstract protected function connect_events(); abstract protected function channel_events(); - abstract protected function query_events(); + abstract protected function query_events(); abstract protected function listener_events(); protected $Debug = false; @@ -92,7 +92,7 @@ protected function get_message() { return trim($Msg[1]); } - protected function get_host() { + protected function get_irc_host() { preg_match('/:[^!:]+!.+@([^\s]+) PRIVMSG [^:]+ :.+/', $this->Data, $Host); return trim($Host[1]); } @@ -126,10 +126,10 @@ protected function whois($Nick) { $this->Whois = $Nick; $this->send_raw("WHOIS $Nick"); } - + /* - This function uses blacklisted_ip, which is no longer in RC2. - You can probably find it in old RC1 code kicking aronud if you need it. + This function uses blacklisted_ip, which is no longer in RC2. + You can probably find it in old RC1 code kicking aronud if you need it. protected function ip_check($IP,$Gline=false,$Channel=BOT_REPORT_CHAN) { global $Cache, $DB; if(blacklisted_ip($IP)) { @@ -209,7 +209,7 @@ protected function listen() { if($this->Listened = @socket_accept($this->ListenSocket)) { $this->listener_events(); } - + $DB->LinkID = false; $DB->Queries = array(); usleep(5000); diff --git a/classes/class_misc.php b/classes/class_misc.php new file mode 100644 index 00000000..48877a97 --- /dev/null +++ b/classes/class_misc.php @@ -0,0 +1,395 @@ +'."\r\n"; + $Headers.='Reply-To: '.$From.'@'.NONSSL_SITE_URL."\r\n"; + $Headers.='X-Mailer: Project Gazelle'."\r\n"; + $Headers.='Message-Id: <'.Users::make_secret().'@'.NONSSL_SITE_URL.">\r\n"; + $Headers.='X-Priority: 3'."\r\n"; + mail($To,$Subject,$Body,$Headers,"-f ".$From."@".NONSSL_SITE_URL); + } + + + /** + * Sanitize a string to be allowed as a filename. + * + * @param string $EscapeStr the string to escape + * @return the string with all banned characters removed. + */ + public static function file_string($EscapeStr) { + return str_replace(array('"','*','/',':','<','>','?','\\','|'), '', $EscapeStr); + } + + + /** + * Sends a PM from $FromId to $ToId. + * + * @param string $ToID ID of user to send PM to. If $ToID is an array and $ConvID is empty, a message will be sent to multiple users. + * @param string $FromID ID of user to send PM from, 0 to send from system + * @param string $Subject + * @param string $Body + * @param int $ConvID The conversation the message goes in. Leave blank to start a new conversation. + * @return + */ + public static function send_pm($ToID,$FromID,$Subject,$Body,$ConvID='') { + global $DB, $Cache, $Time; + if ($ToID == 0 || $ToID == $FromID) { + // Don't allow users to send messages to the system or themselves + return; + } + if ($ConvID=='') { + // Create a new conversation. + $DB->query("INSERT INTO pm_conversations(Subject) VALUES ('".$Subject."')"); + $ConvID = $DB->inserted_id(); + $DB->query("INSERT INTO pm_conversations_users + (UserID, ConvID, InInbox, InSentbox, SentDate, ReceivedDate, UnRead) VALUES + ('$ToID', '$ConvID', '1','0','".sqltime()."', '".sqltime()."', '1')"); + if ($FromID != 0) { + $DB->query("INSERT INTO pm_conversations_users + (UserID, ConvID, InInbox, InSentbox, SentDate, ReceivedDate, UnRead) VALUES + ('$FromID', '$ConvID', '0','1','".sqltime()."', '".sqltime()."', '0')"); + } + $ToID = array($ToID); + } else { + // Update the pre-existing conversations. + $DB->query("UPDATE pm_conversations_users SET + InInbox='1', + UnRead='1', + ReceivedDate='".sqltime()."' + WHERE UserID IN (".implode(',', $ToID).") + AND ConvID='$ConvID'"); + + $DB->query("UPDATE pm_conversations_users SET + InSentbox='1', + SentDate='".sqltime()."' + WHERE UserID='$FromID' + AND ConvID='$ConvID'"); + } + + // Now that we have a $ConvID for sure, send the message. + $DB->query("INSERT INTO pm_messages + (SenderID, ConvID, SentDate, Body) VALUES + ('$FromID', '$ConvID', '".sqltime()."', '".$Body."')"); + + // Update the cached new message count. + foreach ($ToID as $ID) { + $DB->query("SELECT COUNT(ConvID) FROM pm_conversations_users WHERE UnRead = '1' and UserID='$ID' AND InInbox = '1'"); + list($UnRead) = $DB->next_record(); + $Cache->cache_value('inbox_new_'.$ID, $UnRead); + } + + return $ConvID; + } + + + /** + * Create thread function, things should already be escaped when sent here. + * + * @param int $ForumID + * @param int $AuthorID ID of the user creating the post. + * @param string $Title + * @param string $PostBody + * @return -1 on error, -2 on user not existing, thread id on success. + */ + public static function create_thread($ForumID, $AuthorID, $Title, $PostBody) { + global $DB, $Cache, $Time; + if (!$ForumID || !$AuthorID || !is_number($AuthorID) || !$Title || !$PostBody) { + return -1; + } + + $DB->query("SELECT Username FROM users_main WHERE ID=".$AuthorID); + if ($DB->record_count() < 1) { + return -2; + } + list($AuthorName) = $DB->next_record(); + + $ThreadInfo = array(); + $ThreadInfo['IsLocked'] = 0; + $ThreadInfo['IsSticky'] = 0; + + $DB->query("INSERT INTO forums_topics + (Title, AuthorID, ForumID, LastPostTime, LastPostAuthorID) + Values + ('".$Title."', '".$AuthorID."', '$ForumID', '".sqltime()."', '".$AuthorID."')"); + $TopicID = $DB->inserted_id(); + $Posts = 1; + + $DB->query("INSERT INTO forums_posts + (TopicID, AuthorID, AddedTime, Body) + VALUES + ('$TopicID', '".$AuthorID."', '".sqltime()."', '".$PostBody."')"); + $PostID = $DB->inserted_id(); + + $DB->query("UPDATE forums SET + NumPosts = NumPosts+1, + NumTopics = NumTopics+1, + LastPostID = '$PostID', + LastPostAuthorID = '".$AuthorID."', + LastPostTopicID = '$TopicID', + LastPostTime = '".sqltime()."' + WHERE ID = '$ForumID'"); + + $DB->query("UPDATE forums_topics SET + NumPosts = NumPosts+1, + LastPostID = '$PostID', + LastPostAuthorID = '".$AuthorID."', + LastPostTime = '".sqltime()."' + WHERE ID = '$TopicID'"); + + // Bump this topic to head of the cache + list($Forum,,,$Stickies) = $Cache->get_value('forums_'.$ForumID); + if (!empty($Forum)) { + if (count($Forum) == TOPICS_PER_PAGE && $Stickies < TOPICS_PER_PAGE) { + array_pop($Forum); + } + $DB->query("SELECT f.IsLocked, f.IsSticky, f.NumPosts FROM forums_topics AS f + WHERE f.ID ='$TopicID'"); + list($IsLocked,$IsSticky,$NumPosts) = $DB->next_record(); + $Part1 = array_slice($Forum,0,$Stickies,true); //Stickys + $Part2 = array( + $TopicID=>array( + 'ID' => $TopicID, + 'Title' => $Title, + 'AuthorID' => $AuthorID, + 'IsLocked' => $IsLocked, + 'IsSticky' => $IsSticky, + 'NumPosts' => $NumPosts, + 'LastPostID' => $PostID, + 'LastPostTime' => sqltime(), + 'LastPostAuthorID' => $AuthorID, + ) + ); //Bumped thread + $Part3 = array_slice($Forum,$Stickies,TOPICS_PER_PAGE,true); //Rest of page + if ($Stickies > 0) { + $Part1 = array_slice($Forum,0,$Stickies,true); //Stickies + $Part3 = array_slice($Forum,$Stickies,TOPICS_PER_PAGE-$Stickies-1,true); //Rest of page + } else { + $Part1 = array(); + $Part3 = $Forum; + } + if (is_null($Part1)) { $Part1 = array(); } + if (is_null($Part3)) { $Part3 = array(); } + $Forum = $Part1 + $Part2 + $Part3; + $Cache->cache_value('forums_'.$ForumID, array($Forum,'',0,$Stickies), 0); + } + + //Update the forum root + $Cache->begin_transaction('forums_list'); + $UpdateArray = array( + 'NumPosts'=>'+1', + 'NumTopics'=>'+1', + 'LastPostID'=>$PostID, + 'LastPostAuthorID'=>$AuthorID, + 'LastPostTopicID'=>$TopicID, + 'LastPostTime'=>sqltime(), + 'Title'=>$Title, + 'IsLocked'=>$ThreadInfo['IsLocked'], + 'IsSticky'=>$ThreadInfo['IsSticky'] + ); + + $UpdateArray['NumTopics']='+1'; + + $Cache->update_row($ForumID, $UpdateArray); + $Cache->commit_transaction(0); + + $CatalogueID = floor((POSTS_PER_PAGE*ceil($Posts/POSTS_PER_PAGE)-POSTS_PER_PAGE)/THREAD_CATALOGUE); + $Cache->begin_transaction('thread_'.$TopicID.'_catalogue_'.$CatalogueID); + $Post = array( + 'ID'=>$PostID, + 'AuthorID'=>$LoggedUser['ID'], + 'AddedTime'=>sqltime(), + 'Body'=>$PostBody, + 'EditedUserID'=>0, + 'EditedTime'=>'0000-00-00 00:00:00', + 'Username'=>'' + ); + $Cache->insert('', $Post); + $Cache->commit_transaction(0); + + $Cache->begin_transaction('thread_'.$TopicID.'_info'); + $Cache->update_row(false, array('Posts'=>'+1', 'LastPostAuthorID'=>$AuthorID)); + $Cache->commit_transaction(0); + + return $TopicID; + } + + /** + * If the suffix of $Haystack is $Needle + * + * @param string $Haystack String to search in + * @param string $Needle String to search for + * @return boolean True if $Needle is a suffix of $Haystack + */ + public static function ends_with($Haystack, $Needle) { + return substr($Haystack, strlen($Needle) * -1) == $Needle; + } + + + /** + * If the preix of $Haystack is $Needle + * + * @param string $Haystack String to search in + * @param string $Needle String to search for + * @return boolean True if $Needle is a preix of $Haystack + */ + public static function starts_with($Haystack, $Needle) { + return strpos($Haystack, $Needle) === 0; + } + + /** + * Variant of in_array() with trailing wildcard support + * + * @param string $Needle, array $Haystack + * @return boolean true if (substring of) $Needle exists in $Haystack + */ + public static function in_array_partial($Needle, $Haystack) { + static $Searches = array(); + if (array_key_exists($Needle, $Searches)) { + return $Searches[$Needle]; + } + foreach ($Haystack as $String) { + if (substr($String, -1) == '*') { + if (!strncmp($Needle, $String, strlen($String)-1)) { + $Searches[$Needle] = true; + return true; + } + } elseif (!strcmp($Needle, $String)) { + $Searches[$Needle] = true; + return true; + } + } + $Searches[$Needle] = false; + return false; + } + + /** + * Used to check if keys in $_POST and $_GET are all set, and throws an error if not. + * This reduces 'if' statement redundancy for a lot of variables + * + * @param array $Request Either $_POST or $_GET, or whatever other array you want to check. + * @param array $Keys The keys to ensure are set. + * @param boolean $AllowEmpty If set to true, a key that is in the request but blank will not throw an error. + * @param int $Error The error code to throw if one of the keys isn't in the array. + */ + public static function assert_isset_request($Request, $Keys=NULL, $AllowEmpty = False, $Error=0) { + if (isset($Keys)) { + foreach ($Keys as $K) { + if (!isset($Request[$K]) || ($AllowEmpty == False && $Request[$K] == '')) { + error($Error); + break; + } + } + } else { + foreach ($Request as $R) { + if (!isset($R) || ($AllowEmpty == False && $R == '')) { + error($Error); + break; + } + } + } + } + + + /** + * Given an array of tags, return an array of their IDs. + * + * @param arary $TagNames + * @return array IDs + */ + public static function get_tags($TagNames) { + global $Cache, $DB; + $TagIDs = array(); + foreach ($TagNames as $Index => $TagName) { + $Tag = $Cache->get_value('tag_id_'.$TagName); + if (is_array($Tag)) { + unset($TagNames[$Index]); + $TagIDs[$Tag['ID']] = $Tag['Name']; + } + } + if (count($TagNames) > 0) { + $DB->query("SELECT ID, Name FROM tags WHERE Name IN ('".implode("', '", $TagNames)."')"); + $SQLTagIDs = $DB->to_array(); + foreach ($SQLTagIDs as $Tag) { + $TagIDs[$Tag['ID']] = $Tag['Name']; + $Cache->cache_value('tag_id_'.$Tag['Name'], $Tag, 0); + } + } + + return($TagIDs); + } + + + /** + * Gets the alias of the tag, if there is no alias silently returns the original tag. + * + * @param string $BadTag the tag we want to alias + * @return string The aliased tag. + */ + public static function get_alias_tag($BadTag) { + global $DB; + $DB->query("SELECT AliasTag FROM tag_aliases WHERE BadTag = '". $BadTag ."' LIMIT 1"); + if ($DB->record_count() > 0) { + list($AliasTag) = $DB->next_record(); + return $AliasTag; + } + return $BadTag; + } + + + /* + * Write a message to the system log. + * + * @param string $Message the message to write. + */ + public static function write_log($Message) { + global $DB,$Time; + $DB->query('INSERT INTO log (Message, Time) VALUES (\'' + .db_string($Message).'\', \''.sqltime().'\')'); + } + + + /** + * Get a tag ready for database input and display. + * + * @param string $Str + * @return sanitized version of $Str + */ + public static function sanitize_tag($Str) { + $Str = strtolower($Str); + $Str = preg_replace('/[^a-z0-9.]/', '', $Str); + $Str = preg_replace('/(^[.,]*)|([.,]*$)/','',$Str); + $Str = htmlspecialchars($Str); + $Str = db_string(trim($Str)); + return $Str; + } + + /** + * HTML escape an entire array for output. + * @param array $Array, what we want to escape + * @param boolean/array $Escape + * if true, all keys escaped + * if false, no escaping. + * If array, it's a list of array keys not to escape. + * @return mutated version of $Array with values escaped. + */ + public static function display_array($Array, $Escape = array()) { + foreach ($Array as $Key => $Val) { + if((!is_array($Escape) && $Escape == true) || !in_array($Key, $Escape)) { + $Array[$Key] = display_str($Val); + } + } + return $Array; + } +} +?> diff --git a/classes/class_mysql.php b/classes/class_mysql.php index c44cbc48..cd522de2 100644 --- a/classes/class_mysql.php +++ b/classes/class_mysql.php @@ -255,7 +255,7 @@ function next_record($Type=MYSQLI_BOTH, $Escape = true) { // $Escape can be true if (!is_array($this->Record)) { $this->QueryID = FALSE; } elseif($Escape !== FALSE){ - $this->Record = display_array($this->Record, $Escape); + $this->Record = Misc::display_array($this->Record, $Escape); } return $this->Record; } @@ -303,7 +303,7 @@ function to_array($Key = false, $Type = MYSQLI_BOTH, $Escape = true) { $Return = array(); while($Row = mysqli_fetch_array($this->QueryID,$Type)){ if($Escape!==FALSE) { - $Row = display_array($Row, $Escape); + $Row = Misc::display_array($Row, $Escape); } if($Key !== false) { $Return[$Row[$Key]] = $Row; diff --git a/classes/class_permissions.php b/classes/class_permissions.php new file mode 100644 index 00000000..6cda611c --- /dev/null +++ b/classes/class_permissions.php @@ -0,0 +1,100 @@ +=$MinClass + || $LoggedUser['EffectiveClass']>=$MinClass)) ? true : false; + } + + /** + * Gets the permissions associated with a certain permissionid + * + * @param int $PermissionID the kind of permissions to fetch + * @return array permissions + */ + public static function get_permissions($PermissionID) { + global $DB, $Cache; + $Permission = $Cache->get_value('perm_'.$PermissionID); + if (empty($Permission)) { + $DB->query("SELECT p.Level AS Class, p.Values as Permissions, p.Secondary, p.PermittedForums FROM permissions AS p WHERE ID='$PermissionID'"); + $Permission = $DB->next_record(MYSQLI_ASSOC, array('Permissions')); + $Permission['Permissions'] = unserialize($Permission['Permissions']); + $Cache->cache_value('perm_'.$PermissionID, $Permission, 2592000); + } + return $Permission; + } + + /** + * Get a user's permissions. + * + * @param $UserID + * @param array|false $CustomPermissions + * Pass in the user's custom permissions if you already have them. + * Leave false if you don't have their permissions, the function will fetch them. + * @return array Mapping of PermissionName=>bool/int + */ + public static function get_permissions_for_user($UserID, $CustomPermissions = false) { + global $DB; + + $UserInfo = Users::user_info($UserID); + + // Fetch custom permissions if they weren't passed in. + if ($CustomPermissions === false) { + $DB->query('SELECT um.CustomPermissions FROM users_main AS um + WHERE um.ID = '.((int)$UserID)); + list($CustomPermissions) = $DB->next_record(MYSQLI_NUM, false); + } + + if (!empty($CustomPermissions) && !is_array($CustomPermissions)) { + $CustomPermissions = unserialize($CustomPermissions); + } + + $Permissions = Permissions::get_permissions($UserInfo['PermissionID']); + + // Manage 'special' inherited permissions + $BonusPerms = array(); + $BonusCollages = 0; + foreach ($UserInfo['ExtraClasses'] as $PermID => $Value) { + $ClassPerms = Permissions::get_permissions($PermID); + $BonusCollages += $ClassPerms['Permissions']['MaxCollages']; + unset($ClassPerms['Permissions']['MaxCollages']); + $BonusPerms = array_merge($BonusPerms, $ClassPerms['Permissions']); + } + + if (!empty($CustomPermissions)) { + $CustomPerms = $CustomPermissions; + } else { + $CustomPerms = array(); + } + + // This is legacy donor cruft + if ($UserInfo['Donor']) { + $DonorPerms = Permissions::get_permissions(DONOR); + } else { + $DonorPerms = array('Permissions' => array()); + } + + $MaxCollages = $Permissions['Permissions']['MaxCollages'] + + $BonusCollages + + $CustomPerms['MaxCollages'] + + $DonorPerms['Permissions']['MaxCollages']; + + //Combine the permissions + return array_merge( + $Permissions['Permissions'], + $BonusPerms, + $CustomPerms, + $DonorPerms['Permissions'], + array('MaxCollages' => $MaxCollages)); + } +} +?> diff --git a/classes/class_requests.php b/classes/class_requests.php new file mode 100644 index 00000000..4ba0cd3b --- /dev/null +++ b/classes/class_requests.php @@ -0,0 +1,120 @@ +query("REPLACE INTO sphinx_requests_delta ( + ID, UserID, TimeAdded, LastVote, CategoryID, Title, + Year, ReleaseType, CatalogueNumber, BitrateList, + FormatList, MediaList, LogCue, FillerID, TorrentID, + TimeFilled, Visible, Votes, Bounty) + SELECT + ID, r.UserID, UNIX_TIMESTAMP(TimeAdded) AS TimeAdded, + UNIX_TIMESTAMP(LastVote) AS LastVote, CategoryID, + Title, Year, ReleaseType, CatalogueNumber, BitrateList, + FormatList, MediaList, LogCue, FillerID, TorrentID, + UNIX_TIMESTAMP(TimeFilled) AS TimeFilled, Visible, + COUNT(rv.UserID) AS Votes, SUM(rv.Bounty) >> 10 AS Bounty + FROM requests AS r LEFT JOIN requests_votes AS rv ON rv.RequestID=r.ID + wHERE ID = ".$RequestID." + GROUP BY r.ID"); + + $DB->query("UPDATE sphinx_requests_delta + SET ArtistList = (SELECT + GROUP_CONCAT(aa.Name SEPARATOR ' ') + FROM requests_artists AS ra + JOIN artists_alias AS aa ON aa.AliasID=ra.AliasID + WHERE ra.RequestID = ".$RequestID." + GROUP BY NULL) + WHERE ID = ".$RequestID); + + $Cache->delete_value('requests_'.$RequestID); + } + + + + /** + * Function to get data from an array of $RequestIDs. + * In places where the output from this is merged with sphinx filters, it will be in a different order. + * + * @param array $RequestIDs + * @param boolean $Return if set to false, data won't be returned (ie. if we just want to prime the cache.) + * @return The array of requests. + * Format: array(RequestID => Associative array) + * To see what's exactly inside each associate array, peek inside the function. It won't bite. + */ + // + //In places where the output from this is merged with sphinx filters, it will be in a different order. + public static function get_requests($RequestIDs, $Return = true) { + global $DB, $Cache; + + // Try to fetch the requests from the cache first. + $Found = array_flip($RequestIDs); + $NotFound = array_flip($RequestIDs); + + foreach ($RequestIDs as $RequestID) { + $Data = $Cache->get_value('request_'.$RequestID); + if (!empty($Data)) { + unset($NotFound[$RequestID]); + $Found[$RequestID] = $Data; + } + } + + $IDs = implode(',',array_flip($NotFound)); + + /* + Don't change without ensuring you change everything else that uses get_requests() + */ + + if (count($NotFound) > 0) { + $DB->query("SELECT + r.ID AS ID, + r.UserID, + u.Username, + r.TimeAdded, + r.LastVote, + r.CategoryID, + r.Title, + r.Year, + r.Image, + r.Description, + r.CatalogueNumber, + r.RecordLabel, + r.ReleaseType, + r.BitrateList, + r.FormatList, + r.MediaList, + r.LogCue, + r.FillerID, + filler.Username, + r.TorrentID, + r.TimeFilled, + r.GroupID, + r.OCLC + FROM requests AS r + LEFT JOIN users_main AS u ON u.ID=r.UserID + LEFT JOIN users_main AS filler ON filler.ID=FillerID AND FillerID!=0 + WHERE r.ID IN (".$IDs.") + ORDER BY ID"); + + $Requests = $DB->to_array(); + foreach ($Requests as $Request) { + unset($NotFound[$Request['ID']]); + $Request['Tags'] = get_request_tags($Request['ID']); + $Found[$Request['ID']] = $Request; + $Cache->cache_value('request_'.$Request['ID'], $Request, 0); + } + } + + if ($Return) { // If we're interested in the data, and not just caching it + $Matches = array('matches'=>$Found, 'notfound'=>array_flip($NotFound)); + return $Matches; + } + } +} +?> diff --git a/classes/class_sphinxql.php b/classes/class_sphinxql.php index 58f98b07..16a92874 100644 --- a/classes/class_sphinxql.php +++ b/classes/class_sphinxql.php @@ -3,7 +3,7 @@ error('Mysqli Extension not loaded.'); } -class SPHINXQL extends mysqli { +class SphinxQL extends mysqli { private static $Connections = array(); private $Server; private $Port; @@ -56,7 +56,7 @@ private function get_ident($Server, $Port, $Socket) { public static function init_connection($Server, $Port, $Socket) { $Ident = self::get_ident($Server, $Port, $Socket); if(!isset(self::$Connections[$Ident])) { - self::$Connections[$Ident] = new SPHINXQL($Server, $Port, $Socket); + self::$Connections[$Ident] = new SphinxQL($Server, $Port, $Socket); } return self::$Connections[$Ident]; } @@ -130,12 +130,12 @@ public function escape_string($String) { * @param param $QueryProcessTime time building and processing the query */ public function register_query($QueryString, $QueryProcessTime) { - SPHINXQL::$Queries[] = array($QueryString, $QueryProcessTime); - SPHINXQL::$Time += $QueryProcessTime; + SphinxQL::$Queries[] = array($QueryString, $QueryProcessTime); + SphinxQL::$Time += $QueryProcessTime; } } -class SPHINXQL_QUERY { +class SphinxQL_Query { private $SphinxQL; private $Expressions; @@ -157,7 +157,7 @@ class SPHINXQL_QUERY { * @param string $Socket Unix socket address, overrides $Server:$Port */ public function __construct($Server = SPHINXQL_HOST, $Port = SPHINXQL_PORT, $Socket = SPHINXQL_SOCK) { - $this->SphinxQL = SPHINXQL::init_connection($Server, $Port, $Socket); + $this->SphinxQL = SphinxQL::init_connection($Server, $Port, $Socket); $this->reset(); } @@ -248,7 +248,7 @@ public function where_match($Expr, $Field = '*') { if(empty($Expr)) { return $this; } - $this->Expressions[] = "@$Field ".SPHINXQL::escape_string($Expr); + $this->Expressions[] = "@$Field ".SphinxQL::escape_string($Expr); return $this; } @@ -370,7 +370,7 @@ public function query($GetMeta = true) { $QueryString = $this->QueryString; $Result = $this->send_query($GetMeta); $QueryProcessTime = (microtime(true) - $QueryStartTime)*1000; - SPHINXQL::register_query($QueryString, $QueryProcessTime); + SphinxQL::register_query($QueryString, $QueryProcessTime); return $Result; } @@ -405,7 +405,7 @@ private function send_query($GetMeta) { } else { $Meta = $GetMeta ? $this->get_meta() : null; } - return new SPHINXQL_RESULT($Result, $Meta, $Errno, $Error); + return new SphinxQL_Result($Result, $Meta, $Errno, $Error); } /** @@ -441,7 +441,7 @@ private function error($Msg, $Halt = false) { } } -class SPHINXQL_RESULT { +class SphinxQL_Result { private $Result; private $Meta; public $Errno; diff --git a/classes/class_text.php b/classes/class_text.php index 770fcf1d..f3fdcccb 100644 --- a/classes/class_text.php +++ b/classes/class_text.php @@ -458,24 +458,24 @@ function to_html($Array) { $Str.=''.$Block['Val'].''; break; case 'artist': - $Str.=''.$Block['Val'].''; + $Str.=''.$Block['Val'].''; break; case 'rule': $Rule = trim(strtolower($Block['Val'])); if($Rule[0] != 'r' && $Rule[0] != 'h') { $Rule = 'r'.$Rule; } - $Str.=''.preg_replace('/[aA-zZ]/', '', $Block['Val']).''; + $Str.=''.preg_replace('/[aA-zZ]/', '', $Block['Val']).''; break; case 'torrent': $Pattern = '/('.NONSSL_SITE_URL.'\/torrents\.php.*[\?&]id=)?(\d+)($|&|\#).*/i'; $Matches = array(); if (preg_match($Pattern, $Block['Val'], $Matches)) { if (isset($Matches[2])) { - $Groups = get_groups(array($Matches[2]), true, true, false); + $Groups = Torrents::get_groups(array($Matches[2]), true, true, false); if (!empty($Groups['matches'][$Matches[2]])) { $Group = $Groups['matches'][$Matches[2]]; - $Str .= display_artists($Group['ExtendedArtists']).''.$Group['Name'].''; + $Str .= Artists::display_artists($Group['ExtendedArtists']).''.$Group['Name'].''; } else { $Str .= '[torrent]'.str_replace('[inlineurl]','',$Block['Val']).'[/torrent]'; } diff --git a/classes/class_text2.php b/classes/class_text2.php index a77a1a15..46f79f29 100644 --- a/classes/class_text2.php +++ b/classes/class_text2.php @@ -470,7 +470,7 @@ function to_html($Array) { $Str.=''.$Block['Val'].''; break; case 'artist': - $Str.=''.$Block['Val'].''; + $Str.=''.$Block['Val'].''; break; case 'wiki': $Str.=''.$Block['Val'].''; diff --git a/classes/class_tools.php b/classes/class_tools.php new file mode 100644 index 00000000..5cf2e120 --- /dev/null +++ b/classes/class_tools.php @@ -0,0 +1,239 @@ +get_value('ip_bans'); + if (!is_array($IPBans)) { + $DB->query("SELECT ID, FromIP, ToIP FROM ip_bans"); + $IPBans = $DB->to_array(0, MYSQLI_NUM); + $Cache->cache_value('ip_bans', $IPBans, 0); + } + foreach ($IPBans as $Index => $IPBan) { + list ($ID, $FromIP, $ToIP) = $IPBan; + if ($IPNum >= $FromIP && $IPNum <= $ToIP) { + return true; + } + } + + return false; + } + + /** + * Returns the unsigned form of an IP address. + * + * @param string $IP The IP address x.x.x.x + * @return string the long it represents. + */ + public static function ip_to_unsigned($IP) { + return sprintf("%u", ip2long($IP)); + } + + /** + * Geolocate an IP address using the database + * + * @param $IP the ip to fetch the country for + * @return the country of origin + */ + public static function geoip($IP) { + static $IPs = array(); + if (isset($IPs[$IP])) { + return $IPs[$IP]; + } + $Long = Tools::ip_to_unsigned($IP); + if(!$Long || $Long == 2130706433) { // No need to check cc for 127.0.0.1 + return false; + } + global $DB; + $DB->query("SELECT EndIP,Code FROM geoip_country WHERE $Long >= StartIP ORDER BY StartIP DESC LIMIT 1"); + if((!list($EndIP,$Country) = $DB->next_record()) || $EndIP < $Long) { + $Country = '?'; + } + $IPs[$IP] = $Country; + return $Country; + } + + /** + * Gets the hostname for an IP address + * + * @param $IP the IP to get the hostname for + * @return hostname fetched + */ + public static function get_host_by_ip($IP) + { + $testar = explode('.',$IP); + if (count($testar)!=4) { + return $IP; + } + for ($i=0;$i<4;++$i) { + if (!is_numeric($testar[$i])) { + return $IP; + } + } + + $host = `host -W 1 $IP`; + return (($host ? end ( explode (' ', $host)) : $IP)); + } + + /** + * Gets an hostname using AJAX + * + * @param $IP the IP to fetch + * @return a span with javascript code + */ + public static function get_host_by_ajax($IP) { + static $ID = 0; + ++$ID; + return 'Resolving host...'; + } + + + /** + * Looks up the full host of an IP address, by system call. + * Used as the server-side counterpart to get_host_by_ajax. + * + * @param string $IP The IP address to look up. + * @return string the host. + */ + public static function lookup_ip($IP) { + //TODO: use the $Cache + $Output = explode(' ',shell_exec('host -W 1 '.escapeshellarg($IP))); + if(count($Output) == 1 && empty($Output[0])) { + //No output at all implies the command failed + return ''; + } + + if(count($Output) != 5) { + return false; + } else { + return $Output[4]; + } + } + + /** + * Format an IP address with links to IP history. + * + * @param string IP + * @return string The HTML + */ + public static function display_ip($IP) { + $Line = display_str($IP).' ('.Tools::get_country_code_by_ajax($IP).') '; + $Line .= '[S]'; + + return $Line; + } + + public static function get_country_code_by_ajax($IP) { + static $ID = 0; + ++$ID; + return 'Resolving CC...'; + } + + + + + /** + * Disable an array of users. + * + * @param array $UserIDs (You can also send it one ID as an int, because fuck types) + * @param BanReason 0 - Unknown, 1 - Manual, 2 - Ratio, 3 - Inactive, 4 - Unused. + */ + public static function disable_users($UserIDs, $AdminComment, $BanReason = 1) { + global $Cache, $DB; + if(!is_array($UserIDs)) { + $UserIDs = array($UserIDs); + } + $DB->query("UPDATE users_info AS i JOIN users_main AS m ON m.ID=i.UserID + SET m.Enabled='2', + m.can_leech='0', + i.AdminComment = CONCAT('".sqltime()." - ".($AdminComment ? $AdminComment : 'Disabled by system')."\n\n', i.AdminComment), + i.BanDate='".sqltime()."', + i.BanReason='".$BanReason."', + i.RatioWatchDownload=".($BanReason == 2?'m.Downloaded':"'0'")." + WHERE m.ID IN(".implode(',',$UserIDs).") "); + $Cache->decrement('stats_user_count',$DB->affected_rows()); + foreach($UserIDs as $UserID) { + $Cache->delete_value('enabled_'.$UserID); + $Cache->delete_value('user_info_'.$UserID); + $Cache->delete_value('user_info_heavy_'.$UserID); + $Cache->delete_value('user_stats_'.$UserID); + + $DB->query("SELECT SessionID FROM users_sessions WHERE UserID='$UserID' AND Active = 1"); + while(list($SessionID) = $DB->next_record()) { + $Cache->delete_value('session_'.$UserID.'_'.$SessionID); + } + $Cache->delete_value('users_sessions_'.$UserID); + + + $DB->query("DELETE FROM users_sessions WHERE UserID='$UserID'"); + + } + + // Remove the users from the tracker. + $DB->query("SELECT torrent_pass FROM users_main WHERE ID in (".implode(", ",$UserIDs).")"); + $PassKeys = $DB->collect('torrent_pass'); + $Concat = ""; + foreach($PassKeys as $PassKey) { + if(strlen($Concat) > 4000) { + Tracker::update_tracker('remove_users', array('passkeys' => $Concat)); + $Concat = $PassKey; + } else { + $Concat .= $PassKey; + } + } + Tracker::update_tracker('remove_users', array('passkeys' => $Concat)); + } + + /** + * Warn a user. + * + * @param int $UserID + * @param int $Duration length of warning in seconds + * @param string $reason + */ + public static function warn_user($UserID, $Duration, $Reason) { + global $LoggedUser, $DB, $Cache, $Time; + + $DB->query("SELECT Warned FROM users_info + WHERE UserID=".$UserID." + AND Warned <> '0000-00-00 00:00:00'"); + if($DB->record_count() > 0) { + //User was already warned, appending new warning to old. + list($OldDate) = $DB->next_record(); + $NewExpDate = date('Y-m-d H:i:s', strtotime($OldDate) + $Duration); + + Misc::send_pm($UserID, 0, + db_string("You have received multiple warnings."), + db_string("When you received your latest warning (Set to expire on ".date("Y-m-d", (time() + $Duration))."), you already had a different warning (Set to expire on ".date("Y-m-d", strtotime($OldDate)).").\n\n Due to this collision, your warning status will now expire at ".$NewExpDate.".")); + + $AdminComment = date("Y-m-d").' - Warning (Clash) extended to expire at '.$NewExpDate.' by '.$LoggedUser['Username']."\nReason: $Reason\n\n"; + + $DB->query('UPDATE users_info SET + Warned=\''.db_string($NewExpDate).'\', + WarnedTimes=WarnedTimes+1, + AdminComment=CONCAT(\''.db_string($AdminComment).'\',AdminComment) + WHERE UserID=\''.db_string($UserID).'\''); + } else { + //Not changing, user was not already warned + $WarnTime = time_plus($Duration); + + $Cache->begin_transaction('user_info_'.$UserID); + $Cache->update_row(false, array('Warned' => $WarnTime)); + $Cache->commit_transaction(0); + + $AdminComment = date("Y-m-d").' - Warned until '.$WarnTime.' by '.$LoggedUser['Username']."\nReason: $Reason\n\n"; + + $DB->query('UPDATE users_info SET + Warned=\''.db_string($WarnTime).'\', + WarnedTimes=WarnedTimes+1, + AdminComment=CONCAT(\''.db_string($AdminComment).'\',AdminComment) + WHERE UserID=\''.db_string($UserID).'\''); + } + } +} +?> diff --git a/classes/class_torrent_form.php b/classes/class_torrent_form.php index 905e187e..547a4213 100644 --- a/classes/class_torrent_form.php +++ b/classes/class_torrent_form.php @@ -93,9 +93,9 @@ function head() { -Formats) as $Format) { +Formats) as $Format) { echo "