diff options
-rw-r--r-- | artists.php | 98 | ||||
-rwxr-xr-x | docs/CHANGELOG | 2 | ||||
-rw-r--r-- | lib/class/artist.class.php | 139 | ||||
-rw-r--r-- | lib/duplicates.php | 2 | ||||
-rw-r--r-- | lib/ui.lib.php | 2 | ||||
-rw-r--r-- | templates/show_artist_box.inc.php | 1 | ||||
-rw-r--r-- | templates/show_rename_artist.inc.php | 2 | ||||
-rw-r--r-- | templates/show_similar_artists.inc | 159 |
8 files changed, 377 insertions, 28 deletions
diff --git a/artists.php b/artists.php index cbaa6e0a..b1f4452a 100644 --- a/artists.php +++ b/artists.php @@ -47,6 +47,7 @@ switch($action) { $artist = new Artist($_REQUEST['artist']); $artist->format_artist(); $song_ids = $artist->get_song_ids(); + $artist_id = $artist->id; require(conf('prefix') . '/templates/show_artist_box.inc.php'); show_songs($song_ids); break; @@ -64,6 +65,62 @@ switch($action) { echo "<a href=\"" . conf('web_path') . "/artists.php?action=show&artist=" . $_REQUEST['artist'] . "\">[" . _("Return") . "]</a>"; break; + case 'rename_similar': + if (!$user->has_access('100')) { access_denied(); } + $count = 0; + if (isset($_REQUEST['artist']) && is_numeric($_REQUEST['artist']) && isset($_REQUEST['artists']) && is_array($_REQUEST['artists'])) { + $artist = new Artist($_REQUEST['artist']); + if ($artist->id) + foreach ($_REQUEST['artists'] as $artist_id) { + if (is_numeric($artist_id)) { + $that_artist = new Artist($artist_id); + if ($that_artist->id) { + $that_artist->merge($artist->id); + $count++; + } else + $GLOBALS['error']->add_error('general',"Error: No such artist '$artist_id'"); + } else { + $GLOBALS['error']->add_error('general',"Error: '$artist_id' is not a valid ID"); + } + } + else + $GLOBALS['error']->add_error('general',"Error: No such artist '" . $_REQUEST['artist'] . "'"); + } else { + $GLOBALS['error']->add_error('general',"Error: Errenous request"); + } + if ($count > 0) { + show_confirmation ( + "Renamed artist(s)", + "$count artists have been merged with " . $artist->name, + conf('web_path') . "/artists.php?action=show&artist=" . $artist->id + ); + } else { + $GLOBALS['error']->print_error('general'); + } + + break; + case 'show_similar': + if (!$user->has_access('100')) { access_denied(); } + + if (isset($_REQUEST['artist'])) { + $artist = new Artist($_REQUEST['artist']); + //options + $similar_artists = $artist->get_similar_artists( + make_bool($_POST['n_rep_uml']), + $_POST['n_filter'], + $_POST['n_ignore'], + $_POST['c_mode'], + $_POST['c_count_w'], + $_POST['c_percent_w'], + $_POST['c_distance_l'], + make_bool($_POST['c_ignins_l'])); + $artist_id = $artist->id; + $artist_name = $artist->name; + require (conf('prefix') . '/templates/show_similar_artists.inc'); + } else { + $GLOBALS['error']->add_error('general',"Error: No artist given"); + } + break; case 'rename': //die if not enough permissions if (!$user->has_access('100')) { access_denied(); } @@ -78,39 +135,50 @@ switch($action) { //if we want to update id3 tags, then get the array of ids now, it's too late afterwards if (make_bool($_POST['update_id3'])) $songs = $artist->get_songs(); - - //the manual rename takes priority - if ($_POST['artist_name'] != "") { - //then just change the name of the artist in the db - $newid = $artist->rename($_POST['artist_name']); + $ret = 0; + //the manual rename takes priority, but if they tested out the insert thing ignore + if ($_POST['artist_name'] != "" && $_POST['artist_name'] != $artist->name) { + //then just change the name of the artist in the db + $ret = $artist->rename($_POST['artist_name']); + $newid = $ret; + $newname = $_POST['artist_name']; } + //new id? elseif ($_POST['artist_id'] != $artist->id) { - if ($_POST['test_stats'] == 'yes') { - $catalog->merge_stats("artist",$artist->id,$_POST['artist_id']); - } - else { //merge with other artist - $artist->merge($_POST['artist_id']); - $newid = $_POST['artist_id']; - } + $ret = $artist->merge($_POST['artist_id']); + $newid = $_POST['artist_id']; + $newname = $ret; } // elseif different artist and id + //if no changes, no changes - //now flag for id3tag update if selected, and song id changed - if ($_POST['update_id3'] == "yes" && $newid != $artist->id) { + //now flag for id3tag update if selected, and something actually happaned + if ($ret && make_bool($_POST['update_id3'])) { /* Set the rename information in the db */ foreach ($songs as $song) { + $flag = new Flag(); + $flag->add($song->id,"song","retag","Renamed artist, retag"); $flag_qstring = "REPLACE INTO flagged " . "SET type = 'setid3', song = '" . $song->id . "', date = '" . time() . "', user = '" . $GLOBALS['user']->username . "'"; mysql_query($flag_qstring, dbh()); } } // end if they wanted to update + + // show something other than a blank screen after this + if ($ret) { + show_confirmation ( + "Renamed artist", + $artist->name . " is now known as " . $newname, + conf('web_path') . "/artists.php?action=show&artist=" . $newid + ); + } } // if we've got the needed variables - /* Else we've got an error! */ + /* Else we've got an error! But be lenient, and just show the form again */ else { require (conf('prefix') . '/templates/show_rename_artist.inc.php'); } diff --git a/docs/CHANGELOG b/docs/CHANGELOG index 0808c354..0185645e 100755 --- a/docs/CHANGELOG +++ b/docs/CHANGELOG @@ -4,6 +4,8 @@ -------------------------------------------------------------------------- v.3.3.2-Beta2 + - Added loose name compare and rename functions to help with sorting + similar artist names (Thx SpComb) - Fixed a problem with not being able to add Albums to a playlist (Thx eudaimon) - Fixed a problem with browsing genres that would incorrectly put diff --git a/lib/class/artist.class.php b/lib/class/artist.class.php index 1b96b0aa..334472e3 100644 --- a/lib/class/artist.class.php +++ b/lib/class/artist.class.php @@ -205,7 +205,7 @@ class Artist { @param $newname the artist's new name, either a new artist will be created or songs added to existing artist if name exists already - @return the id of the new artist + @return the id of the new artist, or false if an error */ function rename($newname) { @@ -221,11 +221,12 @@ class Artist { /* check that it wasn't just whitespace that we were called to change */ if ($newid == $this->id) { $GLOBALS['error']->add_error('artist_name',_("Error: Name Identical")); - return $newid; + return false; } /* now we can just call merge */ - $this->merge($newid); + if (!$this->merge($newid)) + return false; //now return id return $newid; @@ -237,6 +238,7 @@ class Artist { @discussion changes the artist id of all songs by this artist to the given id and deletes self from db @param $newid the new artist id that this artist's songs should have + @return the name of the new artist on success, false if error */ function merge($newid) { @@ -249,15 +251,14 @@ class Artist { } // First check newid exists - $check_exists_qstring = "SELECT name FROM artist WHERE id='" . sql_escape($newid) . "'"; + $check_exists_qstring = "SELECT name FROM artist WHERE id='" . $newid . "'"; //no need to escape newid, it's numeric $check_exists_query = mysql_query($check_exists_qstring, dbh()); - if ($check_exists_results = mysql_fetch_assoc($check_exists_query)) { - + if ($check_exists_result = mysql_fetch_assoc($check_exists_query)) { $NewName = $check_exists_result['name']; // Now the query - $sql = "UPDATE song SET artist='" . sql_escape($newid) . "' " . + $sql = "UPDATE song SET artist='" . $newid . "' " . "WHERE artist='" . sql_escape($this->id) . "'"; $db_results = mysql_query($sql, dbh()); @@ -266,9 +267,11 @@ class Artist { /* If we've done the merege we need to clean up */ $catalog->clean_artists(); $catalog->clean_albums(); + + return $NewName; } else { - $GLOBALS['error']->add_error('general',"Error: Invalid Artist ID"); + $GLOBALS['error']->add_error('general',"Error: No such artist to merge with"); return false; } } // merge @@ -290,7 +293,125 @@ class Artist { require (conf('prefix') . "/templates/show_artist.inc"); } // show_albums - + + /*! + @function get_similar_artists + @discussion returns an array of artist (id,name) arrays that are similar in name + All whitespace and special chars are ignored + @param extra arguments to normalize and compre, in that order + @return array of artist, each element is (id,name) + */ + function get_similar_artists ($n_rep_uml,$n_filter,$n_ignore,$c_mode,$c_count_w,$c_percent_w,$c_distance_l) { + //strip out just about everything, including whitespace, numbers and weird chars, and then + //lowercase it + $name = $this->normalize_name($this->name,$n_rep_uml,$n_filter,$n_ignore); + + //now for a bit of mysql query + $sql = "SELECT id, name FROM artist WHERE id != '" . sql_escape($this->id) . "'"; + $query = mysql_query($sql, dbh()); + //loop it + $similar_artists = array(); + while ($r = mysql_fetch_assoc($query)) { + $artist_name = $this->normalize_name($r['name'],$n_rep_uml,$n_filter,$n_ignore); + //echo "'" . $r['name'] . "' => '" . $artist_name . "'<br/>\n"; + if ($this->compare_loose($name,$artist_name,$c_mode,$c_count_w,$c_percent_w,$c_distance_l)) { + //echo "***MATCH***<br/>\n"; + $similar_artists[] = array($r['id'],$r['name']); + } + } + return $similar_artists; + } // get_similar_artists + + + /*! + @function normalize_name + @param artist name to normalize + @param $replace_umlaut wether to replace umlauts and others with the plain letter, default true + @param $filter what to filter out, defulat /[^a-z ]/ + @param $ignore terms to ignore, default /\s(the|an?)\s/ (name is padded with whitespace beforehand) + @returns the normalized version of the given artist name, containing only letters and single spaces + */ + function normalize_name ($name,$replace_umlaut = NULL, $filter = NULL, $ignore = NULL) { + if (is_null($replace_umlaut)) $replace_umlaut = true; + if (is_null($filter)) $filter = "/[^a-z ]/"; + if (is_null($ignore)) $ignore = "/\s(the|an?)\s/"; + if ($replace_umlaut) { + //convert ümlauts, idea from http://php.net/manual/en/function.str-replace.php#50081 + $umlauts = array("uml","acute","grave","cedil","ring","circ","tilde","lig","slash"); + $name = str_replace($umlauts,"",htmlentities($name)); + //now replace all &.; with . + $name = preg_replace("/&(.);/","\$1",$name); + //back to normal + $name = html_entity_decode($name); + } + //lowercase + $name = strtolower($name); + //now rip out all the special chars and spaces + $name = preg_replace($filter,"",$name); + //now certains terms can be dropped completely + //we have to add spaces on the sides though + $name = " " . $name . " "; + $name = preg_replace($ignore,"",$name); + //now single spaces + $name = preg_replace("/\s{2,}/"," ",$name); + //return + return trim($name); + } //normalize_name + + /*! + @function compare_loose + @discussion percent and count are ORed together + @param $name1 artist name + @param $name2 artist name to compare against + @param $mode the type of matching to perform, one of line or word, default word + @param $countwords WORD MODE number of words that must be shared to match, 0 to disable, default 0 + @param $percentwords WORD MODE percentage of words that must be shared to match, 0 to disable, default 50% + @param $distance LETTER MODE max levenshtein distance to pass as a match + @return true if given params are similar, false if not + */ + function compare_loose ($name1,$name2,$mode = NULL,$countwords = NULL,$percentwords = NULL,$distance = NULL) { + if (is_null($mode)) $mode = "word"; + if (is_null($countwords)) $countwords = 0; + if (is_null($percentwords)) $percentwords = 50; + if (is_null($distance)) $distance = 2; + + //echo "Compare '$name1' vs. '$name2'<br/>\n"; + + $modes = array("line" => 0,"word" => 0,"letter" => 0); + $mode = (isset($modes[$mode]) ? $mode : "word"); + switch ($mode) { + case "line": + //this is still relevant because of the normalize + return $name1 == $name2; + break; + case "word": + //echo " COMPARE: Word mode<br/>\n"; + //first, count the number of terms in name1, and then the number that also appear in name2 + $words = explode(" ",$name1); + $num_words = count($words); + $num_words_shared = 0; + foreach ($words as $word) { + //echo " Looking for word '$word'... "; + if (strpos($name2,$word) !== false) { + //echo "MATCHED"; + $num_words_shared++; + } else { + //echo " Nope"; + } + //echo "<br/>\n"; + } + //now make the descision + return ( + ($countwords > 0 && $num_words_shared >= $countwords) || + ($percentwords > 0 && $num_words_shared > 0 && $num_words_shared/$num_words >= $percentwords/100) + ); + break; + case "letter": + //simple + return levenshtein($name1,$name2) <= $distance; + break; + } + } //compare_loose } //end of artist class diff --git a/lib/duplicates.php b/lib/duplicates.php index 75ca2a22..1d03a3da 100644 --- a/lib/duplicates.php +++ b/lib/duplicates.php @@ -42,8 +42,6 @@ function get_duplicate_songs($search_type) { $sql = $sql." HAVING count(title) > 1"; $sql = $sql." ORDER BY ctitle"; - //echo $sql."<BR>"; - $result = mysql_query($sql, dbh()); $arr = array(); diff --git a/lib/ui.lib.php b/lib/ui.lib.php index 44a67710..128ba533 100644 --- a/lib/ui.lib.php +++ b/lib/ui.lib.php @@ -880,7 +880,7 @@ function show_artist_pulldown ($artist_id,$select_name='artist') { $artist->get_count(); if ( $artist_id == $r['id'] ) { - echo "\t<option value=\"" . $artist->id . "\" selected=\"selected\">". scrub_out($artist->name) . " (" . $artist->songs . ")</option>\n"; + echo "\t<option value=\"" . $artist->id . "\" selected=\"selected\">". scrub_out($artist->name) . "</option>\n"; } else { echo "\t<option value=\"" . $artist->id . "\">". scrub_out($artist->name) ."</option>\n"; diff --git a/templates/show_artist_box.inc.php b/templates/show_artist_box.inc.php index 75026d7e..8c9779f4 100644 --- a/templates/show_artist_box.inc.php +++ b/templates/show_artist_box.inc.php @@ -33,6 +33,7 @@ $web_path = conf('web_path'); <?php if ($user->has_access('100')) { ?> <li><a href="<?php echo $web_path; ?>/artists.php?action=update_from_tags&artist=<?php echo $artist_id; ?>"><?php echo _("Update from tags"); ?></a></li> <li><a href="<?php echo $web_path; ?>/artists.php?action=show_rename&artist=<?php echo $artist_id; ?>"><?php echo _("Rename Artist"); ?></a></li> + <li><a href="<?php echo $web_path; ?>/artists.php?action=show_similar&artist=<?php echo $artist_id; ?>"><?php echo _("Find duplicate artists"); ?></a></li> <?php } ?> </ul> </td> diff --git a/templates/show_rename_artist.inc.php b/templates/show_rename_artist.inc.php index 929c54c2..72f707ff 100644 --- a/templates/show_rename_artist.inc.php +++ b/templates/show_rename_artist.inc.php @@ -45,7 +45,7 @@ function insert() </td> </tr> <tr> - <td><input type="checkbox" name="update_id3" value="1" /> <?php echo _("Update id3 tags"); ?></td> + <td><input type="checkbox" name="update_id3" value="yes" /> <?php echo _("Update id3 tags"); ?></td> </tr> <tr> <td> diff --git a/templates/show_similar_artists.inc b/templates/show_similar_artists.inc new file mode 100644 index 00000000..25a9f6d2 --- /dev/null +++ b/templates/show_similar_artists.inc @@ -0,0 +1,159 @@ +<?php +/* + + Copyright (c) 2001 - 2006 Ampache.org + All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +$web_path = conf('web_path'); +?> +<form name="artists" method="post" enctype="multipart/form-data" action="<?php echo $web_path; ?>/artists.php?action=rename_similar&artist=<?php echo $artist_id;?>" style="Display:inline;"> + <h1>Similar Artists</h1> + <h3>Please check the artists you want to merge with the current one (<span style='text-decoration: underline;'><?php echo $artist_name;?></span>)</h3> + <table class="border" cellspacing="0" cellpadding="0" border="0"> + <tr class="table-header"> + <th> Select</th> + <th><?php echo _("Artist"); ?></th> + </tr> +<?php +if (count($similar_artists) > 0) { + $show = true; + foreach ($similar_artists as $artist_array) { +?> + <tr class="<?php echo flip_class(); ?>"> + <td align="center"> + <input type="checkbox" name="artists[]" value="<?php echo $artist_array[0]; ?>" /> + </td> + <td> + <a href="<?php echo $web_path; ?>/artists.php?action=show&artist=<?php echo $artist_array[0]; ?>" title="<?php echo htmlspecialchars($artist_array[1]); ?>" <?php echo $text_class; ?>><?php echo htmlspecialchars($artist_array[1]); ?></a> + </td> + </tr> +<?php + }// foreach loop +} else { // no similar artists + $show = false; +?> + <tr class="<?php echo flip_class(); ?>"> + <td align="center"> + + </td> + <td> + No Similar Artists found + </td> + </tr> + <tr class="<?php echo flip_class(); ?>"> + <td align="center"> + + </td> + <td> + <input class="button" type="button" name="action" value="<?php echo _("Back"); ?>" onclick="window.location.href = '<?php echo $web_path; ?>/artists.php?action=show&artist=<?php echo $artist_id;?>';" /> + </td> + </tr> +<?php +} +?> +<?php +if ($show) { +?> +<tr align="left" class="<?php echo flip_class(); ?>"> + <td colspan='2'> + <input class="button" type="button" name="action" value="<?php echo _("Rename selected"); ?>" onclick="document.artists.submit();" /> + <input class="button" type="button" name="action" value="<?php echo _("Cancel"); ?>" onclick="window.location.href = '<?php echo $web_path; ?>/artists.php?action=show&artist=<?php echo $artist_id;?>';" /> + </td> +</tr> +<?php +} +?> +</table> +</form> +<form name='advanced' action='<?php echo $web_path; ?>/artists.php?action=show_similar&artist=<?php echo $artist_id; ?>' method='POST'> +<table class="border" cellspacing="0" cellpadding="0" border="0" style='margin-left: 10px; margin-top: 20px;'> +<!--Advanced--> + <tr class="table-header"> + <th colspan='2'>Advanced options</th> + </tr> + <tr class="<?php echo flip_class(); ?>"> + <th colspan='2' style='text-align: left;'>Normalize options</th> + </tr> + <tr class="<?php echo flip_class(); ?>"> + <td colspan='2'> + <label for='n_rep_uml' style='display: inline;'>Replace ümlauts and áccents with normal letters</label> + <input type='checkbox' name='n_rep_uml' id='n_rep_uml' value='yes' style='display: inline;' <?php echo (is_null($_POST['n_rep_uml']) || make_bool($_POST['n_rep_uml']) ? "checked='checked'" : ''); ?>/> + <br/> + <label for='n_filter' style='display: inline;'>filter out (regexp)</label> + <input type='text' name='n_filter' id='n_filter' value='<?php echo (is_null($_POST['n_filter']) ? "/[^a-z ]/" : $_POST['n_filter']); ?>' style='display: inline;'/> + <a href='javascript:default_filter();'>default</a> + <br/> + <label for='n_ignore' style='display: inline;'>ignore (regexp)</label> + <input type='text' name='n_ignore' id='n_ignore' value='<?php echo (is_null($_POST['n_ignore']) ? "/\s(the|an?)\s/" : $_POST['n_ignore']); ?>' style='display: inline;'/> + <a href='javascript:default_ignore();'>default</a> + <br/> + </td> + </tr> + <tr class="<?php echo flip_class(); ?>"> + <th colspan='2' style='text-align: left;'>Compare options</th> + </tr> + <tr class="<?php echo flip_class(); ?>"> + <td> + <label for='c_mode' style='display: inline;'>Line mode</label> + <input type='radio' name='c_mode' id='c_mode' value='line' style='display: inline;' <?php echo (is_null($_POST['c_mode']) || $_POST['c_mode'] == "line" ? "checked='checked'" : ''); ?> /> + </td> + <td> + <No Options> + </td> + </tr> + + <tr class="<?php echo flip_class(); ?>"> + <td> + <label for='c_mode' style='display: inline;'>Word mode</label> + <input type='radio' name='c_mode' id='c_mode' value='word' style='display: inline;' <?php echo (is_null($_POST['c_mode']) || $_POST['c_mode'] == "word" ? "checked='checked'" : ''); ?> /> + </td> + <td> + <label for='c_count_w' style='display: inline;'># words to match (0=disable)</label> + <input type='text' name='c_count_w' id='c_count_w' value='<?php echo (is_null($_POST['c_count_w']) ? "0" : $_POST['c_count_w']); ?>' style='display: inline;' size='2'/> + <br/>OR<br/> + <label for='c_percent_w' style='display: inline;'>% words to match (0=disable)</label> + <input type='text' name='c_percent_w' id='c_percent_w' value='<?php echo (is_null($_POST['c_percent_w']) ? "50" : $_POST['c_percent_w']); ?>' style='display: inline;' size='4'/>% + <br/> + </td> + </tr> + <tr class="<?php echo flip_class(); ?>"> + <td> + <label for='c_mode' style='display: inline;'>Letter mode</label> + <input type='radio' name='c_mode' id='c_mode' value='letter' style='display: inline;' <?php echo ($_POST['c_mode'] == "letter" ? "checked='checked'" : ''); ?> /> + </td> + <td> + <label for='c_distance_l' style='display: inline;'>Max (levenshtein) difference, the larger the looser</label> + <input type='text' name='c_distance_l' id='c_distance_l' value='<?php echo (is_null($_POST['c_distance_l']) ? "2" : $_POST['c_distance_l']); ?>' style='display: inline;' size='2'/> + </td> + </tr> + <tr class="<?php echo flip_class(); ?>"> + <td colspan='2'> + <input class="button" type="button" name="action" value="<?php echo _("Search Again"); ?>" onclick="document.advanced.submit();" /> + </td> + </tr> + +</table> +</form> +<script language='javascript'> + function default_filter() { + document.getElementById('n_filter').value = "/[^a-z ]/"; + } + function default_ignore() { + document.getElementById('n_ignore').value = "/\s(the|an?)\s/"; + } +</script>
\ No newline at end of file |