Ampache PHP Cross Reference Groupware Applications

Source: /lib/class/catalog.class.php - 1434 lines - 48307 bytes - Summary - Text - Print

Description: LICENSE: GNU General Public License, version 2 (GPLv2) Copyright 2001 - 2014 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.

   1  <?php
   2  /* vim:set softtabstop=4 shiftwidth=4 expandtab: */
   3  /**
   4   *
   5   * LICENSE: GNU General Public License, version 2 (GPLv2)
   6   * Copyright 2001 - 2014 Ampache.org
   7   *
   8   * This program is free software; you can redistribute it and/or
   9   * modify it under the terms of the GNU General Public License v2
  10   * as published by the Free Software Foundation.
  11   *
  12   * This program is distributed in the hope that it will be useful,
  13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15   * GNU General Public License for more details.
  16   *
  17   * You should have received a copy of the GNU General Public License
  18   * along with this program; if not, write to the Free Software
  19   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  20   *
  21   */
  22  
  23  /**
  24   * Catalog Class
  25   *
  26   * This class handles all actual work in regards to the catalog,
  27   * it contains functions for creating/listing/updated the catalogs.
  28   *
  29   */
  30  abstract class Catalog extends database_object
  31  {
  32      public $id;
  33      public $name;
  34      public $last_update;
  35      public $last_add;
  36      public $last_clean;
  37      public $key;
  38      public $rename_pattern;
  39      public $sort_pattern;
  40      public $catalog_type;
  41  
  42      public $f_name;
  43      public $f_name_link;
  44      public $f_update;
  45      public $f_add;
  46      public $f_clean;
  47  
  48      /* This is a private var that's used during catalog builds */
  49      protected $_playlists = array();
  50  
  51      // Cache all files in catalog for quick lookup during add
  52      protected $_filecache = array();
  53  
  54      // Used in functions
  55      protected static $albums    = array();
  56      protected static $artists    = array();
  57      protected static $tags    = array();
  58  
  59      abstract public function get_type();
  60      abstract public function get_description();
  61      abstract public function get_version();
  62      abstract public function get_create_help();
  63      abstract public function is_installed();
  64      abstract public function install();
  65      abstract public function add_to_catalog($options = null);
  66      abstract public function verify_catalog_proc();
  67      abstract public function clean_catalog_proc();
  68      abstract public function catalog_fields();
  69      abstract public function get_rel_path($file_path);
  70      abstract public function prepare_media($media);
  71  
  72          /**
  73       * uninstall
  74       * This removes the remote catalog
  75       */
  76      public function uninstall()
  77      {
  78          $sql = "DELETE FROM `catalog` WHERE `catalog_type` = ?";
  79          Dba::query($sql, array($this->get_type()));
  80  
  81          $sql = "DROP TABLE `catalog_" . $this->get_type() ."`";
  82          Dba::query($sql);
  83  
  84          return true;
  85  
  86      } // uninstall
  87  
  88      public static function create_from_id($id)
  89      {
  90          $sql = 'SELECT `catalog_type` FROM `catalog` WHERE `id` = ?';
  91          $db_results = Dba::read($sql, array($id));
  92          if ($results = Dba::fetch_assoc($db_results)) {
  93              return self::create_catalog_type($results['catalog_type'], $id);
  94          }
  95  
  96          return null;
  97      }
  98  
  99      /**
 100       * create_catalog_type
 101       * This function attempts to create a catalog type
 102       * all Catalog modules should be located in /modules/catalog/<name>.class.php
 103       */
 104      public static function create_catalog_type($type, $id=0)
 105      {
 106          if (!$type) { return false; }
 107  
 108          $filename = AmpConfig::get('prefix') . '/modules/catalog/' . $type . '.catalog.php';
 109          $include = require_once $filename;
 110  
 111          if (!$include) {
 112              /* Throw Error Here */
 113              debug_event('catalog', 'Unable to load ' . $type . ' catalog type', '2');
 114              return false;
 115          } // include
 116          else {
 117              $class_name = "Catalog_" . $type;
 118              if ($id > 0) {
 119                  $catalog = new $class_name($id);
 120              } else {
 121                  $catalog = new $class_name();
 122              }
 123              if (!($catalog instanceof Catalog)) {
 124                  debug_event('catalog', $type . ' not an instance of Catalog abstract, unable to load', '1');
 125                  return false;
 126              }
 127              return $catalog;
 128          }
 129  
 130      } // create_catalog_type
 131  
 132      public static function show_catalog_types($divback = 'catalog_type_fields')
 133      {
 134          echo "<script language=\"javascript\" type=\"text/javascript\">" .
 135              "var type_fields = new Array();" .
 136              "type_fields['none'] = '';";
 137          $seltypes = '<option value="none">[Select]</option>';
 138          $types = self::get_catalog_types();
 139          foreach ($types as $type) {
 140              $catalog = self::create_catalog_type($type);
 141              if ($catalog->is_installed()) {
 142                  $seltypes .= '<option value="' . $type . '">' . $type . '</option>';
 143                  echo "type_fields['" . $type . "'] = \"";
 144                  $fields = $catalog->catalog_fields();
 145                  $help = $catalog->get_create_help();
 146                  if (!empty($help)) {
 147                      echo "<tr><td></td><td>" . $help . "</td></tr>";
 148                  }
 149                  foreach ($fields as $key=>$field) {
 150                      echo "<tr><td style='width: 25%;'>" . $field['description'] . ":</td><td>";
 151  
 152                      switch ($field['type']) {
 153                          case 'checkbox':
 154                              echo "<input type='checkbox' name='" . $key . "' value='1' " . (($field['value']) ? 'checked' : '') . "/>";
 155                              break;
 156                          case 'password':
 157                              echo "<input type='password' name='" . $key . "' value='" . $field['value'] . "' />";
 158                              break;
 159                          default:
 160                              echo "<input type='text' name='" . $key . "' value='" . $field['value'] . "' />";
 161                              break;
 162                      }
 163                      echo "</td></tr>";
 164                  }
 165                  echo "\";";
 166              }
 167          }
 168  
 169          echo "function catalogTypeChanged() {" .
 170              "var sel = document.getElementById('catalog_type');" .
 171              "var seltype = sel.options[sel.selectedIndex].value;" .
 172              "var ftbl = document.getElementById('" . $divback . "');" .
 173              "ftbl.innerHTML = '<table class=\"tabledata\" cellpadding=\"0\" cellspacing=\"0\">' + type_fields[seltype] + '</table>';" .
 174              "} </script>" .
 175              "<select name=\"type\" id=\"catalog_type\" onChange=\"catalogTypeChanged();\">" . $seltypes . "</select>";
 176      }
 177  
 178      /**
 179       * get_catalog_types
 180       * This returns the catalog types that are available
 181       */
 182      public static function get_catalog_types()
 183      {
 184          /* First open the dir */
 185          $handle = opendir(AmpConfig::get('prefix') . '/modules/catalog');
 186  
 187          if (!is_resource($handle)) {
 188              debug_event('catalog', 'Error: Unable to read catalog types directory', '1');
 189              return array();
 190          }
 191  
 192          $results = array();
 193  
 194          while ($file = readdir($handle)) {
 195  
 196              if (substr($file, -11, 11) != 'catalog.php') { continue; }
 197  
 198              /* Make sure it isn't a dir */
 199              if (!is_dir($file)) {
 200                  /* Get the basename and then everything before catalog */
 201                  $filename = basename($file, '.catalog.php');
 202                  $results[] = $filename;
 203              }
 204          } // end while
 205  
 206          return $results;
 207  
 208      } // get_catalog_types
 209  
 210      public static function is_audio_file($file)
 211      {
 212          $pattern = "/\.(" . AmpConfig::get('catalog_file_pattern') . ")$/i";
 213          $match = preg_match($pattern, $file);
 214  
 215          return $match;
 216      }
 217  
 218      public static function is_video_file($file)
 219      {
 220          $video_pattern = "/\.(" . AmpConfig::get('catalog_video_pattern') . ")$/i";
 221          return preg_match($video_pattern, $file);
 222      }
 223  
 224      public static function is_playlist_file($file)
 225      {
 226          $playlist_pattern = "/\.(" . AmpConfig::get('catalog_playlist_pattern') . ")$/i";
 227          return preg_match($playlist_pattern, $file);
 228      }
 229  
 230      public function get_info($id, $table = 'catalog')
 231      {
 232          $info = parent::get_info($id, $table);
 233  
 234          $table = 'catalog_' . $this->get_type();
 235          $sql = "SELECT `id` FROM $table WHERE `catalog_id` = ?";
 236          $db_results = Dba::read($sql, array($id));
 237  
 238          if ($results = Dba::fetch_assoc($db_results)) {
 239  
 240              $info_type = parent::get_info($results['id'], $table);
 241              foreach ($info_type as $key => $value) {
 242                  if (!$info[$key]) {
 243                      $info[$key] = $value;
 244                  }
 245              }
 246          }
 247  
 248          return $info;
 249      }
 250  
 251      public static function get_enable_filter($type, $id)
 252      {
 253          $sql = "";
 254          if ($type == "song" || $type == "album" || $type == "artist") {
 255              if ($type == "song") $type = "id";
 256              $sql = "(SELECT COUNT(`song_dis`.`id`) FROM `song` AS `song_dis` LEFT JOIN `catalog` AS `catalog_dis` ON `catalog_dis`.`id` = `song_dis`.`catalog` " .
 257                  "WHERE `song_dis`.`" . $type . "`=" . $id . " AND `catalog_dis`.`enabled` = '1' GROUP BY `song_dis`.`" . $type . "`) > 0";
 258          } else if ($type == "video") {
 259              $sql = "(SELECT COUNT(`video_dis`.`id`) FROM `video` AS `video_dis` LEFT JOIN `catalog` AS `catalog_dis` ON `catalog_dis`.`id` = `video_dis`.`catalog` " .
 260                  "WHERE `video_dis`.`id`=" . $id . " AND `catalog_dis`.`enabled` = '1' GROUP BY `video_dis`.`id`) > 0";
 261          }
 262  
 263          return $sql;
 264      }
 265  
 266      /**
 267       * _create_filecache
 268       *
 269       * This populates an array which is used to speed up the add process.
 270       */
 271      protected function _create_filecache()
 272      {
 273          if (count($this->_filecache) == 0) {
 274              // Get _EVERYTHING_
 275              $sql = 'SELECT `id`, `file` FROM `song` WHERE `catalog` = ?';
 276              $db_results = Dba::read($sql, array($this->id));
 277  
 278              // Populate the filecache
 279              while ($results = Dba::fetch_assoc($db_results)) {
 280                  $this->_filecache[strtolower($results['file'])] = $results['id'];
 281              }
 282  
 283              $sql = 'SELECT `id`,`file` FROM `video` WHERE `catalog` = ?';
 284              $db_results = Dba::read($sql, array($this->id));
 285  
 286              while ($results = Dba::fetch_assoc($db_results)) {
 287                  $this->_filecache[strtolower($results['file'])] = 'v_' . $results['id'];
 288              }
 289          }
 290  
 291          return true;
 292      }
 293  
 294      /**
 295       * update_enabled
 296       * sets the enabled flag
 297       */
 298      public static function update_enabled($new_enabled, $catalog_id)
 299      {
 300          self::_update_item('enabled', $new_enabled, $catalog_id, '75');
 301  
 302      } // update_enabled
 303  
 304      /**
 305       * _update_item
 306       * This is a private function that should only be called from within the catalog class.
 307       * It takes a field, value, catalog id and level. first and foremost it checks the level
 308       * against $GLOBALS['user'] to make sure they are allowed to update this record
 309       * it then updates it and sets $this->{$field} to the new value
 310       */
 311      private static function _update_item($field, $value, $catalog_id, $level)
 312      {
 313          /* Check them Rights! */
 314          if (!Access::check('interface', $level)) { return false; }
 315  
 316          /* Can't update to blank */
 317          if (!strlen(trim($value))) { return false; }
 318  
 319          $value = Dba::escape($value);
 320  
 321          $sql = "UPDATE `catalog` SET `$field`='$value' WHERE `id`='$catalog_id'";
 322          Dba::write($sql);
 323  
 324          return true;
 325  
 326      } // _update_item
 327  
 328      /**
 329       * format
 330       *
 331       * This makes the object human-readable.
 332       */
 333      public function format()
 334      {
 335          $this->f_name = $this->name;
 336          $this->f_name_link = '<a href="' . AmpConfig::get('web_path') .
 337              '/admin/catalog.php?action=show_customize_catalog&catalog_id=' .
 338              $this->id . '" title="' . scrub_out($this->name) . '">' .
 339              scrub_out($this->f_name) . '</a>';
 340          $this->f_update = $this->last_update
 341              ? date('d/m/Y h:i', $this->last_update)
 342              : T_('Never');
 343          $this->f_add = $this->last_add
 344              ? date('d/m/Y h:i', $this->last_add)
 345              : T_('Never');
 346          $this->f_clean = $this->last_clean
 347              ? date('d/m/Y h:i', $this->last_clean)
 348              : T_('Never');
 349      }
 350  
 351      /**
 352       * get_catalogs
 353       *
 354       * Pull all the current catalogs and return an array of ids
 355       * of what you find
 356       */
 357      public static function get_catalogs()
 358      {
 359          $sql = "SELECT `id` FROM `catalog` ORDER BY `name`";
 360          $db_results = Dba::read($sql);
 361  
 362          $results = array();
 363  
 364          while ($row = Dba::fetch_assoc($db_results)) {
 365              $results[] = $row['id'];
 366          }
 367  
 368          return $results;
 369      }
 370  
 371      /**
 372       * get_stats
 373       *
 374       * This returns an hash with the #'s for the different
 375       * objects that are associated with this catalog. This is used
 376       * to build the stats box, it also calculates time.
 377       */
 378      public static function get_stats($catalog_id = null)
 379      {
 380          $results = self::count_songs($catalog_id);
 381          $results = array_merge(User::count(), $results);
 382          $results['tags'] = self::count_tags();
 383          $results['videos'] = self::count_videos($catalog_id);
 384  
 385          $hours = floor($results['time'] / 3600);
 386  
 387          $results['formatted_size'] = UI::format_bytes($results['size']);
 388  
 389          $days = floor($hours / 24);
 390          $hours = $hours % 24;
 391  
 392          $time_text = "$days ";
 393          $time_text .= ngettext('day','days',$days);
 394          $time_text .= ", $hours ";
 395          $time_text .= ngettext('hour','hours',$hours);
 396  
 397          $results['time_text'] = $time_text;
 398  
 399          return $results;
 400      }
 401  
 402      /**
 403       * create
 404       *
 405       * This creates a new catalog entry and associate it to current instance
 406       */
 407      public static function create($data)
 408      {
 409          $name = $data['name'];
 410          $type = $data['type'];
 411          $rename_pattern = $data['rename_pattern'];
 412          $sort_pattern = $data['sort_pattern'];
 413  
 414          $insert_id = 0;
 415          $filename = AmpConfig::get('prefix') . '/modules/catalog/' . $type . '.catalog.php';
 416          $include = require_once $filename;
 417  
 418          if ($include) {
 419              $sql = 'INSERT INTO `catalog` (`name`, `catalog_type`, ' .
 420                  '`rename_pattern`, `sort_pattern`) VALUES (?, ?, ?, ?)';
 421              Dba::write($sql, array(
 422                  $name,
 423                  $type,
 424                  $rename_pattern,
 425                  $sort_pattern
 426              ));
 427  
 428              $insert_id = Dba::insert_id();
 429  
 430              if (!$insert_id) {
 431                  Error::add('general', T_('Catalog Insert Failed check debug logs'));
 432                  debug_event('catalog', 'Insert failed: ' . json_encode($data), 2);
 433                  return false;
 434              }
 435  
 436              $classname = 'Catalog_' . $type;
 437              if (!$classname::create_type($insert_id, $data)) {
 438                  $sql = 'DELETE FROM `catalog` WHERE `id` = ?';
 439                  Dba::write($sql, array($insert_id));
 440                  $insert_id = 0;
 441              }
 442          }
 443  
 444          return $insert_id;
 445      }
 446  
 447      /**
 448       * count_videos
 449       *
 450       * This returns the current number of video files in the database.
 451       */
 452      public static function count_videos($id = null)
 453      {
 454          $sql = 'SELECT COUNT(`id`) FROM `video` ';
 455          if ($id) {
 456              $sql .= 'WHERE `catalog` = ?';
 457          }
 458          $db_results = Dba::read($sql, $id ? array($id) : null);
 459  
 460          $row = Dba::fetch_assoc($db_results);
 461          return $row[0];
 462      }
 463  
 464      /**
 465       * count_tags
 466       *
 467       * This returns the current number of unique tags in the database.
 468       */
 469      public static function count_tags()
 470      {
 471          // FIXME: Ignores catalog_id
 472          $sql = "SELECT COUNT(`id`) FROM `tag`";
 473          $db_results = Dba::read($sql);
 474  
 475          $row = Dba::fetch_row($db_results);
 476          return $row[0];
 477      }
 478  
 479      /**
 480       * count_songs
 481       *
 482       * This returns the current number of songs, albums, and artists
 483       * in this catalog.
 484       */
 485      public static function count_songs($id = null)
 486      {
 487          $where_sql = $id ? 'WHERE `catalog` = ?' : '';
 488          $params = $id ? array($id) : null;
 489  
 490          $sql = 'SELECT COUNT(`id`), SUM(`time`), SUM(`size`) FROM `song` ' .
 491              $where_sql;
 492  
 493          $db_results = Dba::read($sql, $params);
 494          $data = Dba::fetch_row($db_results);
 495          $songs    = $data[0];
 496          $time    = $data[1];
 497          $size    = $data[2];
 498  
 499          $sql = 'SELECT COUNT(DISTINCT(`album`)) FROM `song` ' . $where_sql;
 500          $db_results = Dba::read($sql, $params);
 501          $data = Dba::fetch_row($db_results);
 502          $albums = $data[0];
 503  
 504          $sql = 'SELECT COUNT(DISTINCT(`artist`)) FROM `song` ' . $where_sql;
 505          $db_results = Dba::read($sql, $params);
 506          $data = Dba::fetch_row($db_results);
 507          $artists = $data[0];
 508  
 509          $results = array();
 510          $results['songs'] = $songs;
 511          $results['albums'] = $albums;
 512          $results['artists'] = $artists;
 513          $results['size'] = $size;
 514          $results['time'] = $time;
 515  
 516          return $results;
 517      }
 518  
 519      /**
 520       * get_album_ids
 521       *
 522       * This returns an array of ids of albums that have songs in this
 523       * catalog
 524       */
 525      public function get_album_ids()
 526      {
 527          $results = array();
 528  
 529          $sql = 'SELECT DISTINCT(`song`.`album`) FROM `song` WHERE `song`.`catalog` = ?';
 530          $db_results = Dba::read($sql, array($this->id));
 531  
 532          while ($r = Dba::fetch_assoc($db_results)) {
 533              $results[] = $r['album'];
 534          }
 535  
 536          return $results;
 537      }
 538  
 539      /**
 540      * get_artist
 541      *
 542      * This returns an array of ids of artists that have songs in the catalogs parameter
 543      */
 544      public static function get_artists($catalogs = null)
 545      {
 546          $sql_where = "";
 547          if (is_array($catalogs) && count($catalogs)) {
 548              $catlist = '(' . implode(',', $catalogs) . ')';
 549              $sql_where = "WHERE `song`.`catalog` IN $catlist";
 550          }
 551  
 552          $sql = "SELECT `artist`.id, `artist`.`name`, `artist`.`summary` FROM `song` LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` $sql_where GROUP BY `song`.artist ORDER BY `artist`.`name`";
 553  
 554          $results = array();
 555          $db_results = Dba::read($sql);
 556  
 557          while ($r = Dba::fetch_assoc($db_results)) {
 558              $results[] = Artist::construct_from_array($r);
 559          }
 560  
 561          return $results;
 562      }
 563  
 564      /**
 565      * get_albums
 566      *
 567      * Returns an array of ids of albums that have songs in the catalogs parameter
 568      */
 569      public static function get_albums($size = 0, $offset = 0, $catalogs = null)
 570      {
 571          $sql_where = "";
 572          if (is_array($catalogs) && count($catalogs)) {
 573              $catlist = '(' . implode(',', $catalogs) . ')';
 574              $sql_where = "WHERE `song`.`catalog` IN $catlist";
 575          }
 576  
 577          $sql_limit = "";
 578          if ($offset > 0 && $size > 0) {
 579              $sql_limit = "LIMIT $offset, $size";
 580          } else if ($size > 0) {
 581              $sql_limit = "LIMIT $size";
 582          } else if ($offset > 0) {
 583              // MySQL doesn't have notation for last row, so we have to use the largest possible BIGINT value
 584              // https://dev.mysql.com/doc/refman/5.0/en/select.html
 585              $sql_limit = "LIMIT $offset, 18446744073709551615";
 586          }
 587  
 588          $sql = "SELECT `album`.`id` FROM `song` LEFT JOIN `album` ON `album`.`id` = `song`.`album` $sql_where GROUP BY `song`.`album` ORDER BY `album`.`name` $sql_limit";
 589  
 590          $db_results = Dba::read($sql);
 591          $results = array();
 592          while ($r = Dba::fetch_assoc($db_results)) {
 593              $results[] = $r['id'];
 594          }
 595  
 596          return $results;
 597      }
 598  
 599      /**
 600      * get_albums_by_artist
 601      *
 602      * Returns an array of ids of albums that have songs in the catalogs parameter, grouped by artist
 603      */
 604      public static function get_albums_by_artist($size = 0, $offset = 0, $catalogs = null)
 605      {
 606          $sql_where = "";
 607          if (is_array($catalogs) && count($catalogs)) {
 608              $catlist = '(' . implode(',', $catalogs) . ')';
 609              $sql_where = "WHERE `song`.`catalog` IN $catlist";
 610          }
 611  
 612          $sql_limit = "";
 613          if ($offset > 0 && $size > 0) {
 614              $sql_limit = "LIMIT $offset, $size";
 615          } else if ($size > 0) {
 616              $sql_limit = "LIMIT $size";
 617          } else if ($offset > 0) {
 618              // MySQL doesn't have notation for last row, so we have to use the largest possible BIGINT value
 619              // https://dev.mysql.com/doc/refman/5.0/en/select.html
 620              $sql_limit = "LIMIT $offset, 18446744073709551615";
 621          }
 622  
 623          $sql = "SELECT `album`.`id` FROM `song` LEFT JOIN `album` ON `album`.`id` = `song`.`album` " .
 624              "LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` $sql_where GROUP BY `song`.`album` ORDER BY `artist`.`name`, `artist`.`id`, `album`.`name` $sql_limit";
 625  
 626          $db_results = Dba::read($sql);
 627          $results = array();
 628          while ($r = Dba::fetch_assoc($db_results)) {
 629              $results[] = $r['id'];
 630          }
 631  
 632          return $results;
 633      }
 634  
 635      /**
 636       * gather_art
 637       *
 638       * This runs through all of the albums and finds art for them
 639       * This runs through all of the needs art albums and trys
 640       * to find the art for them from the mp3s
 641       */
 642      public function gather_art()
 643      {
 644          // Make sure they've actually got methods
 645          $art_order = AmpConfig::get('art_order');
 646          if (!count($art_order)) {
 647              debug_event('gather_art', 'art_order not set, Catalog::gather_art aborting', 3);
 648              return true;
 649          }
 650  
 651          // Prevent the script from timing out
 652          set_time_limit(0);
 653  
 654          $search_count = 0;
 655          $albums = $this->get_album_ids();
 656  
 657          // Run through them and get the art!
 658          foreach ($albums as $album_id) {
 659              $art = new Art($album_id, 'album');
 660              $album = new Album($album_id);
 661              // We're going to need the name here
 662              $album->format();
 663  
 664              debug_event('gather_art', 'Gathering art for ' . $album->name, 5);
 665  
 666              $options = array(
 667                  'album_name' => $album->full_name,
 668                  'artist'     => $album->artist_name,
 669                  'keyword'    => $album->artist_name . ' ' . $album->full_name
 670              );
 671  
 672              $results = $art->gather($options, 1);
 673  
 674              if (count($results)) {
 675                  // Pull the string representation from the source
 676                  $image = Art::get_from_source($results[0], 'album');
 677                  if (strlen($image) > '5') {
 678                      $art->insert($image, $results[0]['mime']);
 679                      // If they've enabled resizing of images generate a thumbnail
 680                      if (AmpConfig::get('resize_images')) {
 681                          $thumb = $art->generate_thumb($image, array(
 682                                  'width' => 275,
 683                                  'height' => 275),
 684                              $results[0]['mime']);
 685                          if (is_array($thumb)) {
 686                              $art->save_thumb($thumb['thumb'], $thumb['thumb_mime'], '275x275');
 687                          }
 688                      }
 689  
 690                  } else {
 691                      debug_event('gather_art', 'Image less than 5 chars, not inserting', 3);
 692                  }
 693              }
 694  
 695              // Stupid little cutesie thing
 696              $search_count++;
 697              if (UI::check_ticker()) {
 698                  UI::update_text('count_art_' . $this->id, $search_count);
 699                  UI::update_text('read_art_' . $this->id, scrub_out($album->name));
 700              }
 701  
 702              unset($found);
 703          } // foreach albums
 704  
 705          // One last time for good measure
 706          UI::update_text('count_art_' . $this->id, $search_count);
 707      }
 708  
 709      /**
 710       * get_songs
 711       *
 712       * Returns an array of song objects.
 713       */
 714      public function get_songs()
 715      {
 716          $results = array();
 717  
 718          $sql = "SELECT `id` FROM `song` WHERE `catalog` = ? AND `enabled`='1'";
 719          $db_results = Dba::read($sql, array($this->id));
 720  
 721          while ($row = Dba::fetch_assoc($db_results)) {
 722              $results[] = new Song($row['id']);
 723          }
 724  
 725          return $results;
 726      }
 727  
 728      /**
 729       * dump_album_art
 730       *
 731       * This runs through all of the albums and tries to dump the
 732       * art for them into the 'folder.jpg' file in the appropriate dir.
 733       */
 734      public function dump_album_art($methods = array())
 735      {
 736          // Get all of the albums in this catalog
 737          $albums = $this->get_album_ids();
 738  
 739          echo "Starting Dump Album Art...\n";
 740          $i = 0;
 741  
 742          // Run through them and get the art!
 743          foreach ($albums as $album_id) {
 744  
 745              $album = new Album($album_id);
 746              $art = new Art($album_id, 'album');
 747  
 748              if (!$art->get_db()) {
 749                  continue;
 750              }
 751  
 752              // Get the first song in the album
 753              $songs = $album->get_songs(1);
 754              $song = new Song($songs[0]);
 755              $dir = dirname($song->file);
 756  
 757              $extension = Art::extension($art->raw_mime);
 758  
 759              // Try the preferred filename, if that fails use folder.???
 760              $preferred_filename = AmpConfig::get('album_art_preferred_filename');
 761              if (!$preferred_filename ||
 762                  strpos($preferred_filename, '%') !== false) {
 763                  $preferred_filename = "folder.$extension";
 764              }
 765  
 766              $file = "$dir/$preferred_filename";
 767              if ($file_handle = fopen($file,"w")) {
 768                  if (fwrite($file_handle, $art->raw)) {
 769  
 770                      // Also check and see if we should write
 771                      // out some metadata
 772                      if ($methods['metadata']) {
 773                          switch ($methods['metadata']) {
 774                              case 'windows':
 775                                  $meta_file = $dir . '/desktop.ini';
 776                                  $string = "[.ShellClassInfo]\nIconFile=$file\nIconIndex=0\nInfoTip=$album->full_name";
 777                                  break;
 778                              case 'linux':
 779                              default:
 780                                  $meta_file = $dir . '/.directory';
 781                                  $string = "Name=$album->full_name\nIcon=$file";
 782                                  break;
 783                          }
 784  
 785                          $meta_handle = fopen($meta_file,"w");
 786                          fwrite($meta_handle,$string);
 787                          fclose($meta_handle);
 788  
 789                      } // end metadata
 790                      $i++;
 791                      if (!($i%100)) {
 792                          echo "Written: $i. . .\n";
 793                          debug_event('art_write',"$album->name Art written to $file",'5');
 794                      }
 795                  } else {
 796                      debug_event('art_write',"Unable to open $file for writing", 5);
 797                      echo "Error: unable to open file for writing [$file]\n";
 798                  }
 799              }
 800              fclose($file_handle);
 801          }
 802  
 803          echo "Album Art Dump Complete\n";
 804      }
 805  
 806      /**
 807       * update_last_update
 808       * updates the last_update of the catalog
 809       */
 810      protected function update_last_update()
 811      {
 812          $date = time();
 813          $sql = "UPDATE `catalog` SET `last_update` = ? WHERE `id` = ?";
 814          Dba::write($sql, array($date, $this->id));
 815  
 816      } // update_last_update
 817  
 818      /**
 819       * update_last_add
 820       * updates the last_add of the catalog
 821       */
 822      public function update_last_add()
 823      {
 824          $date = time();
 825          $sql = "UPDATE `catalog` SET `last_add` = ? WHERE `id` = ?";
 826          Dba::write($sql, array($date, $this->id));
 827  
 828      } // update_last_add
 829  
 830      /**
 831       * update_last_clean
 832       * This updates the last clean information
 833       */
 834      public function update_last_clean()
 835      {
 836          $date = time();
 837          $sql = "UPDATE `catalog` SET `last_clean` = ? WHERE `id` = ?";
 838          Dba::write($sql, array($date, $this->id));
 839  
 840      } // update_last_clean
 841  
 842      /**
 843       * update_settings
 844       * This function updates the basic setting of the catalog
 845       */
 846      public static function update_settings($data)
 847      {
 848          $sql = "UPDATE `catalog` SET `name` = ?, `rename_pattern` = ?, `sort_pattern` = ? WHERE `id` = ?";
 849          $params = array($data['name'], $data['rename_pattern'], $data['sort_pattern'], $data['catalog_id']);
 850          Dba::write($sql, $params);
 851  
 852          return true;
 853  
 854      } // update_settings
 855  
 856      /**
 857       * update_single_item
 858       * updates a single album,artist,song from the tag data
 859       * this can be done by 75+
 860       */
 861      public static function update_single_item($type,$id)
 862      {
 863          // Because single items are large numbers of things too
 864          set_time_limit(0);
 865  
 866          $songs = array();
 867  
 868          switch ($type) {
 869              case 'album':
 870                  $album = new Album($id);
 871                  $songs = $album->get_songs();
 872                  break;
 873              case 'artist':
 874                  $artist = new Artist($id);
 875                  $songs = $artist->get_songs();
 876                  break;
 877              case 'song':
 878                  $songs[] = $id;
 879                  break;
 880          } // end switch type
 881  
 882          foreach ($songs as $song_id) {
 883              $song = new Song($song_id);
 884              $info = self::update_media_from_tags($song,'','');
 885  
 886              if ($info['change']) {
 887                  $file = scrub_out($song->file);
 888                  echo "<dl>\n\t<dd>";
 889                  echo "<strong>$file " . T_('Updated') . "</strong>\n";
 890                  echo $info['text'];
 891                  echo "\t</dd>\n</dl><hr align=\"left\" width=\"50%\" />";
 892                  flush();
 893              } // if change
 894              else {
 895                  echo"<dl>\n\t<dd>";
 896                  echo "<strong>" . scrub_out($song->file) . "</strong><br />" . T_('No Update Needed') . "\n";
 897                  echo "\t</dd>\n</dl><hr align=\"left\" width=\"50%\" />";
 898                  flush();
 899              }
 900          } // foreach songs
 901  
 902          self::gc();
 903  
 904      } // update_single_item
 905  
 906      /**
 907       * update_media_from_tags
 908       * This is a 'wrapper' function calls the update function for the media
 909       * type in question
 910       */
 911      public static function update_media_from_tags($media, $sort_pattern='', $rename_pattern='')
 912      {
 913          // Check for patterns
 914          if (!$sort_pattern OR !$rename_pattern) {
 915              $catalog = Catalog::create_from_id($media->catalog);
 916              $sort_pattern = $catalog->sort_pattern;
 917              $rename_pattern = $catalog->rename_pattern;
 918          }
 919  
 920          debug_event('tag-read', 'Reading tags from ' . $media->file, 5);
 921  
 922          $vainfo = new vainfo($media->file,'','','',$sort_pattern,$rename_pattern);
 923          $vainfo->get_info();
 924  
 925          $key = vainfo::get_tag_type($vainfo->tags);
 926  
 927          $results = vainfo::clean_tag_info($vainfo->tags,$key,$media->file);
 928  
 929          // Figure out what type of object this is and call the right
 930          // function, giving it the stuff we've figured out above
 931          $name = (get_class($media) == 'Song') ? 'song' : 'video';
 932  
 933          $function = 'update_' . $name . '_from_tags';
 934  
 935          $return = call_user_func(array('Catalog',$function),$results,$media);
 936  
 937          return $return;
 938  
 939      } // update_media_from_tags
 940  
 941      /**
 942       * update_video_from_tags
 943       * Updates the video info based on tags
 944       *
 945       * @SuppressWarnings(PHPMD.UnusedFormalParameter)
 946       */
 947      public static function update_video_from_tags($results,$video)
 948      {
 949          // Pretty sweet function here
 950          return $results;
 951  
 952      } // update_video_from_tags
 953  
 954      /**
 955       * update_song_from_tags
 956       * Updates the song info based on tags; this is called from a bunch of
 957       * different places and passes in a full fledged song object, so it's a
 958       * static function.
 959       * FIXME: This is an ugly mess, this really needs to be consolidated and
 960       * cleaned up.
 961       */
 962      public static function update_song_from_tags($results,$song)
 963      {
 964          /* Setup the vars */
 965          $new_song         = new Song();
 966          $new_song->file        = $results['file'];
 967          $new_song->title    = $results['title'];
 968          $new_song->year        = $results['year'];
 969          $new_song->comment    = $results['comment'];
 970          $new_song->language    = $results['language'];
 971          $new_song->lyrics    = $results['lyrics'];
 972          $new_song->bitrate    = $results['bitrate'];
 973          $new_song->rate        = $results['rate'];
 974          $new_song->mode        = ($results['mode'] == 'cbr') ? 'cbr' : 'vbr';
 975          $new_song->size        = $results['size'];
 976          $new_song->time        = $results['time'];
 977          $new_song->mime        = $results['mime'];
 978          $new_song->track    = intval($results['track']);
 979          $new_song->mbid        = $results['mb_trackid'];
 980          $artist            = $results['artist'];
 981          $artist_mbid        = $results['mb_artistid'];
 982          $album            = $results['album'];
 983          $album_mbid        = $results['mb_albumid'];
 984          $disk            = $results['disk'];
 985          $tags            = $results['genre'];    // multiple genre support makes this an array
 986  
 987          /*
 988          * We have the artist/genre/album name need to check it in the tables
 989          * If found then add & return id, else return id
 990          */
 991          $new_song->artist = Artist::check($artist, $artist_mbid);
 992          $new_song->f_artist = $artist;
 993          $new_song->album = Album::check($album, $new_song->year, $disk, $album_mbid);
 994          $new_song->f_album = $album . " - " . $new_song->year;
 995          $new_song->title = self::check_title($new_song->title,$new_song->file);
 996  
 997          // Nothing to assign here this is a multi-value doodly
 998          // multiple genre support
 999          if (is_array($tags)) {
1000              foreach ($tags as $tag) {
1001                  $tag = trim($tag);
1002                  //self::check_tag($tag,$song->id);
1003                  //self::check_tag($tag,$new_song->album,'album');
1004                  //self::check_tag($tag,$new_song->artist,'artist');
1005              }
1006          }
1007  
1008          /* Since we're doing a full compare make sure we fill the extended information */
1009          $song->fill_ext_info();
1010  
1011          $info = Song::compare_song_information($song,$new_song);
1012  
1013          if ($info['change']) {
1014              debug_event('update', "$song->file : differences found, updating database", 5);
1015              $song->update_song($song->id,$new_song);
1016              // Refine our reference
1017              //$song = $new_song;
1018          } else {
1019              debug_event('update', "$song->file : no differences found", 5);
1020          }
1021  
1022          return $info;
1023  
1024      } // update_song_from_tags
1025  
1026      /**
1027       * clean_catalog
1028       *
1029       * Cleans the catalog of files that no longer exist.
1030       */
1031      public function clean_catalog()
1032      {
1033          // We don't want to run out of time
1034          set_time_limit(0);
1035  
1036          debug_event('clean', 'Starting on ' . $this->name, 5);
1037  
1038          require AmpConfig::get('prefix') . '/templates/show_clean_catalog.inc.php';
1039          ob_flush();
1040          flush();
1041  
1042          $dead_total = $this->clean_catalog_proc();
1043  
1044          debug_event('clean', 'clean finished, ' . $dead_total . ' removed from '. $this->name, 5);
1045  
1046          // Remove any orphaned artists/albums/etc.
1047          self::gc();
1048  
1049          UI::show_box_top();
1050          echo "<strong>";
1051          printf (ngettext('Catalog Clean Done. %d file removed.', 'Catalog Clean Done. %d files removed.', $dead_total), $dead_total);
1052          echo "</strong><br />\n\n";
1053          echo "<br />\n";
1054          UI::show_box_bottom();
1055          ob_flush();
1056          flush();
1057  
1058          $this->update_last_clean();
1059      } // clean_catalog
1060  
1061      /**
1062       * verify_catalog
1063       * This function verify the catalog
1064       */
1065      public function verify_catalog()
1066      {
1067  
1068          require AmpConfig::get('prefix') . '/templates/show_verify_catalog.inc.php';
1069          ob_flush();
1070          flush();
1071  
1072          $verified = $this->verify_catalog_proc();+
1073  
1074          UI::show_box_top();
1075          echo '<strong>';
1076          printf(T_('Catalog Verify Done. %d of %d files updated.'), $verified['updated'], $verified['total']);
1077          echo "</strong><br />\n";
1078          echo "<br />\n";
1079          UI::show_box_bottom();
1080          ob_flush();
1081          flush();
1082  
1083          return true;
1084      } // verify_catalog
1085  
1086      /**
1087       * gc
1088       *
1089       * This is a wrapper function for all of the different cleaning
1090       * functions, it runs them in an order that resembles correctness.
1091       */
1092      public static function gc()
1093      {
1094          debug_event('catalog', 'Database cleanup started', 5);
1095          Song::gc();
1096          Album::gc();
1097          Artist::gc();
1098          Art::gc();
1099          Stats::gc();
1100          Rating::gc();
1101          Userflag::gc();
1102          Playlist::gc();
1103          Tmp_Playlist::gc();
1104          Shoutbox::gc();
1105          Tag::gc();
1106          debug_event('catalog', 'Database cleanup ended', 5);
1107      }
1108  
1109      /**
1110       * trim_prefix
1111       * Splits the prefix from the string
1112       */
1113      public static function trim_prefix($string)
1114      {
1115          $prefix_pattern = '/^(' . implode('\\s|',explode('|',AmpConfig::get('catalog_prefix_pattern'))) . '\\s)(.*)/i';
1116          preg_match($prefix_pattern, $string, $matches);
1117  
1118          if (count($matches)) {
1119              $string = trim($matches[2]);
1120              $prefix = trim($matches[1]);
1121          } else {
1122              $prefix = null;
1123          }
1124  
1125          return array('string' => $string, 'prefix' => $prefix);
1126      } // trim_prefix
1127  
1128      /**
1129       * check_title
1130       * this checks to make sure something is
1131       * set on the title, if it isn't it looks at the
1132       * filename and trys to set the title based on that
1133       */
1134      public static function check_title($title,$file=0)
1135      {
1136          if (strlen(trim($title)) < 1) {
1137              $title = Dba::escape($file);
1138          }
1139  
1140          return $title;
1141  
1142      } // check_title
1143  
1144      /**
1145       * playlist_import
1146       * Attempts to create a Public Playlist based on the playlist file
1147       */
1148      public static function import_playlist($playlist)
1149      {
1150          $data = file_get_contents($playlist);
1151          if (substr($playlist, -3,3) == 'm3u') {
1152              $files = self::parse_m3u($data);
1153          } elseif (substr($playlist, -3,3) == 'pls') {
1154              $files = self::parse_pls($data);
1155          } elseif (substr($playlist, -3,3) == 'asx') {
1156              $files = self::parse_asx($data);
1157          } elseif (substr($playlist, -4,4) == 'xspf') {
1158              $files = self::parse_xspf($data);
1159          }
1160  
1161          $songs = array();
1162          $pinfo = pathinfo($playlist);
1163          if (isset($files)) {
1164              foreach ($files as $file) {
1165                  $file = trim($file);
1166                  // Check to see if it's a url from this ampache instance
1167                  if (substr($file, 0, strlen(AmpConfig::get('web_path'))) == AmpConfig::get('web_path')) {
1168                      $data = Stream_URL::parse($file);
1169                      $sql = 'SELECT COUNT(*) FROM `song` WHERE `id` = ?';
1170                      $db_results = Dba::read($sql, array($data['id']));
1171                      if (Dba::num_rows($db_results)) {
1172                          $songs[] = $data['id'];
1173                      }
1174                  } // end if it's an http url
1175                  else {
1176                      // Remove file:// prefix if any
1177                      if (strpos($file, "file://") !== false) {
1178                          $file = urldecode(substr($file, 7));
1179                          if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1180                              // Removing starting / on Windows OS.
1181                              if (substr($file, 0, 1) == '/') {
1182                                  $file = substr($file, 1);
1183                              }
1184                              // Restore real directory separator
1185                              $file = str_replace("/", DIRECTORY_SEPARATOR, $file);
1186                          }
1187                      }
1188                      debug_event('catalog', 'Add file ' . $file . ' to playlist.', '5');
1189  
1190                      // First, try to found the file as absolute path
1191                      $sql = "SELECT `id` FROM `song` WHERE `file` = ?";
1192                      $db_results = Dba::read($sql, array($file));
1193                      $results = Dba::fetch_assoc($db_results);
1194  
1195                      if (isset($results['id'])) {
1196                          $songs[] = $results['id'];
1197                      } else {
1198                          // Not found in absolute path, create it from relative path
1199                          $file = $pinfo['dirname'] . DIRECTORY_SEPARATOR . $file;
1200                          // Normalize the file path. realpath requires the files to exists.
1201                          $file = realpath($file);
1202                          if ($file) {
1203                              $sql = "SELECT `id` FROM `song` WHERE `file` = ?";
1204                              $db_results = Dba::read($sql, array($file));
1205                              $results = Dba::fetch_assoc($db_results);
1206  
1207                              if (isset($results['id'])) {
1208                                  $songs[] = $results['id'];
1209                              }
1210                          }
1211                      }
1212                  } // if it's a file
1213              }
1214          }
1215  
1216          debug_event('import_playlist', "Parsed " . $playlist . ", found " . count($songs) . " songs", 5);
1217  
1218          if (count($songs)) {
1219              $name = $pinfo['extension'] . " - " . $pinfo['filename'];
1220              $playlist_id = Playlist::create($name, 'public');
1221  
1222              if (!$playlist_id) {
1223                  return array(
1224                      'success' => false,
1225                      'error' => T_('Failed to create playlist.'),
1226                  );
1227              }
1228  
1229              /* Recreate the Playlist */
1230              $playlist = new Playlist($playlist_id);
1231              $playlist->add_songs($songs, true);
1232  
1233              return array(
1234                  'success' => true,
1235                  'id' => $playlist_id,
1236                  'count' => count($songs)
1237              );
1238          }
1239  
1240          return array(
1241              'success' => false,
1242              'error' => T_('No valid songs found in playlist file.')
1243          );
1244      }
1245  
1246      /**
1247       * parse_m3u
1248       * this takes m3u filename and then attempts to found song filenames listed in the m3u
1249       */
1250      public static function parse_m3u($data)
1251      {
1252          $files = array();
1253          $results = explode("\n", $data);
1254  
1255          foreach ($results as $value) {
1256              $value = trim($value);
1257              if (!empty($value) && substr($value, 0, 1) != '#') {
1258                  $files[] = $value;
1259              }
1260          }
1261  
1262          return $files;
1263      } // parse_m3u
1264  
1265      /**
1266       * parse_pls
1267       * this takes pls filename and then attempts to found song filenames listed in the pls
1268       */
1269      public static function parse_pls($data)
1270      {
1271          $files = array();
1272          $results = explode("\n", $data);
1273  
1274          foreach ($results as $value) {
1275              $value = trim($value);
1276              if (preg_match("/file[0-9]+[\s]*\=(.*)/i", $value, $matches)) {
1277                  $file = trim($matches[1]);
1278                  if (!empty($file)) {
1279                      $files[] = $file;
1280                  }
1281              }
1282          }
1283  
1284          return $files;
1285      } // parse_pls
1286  
1287      /**
1288       * parse_asx
1289       * this takes asx filename and then attempts to found song filenames listed in the asx
1290       */
1291      public static function parse_asx($data)
1292      {
1293          $files = array();
1294          $xml = simplexml_load_string($data);
1295  
1296          if ($xml) {
1297              foreach ($xml->entry as $entry) {
1298                  $file = trim($entry->ref['href']);
1299                  if (!empty($file)) {
1300                      $files[] = $file;
1301                  }
1302              }
1303          }
1304  
1305          return $files;
1306      } // parse_asx
1307  
1308      /**
1309       * parse_xspf
1310       * this takes xspf filename and then attempts to found song filenames listed in the xspf
1311       */
1312      public static function parse_xspf($data)
1313      {
1314          $files = array();
1315          $xml = simplexml_load_string($data);
1316          if ($xml) {
1317              foreach ($xml->trackList->track as $track) {
1318                  $file = trim($track->location);
1319                  if (!empty($file)) {
1320                      $files[] = $file;
1321                  }
1322              }
1323          }
1324  
1325          return $files;
1326      } // parse_xspf
1327  
1328      /**
1329       * delete
1330       * Deletes the catalog and everything associated with it
1331       * it takes the catalog id
1332       */
1333      public static function delete($catalog_id)
1334      {
1335          // Large catalog deletion can take time
1336          set_time_limit(0);
1337  
1338          // First remove the songs in this catalog
1339          $sql = "DELETE FROM `song` WHERE `catalog` = ?";
1340          $db_results = Dba::write($sql, array($catalog_id));
1341  
1342          // Only if the previous one works do we go on
1343          if (!$db_results) { return false; }
1344  
1345          $sql = "DELETE FROM `video` WHERE `catalog` = ?";
1346          $db_results = Dba::write($sql, array($catalog_id));
1347  
1348          if (!$db_results) { return false; }
1349  
1350          $catalog = self::create_from_id($catalog_id);
1351  
1352          $sql = 'DELETE FROM `catalog_' . $catalog->get_type() . '` WHERE catalog_id = ?';
1353          $db_results = Dba::write($sql, array($catalog_id));
1354  
1355          if (!$db_results) { return false; }
1356  
1357          // Next Remove the Catalog Entry it's self
1358          $sql = "DELETE FROM `catalog` WHERE `id` = ?";
1359          Dba::write($sql, array($catalog_id));
1360  
1361          // Run the cleaners...
1362          self::gc();
1363  
1364      } // delete
1365  
1366      /**
1367       * exports the catalog
1368       * it exports all songs in the database to the given export type.
1369       */
1370      public static function export($type, $catalog_id = '')
1371      {
1372          // Select all songs in catalog
1373          $params = array();
1374          if ($catalog_id) {
1375              $sql = 'SELECT `id` FROM `song` ' .
1376                  "WHERE `catalog`= ? " .
1377                  'ORDER BY `album`, `track`';
1378              $params[] = $catalog_id;
1379          } else {
1380              $sql = 'SELECT `id` FROM `song` ORDER BY `album`, `track`';
1381          }
1382          $db_results = Dba::read($sql, $params);
1383  
1384          switch ($type) {
1385              case 'itunes':
1386                  echo xml_get_header('itunes');
1387                  while ($results = Dba::fetch_assoc($db_results)) {
1388                      $song = new Song($results['id']);
1389                      $song->format();
1390  
1391                      $xml = array();
1392                      $xml['key']= $results['id'];
1393                      $xml['dict']['Track ID']= intval($results['id']);
1394                      $xml['dict']['Name'] = $song->title;
1395                      $xml['dict']['Artist'] = $song->f_artist_full;
1396                      $xml['dict']['Album'] = $song->f_album_full;
1397                      $xml['dict']['Total Time'] = intval($song->time) * 1000; // iTunes uses milliseconds
1398                      $xml['dict']['Track Number'] = intval($song->track);
1399                      $xml['dict']['Year'] = intval($song->year);
1400                      $xml['dict']['Date Added'] = date("Y-m-d\TH:i:s\Z",$song->addition_time);
1401                      $xml['dict']['Bit Rate'] = intval($song->bitrate/1000);
1402                      $xml['dict']['Sample Rate'] = intval($song->rate);
1403                      $xml['dict']['Play Count'] = intval($song->played);
1404                      $xml['dict']['Track Type'] = "URL";
1405                      $xml['dict']['Location'] = Song::play_url($song->id);
1406                      echo xoutput_from_array($xml, 1, 'itunes');
1407                      // flush output buffer
1408                  } // while result
1409                  echo xml_get_footer('itunes');
1410  
1411                  break;
1412              case 'csv':
1413                  echo "ID,Title,Artist,Album,Length,Track,Year,Date Added,Bitrate,Played,File\n";
1414                  while ($results = Dba::fetch_assoc($db_results)) {
1415                      $song = new Song($results['id']);
1416                      $song->format();
1417                      echo '"' . $song->id . '","' .
1418                          $song->title . '","' .
1419                          $song->f_artist_full . '","' .
1420                          $song->f_album_full .'","' .
1421                          $song->f_time . '","' .
1422                          $song->f_track . '","' .
1423                          $song->year .'","' .
1424                          date("Y-m-d\TH:i:s\Z", $song->addition_time) . '","' .
1425                          $song->f_bitrate .'","' .
1426                          $song->played . '","' .
1427                          $song->file . "\n";
1428                  }
1429                  break;
1430          } // end switch
1431  
1432      } // export
1433  
1434  } // end of catalog class

title

Description

title

Description

title

Description

title

title

Body