1
0
Fork 0
mirror of https://github.com/Yetangitu/ampache synced 2025-10-03 09:49:30 +02:00
ampache/lib/class/search.class.php
2015-04-21 00:05:41 +02:00

1483 lines
48 KiB
PHP

<?php
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
/**
*
* LICENSE: GNU General Public License, version 2 (GPLv2)
* Copyright 2001 - 2015 Ampache.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License v2
* as published by the Free Software Foundation
*
* 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.
*
*/
/**
* Search Class
* Search-related voodoo. Beware tentacles.
*/
class Search extends playlist_object
{
public $searchtype;
public $rules;
public $logic_operator = 'AND';
public $type = 'public';
public $random = false;
public $limit = 0;
public $basetypes;
public $types;
public $link;
public $f_link;
/**
* constructor
*/
public function __construct($id = null, $searchtype = 'song')
{
$this->searchtype = $searchtype;
if ($id) {
$info = $this->get_info($id);
foreach ($info as $key=>$value) {
$this->$key = $value;
}
$this->rules = unserialize($this->rules);
}
// Define our basetypes
$this->basetypes['numeric'][] = array(
'name' => 'gte',
'description' => T_('is greater than or equal to'),
'sql' => '>='
);
$this->basetypes['numeric'][] = array(
'name' => 'lte',
'description' => T_('is less than or equal to'),
'sql' => '<='
);
$this->basetypes['numeric'][] = array(
'name' => 'equal',
'description' => T_('is'),
'sql' => '<=>'
);
$this->basetypes['numeric'][] = array(
'name' => 'ne',
'description' => T_('is not'),
'sql' => '<>'
);
$this->basetypes['numeric'][] = array(
'name' => 'gt',
'description' => T_('is greater than'),
'sql' => '>'
);
$this->basetypes['numeric'][] = array(
'name' => 'lt',
'description' => T_('is less than'),
'sql' => '<'
);
$this->basetypes['boolean'][] = array(
'name' => 'true',
'description' => T_('is true')
);
$this->basetypes['boolean'][] = array(
'name' => 'false',
'description' => T_('is false')
);
$this->basetypes['text'][] = array(
'name' => 'contain',
'description' => T_('contains'),
'sql' => 'LIKE',
'preg_match' => array('/^/','/$/'),
'preg_replace' => array('%', '%')
);
$this->basetypes['text'][] = array(
'name' => 'notcontain',
'description' => T_('does not contain'),
'sql' => 'NOT LIKE',
'preg_match' => array('/^/','/$/'),
'preg_replace' => array('%', '%')
);
$this->basetypes['text'][] = array(
'name' => 'start',
'description' => T_('starts with'),
'sql' => 'LIKE',
'preg_match' => '/$/',
'preg_replace' => '%'
);
$this->basetypes['text'][] = array(
'name' => 'end',
'description' => T_('ends with'),
'sql' => 'LIKE',
'preg_match' => '/^/',
'preg_replace' => '%'
);
$this->basetypes['text'][] = array(
'name' => 'equal',
'description' => T_('is'),
'sql' => '='
);
$this->basetypes['text'][] = array(
'name' => 'sounds',
'description' => T_('sounds like'),
'sql' => 'SOUNDS LIKE'
);
$this->basetypes['text'][] = array(
'name' => 'notsounds',
'description' => T_('does not sound like'),
'sql' => 'NOT SOUNDS LIKE'
);
$this->basetypes['boolean_numeric'][] = array(
'name' => 'equal',
'description' => T_('is'),
'sql' => '<=>'
);
$this->basetypes['boolean_numeric'][] = array(
'name' => 'ne',
'description' => T_('is not'),
'sql' => '<>'
);
$this->basetypes['boolean_subsearch'][] = array(
'name' => 'equal',
'description' => T_('is'),
'sql' => ''
);
$this->basetypes['boolean_subsearch'][] = array(
'name' => 'ne',
'description' => T_('is not'),
'sql' => 'NOT'
);
$this->basetypes['date'][] = array(
'name' => 'lt',
'description' => T_('before'),
'sql' => '<'
);
$this->basetypes['date'][] = array(
'name' => 'gt',
'description' => T_('after'),
'sql' => '>'
);
switch ($searchtype) {
case 'song':
$this->types[] = array(
'name' => 'anywhere',
'label' => T_('Any searchable text'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'title',
'label' => T_('Title'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'album',
'label' => T_('Album'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'artist',
'label' => T_('Artist'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'composer',
'label' => T_('Composer'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'comment',
'label' => T_('Comment'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'label',
'label' => T_('Label'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'tag',
'label' => T_('Tag'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'file',
'label' => T_('Filename'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'year',
'label' => T_('Year'),
'type' => 'numeric',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'time',
'label' => T_('Length (in minutes)'),
'type' => 'numeric',
'widget' => array('input', 'text')
);
if (AmpConfig::get('ratings')) {
$this->types[] = array(
'name' => 'rating',
'label' => T_('Rating'),
'type' => 'numeric',
'widget' => array(
'select',
array(
'1 Star',
'2 Stars',
'3 Stars',
'4 Stars',
'5 Stars'
)
)
);
}
if (AmpConfig::get('show_played_times')) {
$this->types[] = array(
'name' => 'played_times',
'label' => T_('# Played'),
'type' => 'numeric',
'widget' => array('input', 'text')
);
}
$this->types[] = array(
'name' => 'bitrate',
'label' => T_('Bitrate'),
'type' => 'numeric',
'widget' => array(
'select',
array(
'32',
'40',
'48',
'56',
'64',
'80',
'96',
'112',
'128',
'160',
'192',
'224',
'256',
'320'
)
)
);
$this->types[] = array(
'name' => 'played',
'label' => T_('Played'),
'type' => 'boolean',
'widget' => array('input', 'hidden')
);
$this->types[] = array(
'name' => 'added',
'label' => T_('Added'),
'type' => 'date',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'updated',
'label' => T_('Updated'),
'type' => 'date',
'widget' => array('input', 'text')
);
$catalogs = array();
foreach (Catalog::get_catalogs() as $catid) {
$catalog = Catalog::create_from_id($catid);
$catalog->format();
$catalogs[$catid] = $catalog->f_name;
}
$this->types[] = array(
'name' => 'catalog',
'label' => T_('Catalog'),
'type' => 'boolean_numeric',
'widget' => array('select', $catalogs)
);
$playlists = array();
foreach (Playlist::get_playlists() as $playlistid) {
$playlist = new Playlist($playlistid);
$playlist->format();
$playlists[$playlistid] = $playlist->f_name;
}
$this->types[] = array(
'name' => 'playlist',
'label' => T_('Playlist'),
'type' => 'boolean_numeric',
'widget' => array('select', $playlists)
);
$this->types[] = array(
'name' => 'playlist_name',
'label' => T_('Playlist Name'),
'type' => 'text',
'widget' => array('input', 'text')
);
$playlists = array();
foreach (Search::get_searches() as $playlistid) {
// Slightly different from the above so we don't instigate
// a vicious loop.
$playlists[$playlistid] = Search::get_name_byid($playlistid);
}
$this->types[] = array(
'name' => 'smartplaylist',
'label' => T_('Smart Playlist'),
'type' => 'boolean_subsearch',
'widget' => array('select', $playlists)
);
$licenses = array();
foreach (License::get_licenses() as $license_id) {
$license = new License($license_id);
$licenses[$license_id] = $license->name;
}
if (AmpConfig::get('licensing')) {
$this->types[] = array(
'name' => 'license',
'label' => T_('Music License'),
'type' => 'boolean_numeric',
'widget' => array('select', $licenses)
);
}
break;
case 'album':
$this->types[] = array(
'name' => 'title',
'label' => T_('Title'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'year',
'label' => T_('Year'),
'type' => 'numeric',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'image width',
'label' => T_('Image Width'),
'type' => 'numeric',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'image height',
'label' => T_('Image Height'),
'type' => 'numeric',
'widget' => array('input', 'text')
);
if (AmpConfig::get('ratings')) {
$this->types[] = array(
'name' => 'rating',
'label' => T_('Rating'),
'type' => 'numeric',
'widget' => array(
'select',
array(
'1 Star',
'2 Stars',
'3 Stars',
'4 Stars',
'5 Stars'
)
)
);
}
$catalogs = array();
foreach (Catalog::get_catalogs() as $catid) {
$catalog = Catalog::create_from_id($catid);
$catalog->format();
$catalogs[$catid] = $catalog->f_name;
}
$this->types[] = array(
'name' => 'catalog',
'label' => T_('Catalog'),
'type' => 'boolean_numeric',
'widget' => array('select', $catalogs)
);
$this->types[] = array(
'name' => 'tag',
'label' => T_('Tag'),
'type' => 'text',
'widget' => array('input', 'text')
);
break;
case 'video':
$this->types[] = array(
'name' => 'filename',
'label' => T_('Filename'),
'type' => 'text',
'widget' => array('input', 'text')
);
break;
case 'artist':
$this->types[] = array(
'name' => 'name',
'label' => T_('Name'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'tag',
'label' => T_('Tag'),
'type' => 'text',
'widget' => array('input', 'text')
);
break;
case 'playlist':
$this->types[] = array(
'name' => 'name',
'label' => T_('Name'),
'type' => 'text',
'widget' => array('input', 'text')
);
break;
case 'label':
$this->types[] = array(
'name' => 'name',
'label' => T_('Name'),
'type' => 'text',
'widget' => array('input', 'text')
);
$this->types[] = array(
'name' => 'category',
'label' => T_('Category'),
'type' => 'text',
'widget' => array('input', 'text')
);
break;
} // end switch on searchtype
} // end constructor
/**
* clean_request
*
* Sanitizes raw search data
*/
public static function clean_request($data)
{
$request = array();
foreach ($data as $key => $value) {
$prefix = substr($key, 0, 4);
$value = trim($value);
if ($prefix == 'rule' && strlen($value)) {
$request[$key] = Dba::escape($value);
}
}
// Figure out if they want an AND based search or an OR based search
switch ($data['operator']) {
case 'or':
$request['operator'] = 'OR';
break;
default:
$request['operator'] = 'AND';
break;
}
// Verify the type
switch ($data['type']) {
case 'album':
case 'artist':
case 'video':
case 'song':
case 'playlist':
$request['type'] = $data['type'];
break;
default:
$request['type'] = 'song';
break;
}
return $request;
} // end clean_request
/**
* get_name_byid
*
* Returns the name of the saved search corresponding to the given ID
*/
public static function get_name_byid($id)
{
$sql = "SELECT `name` FROM `search` WHERE `id` = '$id'";
$db_results = Dba::read($sql);
$r = Dba::fetch_assoc($db_results);
return $r['name'];
}
/**
* get_searches
*
* Return the IDs of all saved searches accessible by the current user.
*/
public static function get_searches()
{
$sql = "SELECT `id` from `search` WHERE `type`='public' OR " .
"`user`='" . $GLOBALS['user']->id . "' ORDER BY `name`";
$db_results = Dba::read($sql);
$results = array();
while ($row = Dba::fetch_assoc($db_results)) {
$results[] = $row['id'];
}
return $results;
}
/**
* run
*
* This function actually runs the search and returns an array of the
* results.
*/
public static function run($data)
{
$limit = intval($data['limit']);
$offset = intval($data['offset']);
$data = Search::clean_request($data);
$search = new Search(null, $data['type']);
$search->parse_rules($data);
// Generate BASE SQL
$limit_sql = "";
if ($limit > 0) {
$limit_sql = ' LIMIT ';
if ($offset) $limit_sql .= $offset . ",";
$limit_sql .= $limit;
}
$search_info = $search->to_sql();
$sql = $search_info['base'] . ' ' . $search_info['table_sql'];
if (!empty($search_info['where_sql'])) {
$sql .= ' WHERE ' . $search_info['where_sql'];
}
if (!empty($search_info['group_sql'])) {
$sql .= ' GROUP BY ' . $search_info['group_sql'];
if (!empty($search_info['having_sql'])) {
$sql .= ' HAVING ' . $search_info['having_sql'];
}
}
$sql .= ' ' . $limit_sql;
$sql = trim($sql);
$db_results = Dba::read($sql);
$results = array();
while ($row = Dba::fetch_assoc($db_results)) {
$results[] = $row['id'];
}
return $results;
}
/**
* delete
*
* Does what it says on the tin.
*/
public function delete()
{
$id = Dba::escape($this->id);
$sql = "DELETE FROM `search` WHERE `id` = ?";
Dba::write($sql, array($id));
return true;
}
/**
* format
* Gussy up the data
*/
public function format($details = true)
{
parent::format();
$this->link = AmpConfig::get('web_path') . '/smartplaylist.php?action=show_playlist&playlist_id=' . $this->id;
$this->f_link = '<a href="' . $this->link . '">' . $this->f_name . '</a>';
}
/**
* get_items
*
* Return an array of the items output by our search (part of the
* playlist interface).
*/
public function get_items()
{
$results = array();
$sqltbl = $this->to_sql();
$sql = $sqltbl['base'] . ' ' . $sqltbl['table_sql'];
if (!empty($sqltbl['where_sql'])) {
$sql .= ' WHERE ' . $sqltbl['where_sql'];
}
if (!empty($sqltbl['group_sql'])) {
$sql .= ' GROUP BY ' . $sqltbl['group_sql'];
}
if (!empty($sqltbl['having_sql'])) {
$sql .= ' HAVING ' . $sqltbl['having_sql'];
}
if ($this->random) {
$sql .= " ORDER BY RAND()";
}
if ($this->limit > 0) {
$sql .= " LIMIT " . intval($this->limit);
}
$db_results = Dba::read($sql);
while ($row = Dba::fetch_assoc($db_results)) {
$results[] = array(
'object_id' => $row['id'],
'object_type' => $this->searchtype
);
}
return $results;
}
/**
* get_random_items
*
* Returns a randomly sorted array (with an optional limit) of the items
* output by our search (part of the playlist interface)
*/
public function get_random_items($limit = null)
{
$results = array();
$sqltbl = $this->to_sql();
$sql = $sqltbl['base'] . ' ' . $sqltbl['table_sql'];
if (!empty($sqltbl['where_sql'])) {
$sql .= ' WHERE ' . $sqltbl['where_sql'];
}
if (!empty($sqltbl['group_sql'])) {
$sql .= ' GROUP BY ' . $sqltbl['group_sql'];
}
if (!empty($sqltbl['having_sql'])) {
$sql .= ' HAVING ' . $sqltbl['having_sql'];
}
$sql .= ' ORDER BY RAND()';
$sql .= $limit ? ' LIMIT ' . intval($limit) : '';
$db_results = Dba::read($sql);
while ($row = Dba::fetch_assoc($db_results)) {
$results[] = array(
'object_id' => $row['id'],
'object_type' => $this->searchtype
);
}
return $results;
}
/**
* name_to_basetype
*
* Iterates over our array of types to find out the basetype for
* the passed string.
*/
public function name_to_basetype($name)
{
foreach ($this->types as $type) {
if ($type['name'] == $name) {
return $type['type'];
}
}
return false;
}
/**
* parse_rules
*
* Takes an array of sanitized search data from the form and generates
* our real array from it.
*/
public function parse_rules($data)
{
$this->rules = array();
foreach ($data as $rule => $value) {
if (preg_match('/^rule_(\d+)$/', $rule, $ruleID)) {
$ruleID = $ruleID[1];
foreach (explode('|', $data['rule_' . $ruleID . '_input']) as $input) {
$this->rules[] = array(
$value,
$this->basetypes[$this->name_to_basetype($value)][$data['rule_' . $ruleID . '_operator']]['name'],
$input
);
}
}
}
$this->logic_operator = $data['operator'];
}
/**
* save
*
* Save this search to the database for use as a smart playlist
*/
public function save()
{
// Make sure we have a unique name
if (! $this->name) {
$this->name = $GLOBALS['user']->username . ' - ' . date('Y-m-d H:i:s', time());
}
$sql = "SELECT `id` FROM `search` WHERE `name` = ?";
$db_results = Dba::read($sql, array($this->name));
if (Dba::num_rows($db_results)) {
$this->name .= uniqid('', true);
}
$sql = "INSERT INTO `search` (`name`, `type`, `user`, `rules`, `logic_operator`, `random`, `limit`) VALUES (?, ?, ?, ?, ?, ?, ?)";
Dba::write($sql, array($this->name, $this->type, $GLOBALS['user']->id, serialize($this->rules), $this->logic_operator, $this->random, $this->limit));
$insert_id = Dba::insert_id();
$this->id = $insert_id;
return $insert_id;
}
/**
* to_js
*
* Outputs the javascript necessary to re-show the current set of rules.
*/
public function to_js()
{
$js = "";
foreach ($this->rules as $rule) {
$js .= '<script type="text/javascript">' .
'SearchRow.add("' . $rule[0] . '","' .
$rule[1] . '","' . $rule[2] . '"); </script>';
}
return $js;
}
/**
* to_sql
*
* Call the appropriate real function.
*/
public function to_sql()
{
return call_user_func(array($this, $this->searchtype . "_to_sql"));
}
/**
* update
*
* This function updates the saved version with the current settings.
*/
public function update(array $data = null)
{
if ($data && is_array($data)) {
$this->name = $data['name'];
$this->type = $data['pl_type'];
$this->random = $data['random'];
$this->limit = $data['limit'];
}
if (!$this->id) {
return false;
}
$sql = "UPDATE `search` SET `name` = ?, `type` = ?, `rules` = ?, `logic_operator` = ?, `random` = ?, `limit` = ? WHERE `id` = ?";
Dba::write($sql, array($this->name, $this->type, serialize($this->rules), $this->logic_operator, $this->random, $this->limit, $this->id));
return $this->id;
}
public static function gc()
{
}
/**
* _mangle_data
*
* Private convenience function. Mangles the input according to a set
* of predefined rules so that we don't have to include this logic in
* foo_to_sql.
*/
private function _mangle_data($data, $type, $operator)
{
if ($operator['preg_match']) {
$data = preg_replace(
$operator['preg_match'],
$operator['preg_replace'],
$data
);
}
if ($type == 'numeric') {
return intval($data);
}
if ($type == 'boolean') {
return make_bool($data);
}
return $data;
}
/**
* album_to_sql
*
* Handles the generation of the SQL for album searches.
*/
private function album_to_sql()
{
$sql_logic_operator = $this->logic_operator;
$where = array();
$table = array();
$join = array();
$group = array();
$having = array();
$join['tag'] = array();
foreach ($this->rules as $rule) {
$type = $this->name_to_basetype($rule[0]);
$operator = array();
foreach ($this->basetypes[$type] as $op) {
if ($op['name'] == $rule[1]) {
$operator = $op;
break;
}
}
$input = $this->_mangle_data($rule[2], $type, $operator);
$sql_match_operator = $operator['sql'];
switch ($rule[0]) {
case 'title':
$where[] = "`album`.`name` $sql_match_operator '$input'";
break;
case 'year':
$where[] = "`album`.`year` $sql_match_operator '$input'";
break;
case 'rating':
if ($this->type != "public") {
$where[] = "COALESCE(`rating`.`rating`,0) $sql_match_operator '$input'";
} else {
$group[] = "`album`.`id`";
$having[] = "ROUND(AVG(`rating`.`rating`)) $sql_match_operator '$input'";
}
$join['rating'] = true;
break;
case 'catalog':
$where[] = "`song`.`catalog` $sql_match_operator '$input'";
$join['song'] = true;
break;
case 'tag':
$key = md5($input . $sql_match_operator);
$where[] = "`realtag_$key`.`match` > 0";
$join['tag'][$key] = "$sql_match_operator '$input'";
break;
case 'image height':
$where[] = "`image`.`height` $sql_match_operator '$input'";
$join['image'] = true;
break;
case 'image width':
$where[] = "`image`.`width` $sql_match_operator '$input'";
$join['image'] = true;
break;
default:
// Nae laird!
break;
} // switch on ruletype
} // foreach rule
$join['song'] = $join['song'] || AmpConfig::get('catalog_disable');
$join['catalog'] = AmpConfig::get('catalog_disable');
$where_sql = implode(" $sql_logic_operator ", $where);
foreach ($join['tag'] as $key => $value) {
$table['tag_' . $key] =
"LEFT JOIN (" .
"SELECT `object_id`, COUNT(`name`) AS `match` ".
"FROM `tag` LEFT JOIN `tag_map` " .
"ON `tag`.`id`=`tag_map`.`tag_id` " .
"WHERE `tag_map`.`object_type`='album' " .
"AND `tag`.`name` $value GROUP BY `object_id`" .
") AS realtag_$key " .
"ON `album`.`id`=`realtag_$key`.`object_id`";
}
if ($join['song']) {
$table['song'] = "LEFT JOIN `song` ON `song`.`album`=`album`.`id`";
if ($join['catalog']) {
$table['catalog'] = "LEFT JOIN `catalog` AS `catalog_se` ON `catalog_se`.`id`=`song`.`catalog`";
$where_sql .= " AND `catalog_se`.`enabled` = '1'";
}
}
if ($join['rating']) {
$userid = intval($GLOBALS['user']->id);
$table['rating'] = "LEFT JOIN `rating` ON `rating`.`object_type`='album' ";
if ($this->type != 'public') {
$table['rating'] .= "AND `rating`.`user`='$userid' ";
}
$table['rating'] .= "AND `rating`.`object_id`=`album`.`id`";
}
if ($join['image']) {
$table['song'] = "LEFT JOIN `image` ON `image`.`object_id`=`album`.`id`";
$where_sql .= " AND `image`.`object_type`='album'";
$where_sql .= " AND `image`.`size`='original'";
}
$table_sql = implode(' ', $table);
$group_sql = implode(', ', $group);
$having_sql = implode(" $sql_logic_operator ", $having);
return array(
'base' => 'SELECT DISTINCT(`album`.`id`) FROM `album`',
'join' => $join,
'where' => $where,
'where_sql' => $where_sql,
'table' => $table,
'table_sql' => $table_sql,
'group_sql' => $group_sql,
'having_sql' => $having_sql
);
}
/**
* artist_to_sql
*
* Handles the generation of the SQL for artist searches.
*/
private function artist_to_sql()
{
$sql_logic_operator = $this->logic_operator;
$where = array();
$table = array();
$join = array();
$group = array();
$having = array();
$join['tag'] = array();
foreach ($this->rules as $rule) {
$type = $this->name_to_basetype($rule[0]);
$operator = array();
foreach ($this->basetypes[$type] as $op) {
if ($op['name'] == $rule[1]) {
$operator = $op;
break;
}
}
$input = $this->_mangle_data($rule[2], $type, $operator);
$sql_match_operator = $operator['sql'];
switch ($rule[0]) {
case 'name':
$where[] = "`artist`.`name` $sql_match_operator '$input'";
break;
case 'tag':
$key = md5($input . $sql_match_operator);
$where[] = "`realtag_$key`.`match` > 0";
$join['tag'][$key] = "$sql_match_operator '$input'";
break;
default:
// Nihil
break;
} // switch on ruletype
} // foreach rule
$join['song'] = $join['song'] || AmpConfig::get('catalog_disable');
$join['catalog'] = AmpConfig::get('catalog_disable');
$where_sql = implode(" $sql_logic_operator ", $where);
foreach ($join['tag'] as $key => $value) {
$table['tag_' . $key] =
"LEFT JOIN (" .
"SELECT `object_id`, COUNT(`name`) AS `match` ".
"FROM `tag` LEFT JOIN `tag_map` " .
"ON `tag`.`id`=`tag_map`.`tag_id` " .
"WHERE `tag_map`.`object_type`='artist' " .
"AND `tag`.`name` $value GROUP BY `object_id`" .
") AS realtag_$key " .
"ON `artist`.`id`=`realtag_$key`.`object_id`";
}
if ($join['song']) {
$table['song'] = "LEFT JOIN `song` ON `song`.`artist`=`artist`.`id`";
if ($join['catalog']) {
$table['catalog'] = "LEFT JOIN `catalog` AS `catalog_se` ON `catalog_se`.`id`=`song`.`catalog`";
$where_sql .= " AND `catalog_se`.`enabled` = '1'";
}
}
$table_sql = implode(' ', $table);
$group_sql = implode(', ', $group);
$having_sql = implode(" $sql_logic_operator ", $having);
return array(
'base' => 'SELECT DISTINCT(`artist`.`id`) FROM `artist`',
'join' => $join,
'where' => $where,
'where_sql' => $where_sql,
'table' => $table,
'table_sql' => $table_sql,
'group_sql' => $group_sql,
'having_sql' => $having_sql
);
}
/**
* song_to_sql
* Handles the generation of the SQL for song searches.
*/
private function song_to_sql()
{
$sql_logic_operator = $this->logic_operator;
$where = array();
$table = array();
$join = array();
$group = array();
$having = array();
$join['tag'] = array();
foreach ($this->rules as $rule) {
$type = $this->name_to_basetype($rule[0]);
$operator = array();
foreach ($this->basetypes[$type] as $op) {
if ($op['name'] == $rule[1]) {
$operator = $op;
break;
}
}
$input = $this->_mangle_data($rule[2], $type, $operator);
$sql_match_operator = $operator['sql'];
switch ($rule[0]) {
case 'anywhere':
$where[] = "(`artist`.`name` $sql_match_operator '$input' OR `album`.`name` $sql_match_operator '$input' OR `song_data`.`comment` $sql_match_operator '$input' OR `song_data`.`label` $sql_match_operator '$input' OR `song`.`file` $sql_match_operator '$input' OR `song`.`title` $sql_match_operator '$input')";
$join['album'] = true;
$join['artist'] = true;
$join['song_data'] = true;
break;
case 'tag':
$key = md5($input . $sql_match_operator);
$where[] = "`realtag_$key`.`match` > 0";
$join['tag'][$key] = "$sql_match_operator '$input'";
break;
case 'title':
$where[] = "`song`.`title` $sql_match_operator '$input'";
break;
case 'album':
$where[] = "`album`.`name` $sql_match_operator '$input'";
$join['album'] = true;
break;
case 'artist':
$where[] = "`artist`.`name` $sql_match_operator '$input'";
$join['artist'] = true;
break;
case 'composer':
$where[] = "`song`.`composer` $sql_match_operator '$input'";
break;
case 'time':
$input = $input * 60;
$where[] = "`song`.`time` $sql_match_operator '$input'";
break;
case 'file':
$where[] = "`song`.`file` $sql_match_operator '$input'";
break;
case 'year':
$where[] = "`song`.`year` $sql_match_operator '$input'";
break;
case 'comment':
$where[] = "`song_data`.`comment` $sql_match_operator '$input'";
$join['song_data'] = true;
break;
case 'label':
$where[] = "`song_data`.`label` $sql_match_operator '$input'";
$join['song_data'] = true;
break;
case 'played':
$where[] = " `song`.`played` = '$input'";
break;
case 'bitrate':
$input = $input * 1000;
$where[] = "`song`.`bitrate` $sql_match_operator '$input'";
break;
case 'rating':
if ($this->type != "public") {
$where[] = "COALESCE(`rating`.`rating`,0) $sql_match_operator '$input'";
} else {
$group[] = "`song`.`id`";
$having[] = "ROUND(AVG(`rating`.`rating`)) $sql_match_operator '$input'";
}
$join['rating'] = true;
break;
case 'played_times':
$where[] = "`song`.`id` IN (SELECT `object_count`.`object_id` FROM `object_count` " .
"WHERE `object_count`.`object_type` = 'song' AND `object_count`.`count_type` = 'stream' " .
"GROUP BY `object_count`.`object_id` HAVING COUNT(*) $sql_match_operator '$input')";
break;
case 'catalog':
$where[] = "`song`.`catalog` $sql_match_operator '$input'";
break;
case 'playlist_name':
$join['playlist'] = true;
$join['playlist_data'] = true;
$where[] = "`playlist`.`name` $sql_match_operator '$input'";
break;
case 'playlist':
$join['playlist_data'] = true;
$where[] = "`playlist_data`.`playlist` $sql_match_operator '$input'";
break;
case 'smartplaylist':
$subsearch = new Search($input, 'song');
$subsql = $subsearch->to_sql();
$where[] = "$sql_match_operator (" . $subsql['where_sql'] . ")";
// HACK: array_merge would potentially lose tags, since it
// overwrites. Save our merged tag joins in a temp variable,
// even though that's ugly.
$tagjoin = array_merge($subsql['join']['tag'], $join['tag']);
$join = array_merge($subsql['join'], $join);
$join['tag'] = $tagjoin;
break;
case 'license':
$where[] = "`song`.`license` $sql_match_operator '$input'";
break;
case 'added':
$input = strtotime($input);
$where[] = "`song`.`addition_time` $sql_match_operator $input";
break;
case 'updated':
$input = strtotime($input);
$where[] = "`song`.`update_time` $sql_match_operator $input";
default:
// NOSSINK!
break;
} // switch on type
} // foreach over rules
$join['catalog'] = AmpConfig::get('catalog_disable');
$where_sql = implode(" $sql_logic_operator ", $where);
// now that we know which things we want to JOIN...
if ($join['artist']) {
$table['artist'] = "LEFT JOIN `artist` ON `song`.`artist`=`artist`.`id`";
}
if ($join['album']) {
$table['album'] = "LEFT JOIN `album` ON `song`.`album`=`album`.`id`";
}
if ($join['song_data']) {
$table['song_data'] = "LEFT JOIN `song_data` ON `song`.`id`=`song_data`.`song_id`";
}
foreach ($join['tag'] as $key => $value) {
$table['tag_' . $key] =
"LEFT JOIN (" .
"SELECT `object_id`, COUNT(`name`) AS `match` ".
"FROM `tag` LEFT JOIN `tag_map` " .
"ON `tag`.`id`=`tag_map`.`tag_id` " .
"WHERE `tag_map`.`object_type`='song' " .
"AND `tag`.`name` $value GROUP BY `object_id`" .
") AS realtag_$key " .
"ON `song`.`id`=`realtag_$key`.`object_id`";
}
if ($join['rating']) {
$userid = $GLOBALS['user']->id;
$table['rating'] = "LEFT JOIN `rating` ON `rating`.`object_type`='song' AND ";
if ($this->type != "public") {
$table['rating'] .= "`rating`.`user`='$userid' AND ";
}
$table['rating'] .= "`rating`.`object_id`=`song`.`id`";
}
if ($join['playlist_data']) {
$table['playlist_data'] = "LEFT JOIN `playlist_data` ON `song`.`id`=`playlist_data`.`object_id` AND `playlist_data`.`object_type`='song'";
if ($join['playlist']) {
$table['playlist'] = "LEFT JOIN `playlist` ON `playlist_data`.`playlist`=`playlist`.`id`";
}
}
if ($join['catalog']) {
$table['catalog'] = "LEFT JOIN `catalog` AS `catalog_se` ON `catalog_se`.`id`=`song`.`catalog`";
$where_sql .= " AND `catalog_se`.`enabled` = '1'";
}
$table_sql = implode(' ', $table);
$group_sql = implode(', ', $group);
$having_sql = implode(" $sql_logic_operator ", $having);
return array(
'base' => 'SELECT DISTINCT(`song`.`id`) FROM `song`',
'join' => $join,
'where' => $where,
'where_sql' => $where_sql,
'table' => $table,
'table_sql' => $table_sql,
'group_sql' => $group_sql,
'having_sql' => $having_sql
);
}
/**
* video_to_sql
*
* Handles the generation of the SQL for video searches.
*/
private function video_to_sql()
{
$sql_logic_operator = $this->logic_operator;
$where = array();
$table = array();
$join = array();
$group = array();
$having = array();
foreach ($this->rules as $rule) {
$type = $this->name_to_basetype($rule[0]);
$operator = array();
foreach ($this->basetypes[$type] as $op) {
if ($op['name'] == $rule[1]) {
$operator = $op;
break;
}
}
$input = $this->_mangle_data($rule[2], $type, $operator);
$sql_match_operator = $operator['sql'];
switch ($rule[0]) {
case 'filename':
$where[] = "`video`.`file` $sql_match_operator '$input'";
break;
default:
// WE WILLNA BE FOOLED AGAIN!
} // switch on ruletype
} // foreach rule
$join['catalog'] = AmpConfig::get('catalog_disable');
$where_sql = implode(" $sql_logic_operator ", $where);
if ($join['catalog']) {
$table['catalog'] = "LEFT JOIN `catalog` AS `catalog_se` ON `catalog_se`.`id`=`video`.`catalog`";
$where_sql .= " AND `catalog_se`.`enabled` = '1'";
}
$table_sql = implode(' ', $table);
$group_sql = implode(', ', $group);
$having_sql = implode(" $sql_logic_operator ", $having);
return array(
'base' => 'SELECT DISTINCT(`video`.`id`) FROM `video`',
'join' => $join,
'where' => $where,
'where_sql' => $where_sql,
'table' => $table,
'table_sql' => $table_sql,
'group_sql' => $group_sql,
'having_sql' => $having_sql
);
}
/**
* playlist_to_sql
*
* Handles the generation of the SQL for playlist searches.
*/
private function playlist_to_sql()
{
$sql_logic_operator = $this->logic_operator;
$where = array();
$table = array();
$join = array();
$group = array();
$having = array();
foreach ($this->rules as $rule) {
$type = $this->name_to_basetype($rule[0]);
$operator = array();
foreach ($this->basetypes[$type] as $op) {
if ($op['name'] == $rule[1]) {
$operator = $op;
break;
}
}
$input = $this->_mangle_data($rule[2], $type, $operator);
$sql_match_operator = $operator['sql'];
$where[] = "`playlist`.`type` = 'public'";
switch ($rule[0]) {
case 'name':
$where[] = "`playlist`.`name` $sql_match_operator '$input'";
break;
default:
// Nihil
break;
} // switch on ruletype
} // foreach rule
$join['playlist_data'] = true;
$join['song'] = $join['song'] || AmpConfig::get('catalog_disable');
$join['catalog'] = AmpConfig::get('catalog_disable');
$where_sql = implode(" $sql_logic_operator ", $where);
if ($join['playlist_data']) {
$table['playlist_data'] = "LEFT JOIN `playlist_data` ON `playlist_data`.`playlist` = `playlist`.`id`";
}
if ($join['song']) {
$table['song'] = "LEFT JOIN `song` ON `song`.`id`=`playlist_data`.`object_id`";
$where_sql .= " AND `playlist_data`.`object_type` = 'song'";
if ($join['catalog']) {
$table['catalog'] = "LEFT JOIN `catalog` AS `catalog_se` ON `catalog_se`.`id`=`song`.`catalog`";
$where_sql .= " AND `catalog_se`.`enabled` = '1'";
}
}
$table_sql = implode(' ', $table);
$group_sql = implode(', ', $group);
$having_sql = implode(" $sql_logic_operator ", $having);
return array(
'base' => 'SELECT DISTINCT(`playlist`.`id`) FROM `playlist`',
'join' => $join,
'where' => $where,
'where_sql' => $where_sql,
'table' => $table,
'table_sql' => $table_sql,
'group_sql' => $group_sql,
'having_sql' => $having_sql
);
}
/**
* label_to_sql
*
* Handles the generation of the SQL for label searches.
*/
private function label_to_sql()
{
$sql_logic_operator = $this->logic_operator;
$where = array();
$table = array();
foreach ($this->rules as $rule) {
$type = $this->name_to_basetype($rule[0]);
$operator = array();
foreach ($this->basetypes[$type] as $op) {
if ($op['name'] == $rule[1]) {
$operator = $op;
break;
}
}
$input = $this->_mangle_data($rule[2], $type, $operator);
$sql_match_operator = $operator['sql'];
switch ($rule[0]) {
case 'name':
$where[] = "`label`.`name` $sql_match_operator '$input'";
break;
case 'category':
$where[] = "`label`.`category` $sql_match_operator '$input'";
break;
default:
// Nihil
break;
} // switch on ruletype
} // foreach rule
$where_sql = implode(" $sql_logic_operator ", $where);
return array(
'base' => 'SELECT DISTINCT(`label`.`id`) FROM `label`',
'join' => $join,
'where' => $where,
'where_sql' => $where_sql,
'table' => $table,
'table_sql' => '',
'group_sql' => '',
'having_sql' => ''
);
}
}